Lumiera 0.pre.04~rc.1
»edit your freedom«
Loading...
Searching...
No Matches
proc-node.cpp
Go to the documentation of this file.
1/*
2 ProcNode - Implementation of render node processing
3
4 Copyright (C)
5 2024, Hermann Vosseler <Ichthyostega@web.de>
6
7  **Lumiera** is free software; you can redistribute it and/or modify it
8  under the terms of the GNU General Public License as published by the
9  Free Software Foundation; either version 2 of the License, or (at your
10  option) any later version. See the file COPYING for further details.
11
12* *****************************************************************/
13
14
27#include "lib/iter-explorer.hpp"
28#include "lib/format-string.hpp"
29#include "lib/format-util.hpp"
30#include "lib/parse.hpp"
31#include "lib/util.hpp"
32
33// used for a »backdoor access« in PortDiagnostic::srcPorts
36
37#include <boost/functional/hash.hpp>
38#include <unordered_set>
39#include <set>
40
41
42namespace steam {
43namespace engine {
44
45 using lib::explore;
46 using util::_Fmt;
47 using util::isnil;
48 using util::unConst;
49 using util::contains;
51 using boost::hash_combine;
52
53 namespace {// Details: parsing, registration and symbol table for node spec data...
54
55 std::unordered_set<ProcID> procRegistry;
56 std::unordered_set<string> symbRegistry;
57
59 void inline
60 dedupSymbol (StrView& symbol)
61 {
62 auto res = symbRegistry.emplace (symbol);
63 symbol = *res.first;
64 }
65
66 /* ===== Parse nested spec ===== */
67
68 using lib::meta::Nil;
73 using std::regex_match;
74 using std::regex;
75
76 const regex SPEC_CONTENT{R"_([^,\\\‍(\)\[\]{}<>"]+)_", regex::optimize};
77 const regex NON_QUOTE {R"_([^"\\]+)_" , regex::optimize};
78 const regex ESCAPE {R"_(\\.)_" , regex::optimize};
79 const regex COMMA {R"_(,)_" , regex::optimize};
80 const regex D_QUOTE {R"_(")_" , regex::optimize};
81
82 auto quoted = accept_repeated(accept(NON_QUOTE).alt(ESCAPE));
83 auto quote = accept_bracket(D_QUOTE,D_QUOTE, quoted);
84
85 template<char OPE, char CLO>
86 auto&
87 syntaxBracketed()
88 {
89 string esc{"\\"};
90 regex OPENING{esc+OPE};
91 regex CLOSING{esc+CLO};
92 regex NON_PAREN{R"_([^\\)_"+esc+OPE+esc+CLO+"]+"};
93
94 static auto paren = expectResult<Nil>();
95 auto parenContent = accept_repeated(accept(NON_PAREN)
96 .alt(ESCAPE)
97 .alt(quote)
98 .alt(paren));
99
100 paren = accept_bracket(OPENING,CLOSING, parenContent).bind([](auto){ return Nil{}; });
101 return paren;
102 }
103
104 auto specTermSyntax = accept_repeated(accept(SPEC_CONTENT)
105 .alt(ESCAPE)
106 .alt(quote)
107 .alt(syntaxBracketed<'(',')'>())
108 .alt(syntaxBracketed<'<','>'>())
109 .alt(syntaxBracketed<'[',']'>())
110 .alt(syntaxBracketed<'{','}'>())
111 )
112 .bindMatch();
113
114
115 const regex REPEAT_SPEC {R"_(^(.+)\s*/(\d+)\s*$)_", regex::optimize};
116
123 template<class IT>
124 struct RepetitionExpander
126 {
127 using Core = lib::IterStateCore<IT>;
128
129 mutable uint repeat_{0};
130 mutable std::smatch mat_;
131
132 StrView
133 yield() const
134 {
135 if (not repeat_)
136 { // check if the next string ends with repetition marker /NUM
137 if (not regex_match (*Core::srcIter(), mat_, REPEAT_SPEC))
138 return *Core::srcIter(); // no repetition -> pass through
139
140 // setup repetition by extracting the repetition count
141 repeat_ = boost::lexical_cast<uint>(mat_.str(2));
142 }
143 return StrView(& *mat_[1].first, mat_[1].length());
144 } // several repetitions created from same source
145
146 void
147 iterNext()
148 { // hold iteration until all repetitions were delivered
149 if (repeat_)
150 --repeat_;
151 if (not repeat_)
152 ++ Core::srcIter();
153 }
154
155 using Core::Core;
156 };
157
163 evaluateArgSeq (std::vector<string>& parsedArgTerms)
164 {
165 auto several = lib::makeSeveral<const string>();
166 lib::explore (parsedArgTerms)
167 .processingLayer<RepetitionExpander>()
168 .foreach([&](StrView s){ several.emplace<string>(s); });
169 return several.build();
170 }
171
172 auto
173 emptyArgSeq()
174 {
176 }
177
178 } // (END) Details...
179
180
181
182 Port::~Port() { }
183
184
192 ProcID&
193 ProcID::describe (StrView nodeSymb, StrView portSpec, ProcAttrib extAttrib)
194 {
195 REQUIRE (not isnil (nodeSymb));
196 REQUIRE (not isnil (portSpec));
197 REQUIRE (not contains (nodeSymb, ' '));
198 auto p = portSpec.find('(');
199 if (p == string::npos)
200 throw err::Invalid{_Fmt{"Spec for processing operation must contain at least one argument list. "
201 "Node:%s Spec:%s"}
202 % nodeSymb % portSpec
203 };
204 auto res = procRegistry.emplace (ProcID{nodeSymb, portSpec.substr(0,p), portSpec.substr(p), extAttrib});
205 ProcID& entry{unConst (*res.first)};
206 if (res.second)
207 {// new record placed into the registry
208 dedupSymbol (entry.nodeName_);
209 dedupSymbol (entry.argLists_);
210 if (not isnil(entry.portQual_))
211 dedupSymbol (entry.portQual_);
212 }
213 return entry;
214 }
215
217 ProcID::ProcID (StrView nodeSymb, StrView portQual, StrView argLists, ProcAttrib extAttrib)
218 : nodeName_{nodeSymb}
219 , portQual_{portQual}
220 , argLists_{argLists}
221 , attrib_{extAttrib}
222 { }
223
229 HashVal
230 hash_value (ProcID const& procID)
231 {
232 HashVal hash = boost::hash_value (procID.nodeName_);
233 if (not isnil(procID.portQual_))
234 hash_combine (hash, procID.portQual_);
235 hash_combine (hash, procID.argLists_);
236 return hash;
237 }
238
239 string
240 ProcID::genProcName() const
241 {
242 std::ostringstream buffer;
243 buffer << genNodeSymbol()
244 << genQualifier();
245 return buffer.str();
246 }
247
248 string
249 ProcID::genProcSpec() const
250 {
251 std::ostringstream buffer;
252 buffer << genNodeSymbol()
253 << genQualifier()
254 << argLists_;
255 return buffer.str();
256 }
257
258 string
259 ProcID::genNodeName() const
260 {
261 return string{nodeName_};
262 }
263
264 string
265 ProcID::genNodeSymbol() const
266 {
267 auto p = nodeName_.find(':');
268 return p == string::npos? string{nodeName_}
269 : string{nodeName_.substr(p+1)};
270 }
271
272 string
273 ProcID::genNodeDomain() const
274 {
275 auto p = nodeName_.find(':');
276 return p == string::npos? string{}
277 : string{nodeName_.substr(0,p)};
278 }
279
280 string
281 ProcID::genQualifier() const
282 {
283 std::ostringstream buffer;
284 if (not isnil(portQual_))
285 buffer << '.' << portQual_;
286 return buffer.str();
287 }
288
289
290 namespace { // Helper to access ProcID recursively
291 ProcID&
292 procID (ProcNode& node)
293 {
294 REQUIRE (not isnil(watch(node).ports()));
295 return watch(node).ports().front().procID;
296 }
297 }
298
299 string
300 ProcID::genNodeSpec (Leads& leads) const
301 {
302 std::ostringstream buffer;
303 buffer << nodeName_;
304 if (1 != leads.size())
305 buffer << genSrcSpec(leads);
306 else
307 { // single chain....
308 ProcNode& p{leads.front().get()};
309 buffer << "◁—"
310 << procID(p).genNodeName() // show immediate predecessor
311 << procID(p).genSrcSpec( // ...followed by it's source(s)
312 watch(p).leads());
313 }
314 return buffer.str();
315 }
316
317 string
318 ProcID::genSrcSpec (Leads& leads) const
319 {
320 return isnil(leads)? string{"-◎"} // no leads => starting point itself is a source node
321 : "┉┉{"
322 + util::join(
323 explore(leads)
324 .expandAll([](ProcNode& n){ return explore(watch(n).leads()); }) // depth-first expand all predecessors
325 .filter ([](ProcNode& n){ return watch(n).isSrc(); }) // but retain only leafs (≙ source nodes)
326 .transform([](ProcNode& n){ return procID(n).nodeName_;}) // render the namespace and name of each src
327 .deduplicate()) // sort and deduplicate
328 + "}";
329 }
330
331
333 ProcID::ArgModel
334 ProcID::genArgModel() const
335 {
336 auto argListSyntax = accept_bracket(accept_repeated(0,MAX_NODE_ARG, COMMA, specTermSyntax));
337 auto argSpecSyntax = accept(argListSyntax)
338 .opt(argListSyntax)
339 .bind([](auto model) -> ProcID::ArgModel
340 {
341 auto [list1,list2] = model;
342 if (list2)
343 return {evaluateArgSeq(list1), evaluateArgSeq(*list2)};
344 else
345 return {emptyArgSeq(), evaluateArgSeq(list1)};
346 });
347
348 argSpecSyntax.parse (argLists_);
349 if (not argSpecSyntax.success())
350 throw err::Invalid{_Fmt{"Unable to parse argument list. "
351 "Node:%s Spec:%s"}
352 % genProcName() % argLists_
353 };
354 return argSpecSyntax.extractResult();
355 }
356
357
358
364 string
365 ProcNodeDiagnostic::getNodeSpec()
366 {
367 REQUIRE (not isnil(ports()));
368 return ports().front().procID.genNodeSpec (leads());
369 }
370
371 string
372 ProcNodeDiagnostic::getNodeName()
373 {
374 REQUIRE (not isnil(ports()));
375 return ports().front().procID.genNodeName();
376 }
377
378 HashVal
379 ProcNodeDiagnostic::getNodeHash()
380 {
381 UNIMPLEMENTED ("calculate an unique hash-key to designate this node");
382 }
383
388 string
389 ProcNodeDiagnostic::getPortSpec (uint portIdx)
390 {
391 auto& p{n_.wiring_.ports};
392 return portIdx < p.size()? p[portIdx].procID.genProcSpec()
393 : util::FAILURE_INDICATOR;
394 }
395
396 HashVal
397 ProcNodeDiagnostic::getPortHash (uint portIdx)
398 {
399 UNIMPLEMENTED ("calculate an unique, stable and reproducible hash-key to identify the Turnout");
400 }
401
402
403
404 namespace {// create a »backdoor access« into actual weaving-pattern instances
405
406 using _DummyProc = void(&)(Nil*);
407 using _DummyProto = FeedPrototype<_DummyProc>;
408 using _DummyMediaWeaving = MediaWeavingPattern<_DummyProto>;
409 using _RecastMediaWeaving = _TurnoutDiagnostic<_DummyMediaWeaving>;
410
411 using _EmptySpec = decltype(buildParamSpec());
412 using _DummyParamWeaving = ParamWeavingPattern<_EmptySpec>;
413 using _RecastParamWeaving = _TurnoutDiagnostic<_DummyParamWeaving>;
414
415 lib::Several<PortRef> EMPTY_PRECURSORS;
416 }
417
432 PortDiagnostic::srcPorts()
433 {
434 if (p_.procID.hasManifoldPatt())
435 {
436 auto [leads,types] = _RecastMediaWeaving::accessInternal (p_);
437 return leads;
438 }
439 else
440 if (p_.procID.hasProxyPatt())
441 {
442 Port& delegate = std::get<0>(_RecastParamWeaving::accessInternal (p_));
443 return watch(delegate).srcPorts();
444 } // recursive invocation on delegate of proxy
445 else
446 return EMPTY_PRECURSORS;
447 }
448
453 string
454 PortDiagnostic::getProcSpec()
455 {
456 return p_.procID.genProcSpec();
457 }
458
459 string
460 PortDiagnostic::getProcName()
461 {
462 return p_.procID.genProcName();
463 }
464
465 HashVal
466 PortDiagnostic::getProcHash()
467 {
468 UNIMPLEMENTED ("calculate an unique, stable and reproducible hash-key to identify the Turnout");
469 }
470
471
472 /* === cross-navigation === */
473
474 ProcNodeDiagnostic
475 ProcNodeDiagnostic::watchLead (uint leadIdx)
476 {
477 if (leadIdx >= leads().size())
478 throw err::Invalid{_Fmt{"Lead-# %d >= %d (available lead-nodes)."}
479 % leadIdx % leads().size()};
480 return watch (leads()[leadIdx]);
481 }
482
483 PortDiagnostic
484 ProcNodeDiagnostic::watchPort (uint portIdx)
485 {
486 if (portIdx >= ports().size())
487 throw err::Invalid{_Fmt{"Port-idx %d >= %d (available Ports)."}
488 % portIdx % ports().size()};
489 return watch (ports()[portIdx]);
490 }
491
492 PortDiagnostic
493 PortDiagnostic::watchLead (uint leadIdx)
494 {
495 auto& leadPorts = srcPorts();
496 if (leadIdx >= leadPorts.size())
497 throw err::Invalid{_Fmt{"Lead-Port# %d >= %d (available src-ports)."}
498 % leadIdx % leadPorts.size()};
499 return watch (leadPorts[leadIdx]);
500 }
501
502 bool
503 PortDiagnostic::verify_connected (uint input, Port& tarPort)
504 {
505 auto& leadPorts = srcPorts();
506 return input < leadPorts.size()
507 and leadPorts[input] == tarPort;
508 }
509
510 bool
511 PortDiagnostic::verify_connected (Port& tarPort)
512 {
513 for (Port& port : srcPorts())
514 if (port == tarPort)
515 return true;
516 return false;
517 }
518
519
537 _ConCheck::operator bool()
538 {
539 auto validPort = [this](uint idx) { return idx < anchor.ports().size(); };
540 auto validLead = [this](uint idx) { return idx < anchor.leads().size(); };
541 auto validSrc = [this](uint pNo
542 ,uint sNo) { return sNo < anchor.watchPort(pNo).srcPorts().size(); };
543 auto validSrcP = [this](ProcNode& lead
544 ,uint idx) { return idx < watch(lead).ports().size(); };
545
546 auto find_link = [](auto& seq1, auto& seq2)
547 {
548 return explore(seq1)
549 .transform([&](auto& elm){ return contains (seq2, *elm); })
550 .has_any();
551 };
552
553 // Determine case to handle,
554 // starting with the most constricted...
555 if (portNo and srcNo and srcNode and srcPNo)
556 return validPort(*portNo) // is_linked(node).port(portNo).asSrc(srcNo).to(srcNode).atPort(srcPNo)
557 and validSrc (*portNo, *srcNo)
558 and validSrcP(*srcNode,*srcPNo)
559 and anchor.watchPort(*portNo).srcPorts()[*srcNo]
560 == watch(*srcNode).ports()[*srcPNo];
561 else
562 if (portNo and srcNo and leadNo and srcPNo)
563 return validPort(*portNo) // is_linked(node).port(portNo).asSrc(srcNo).toLead(leadNo).atPort(srcPNo)
564 and validSrc (*portNo,*srcNo)
565 and validLead(*leadNo)
566 and validSrcP(anchor.leads()[*leadNo], *srcPNo)
567 and anchor.watchPort(*portNo).srcPorts()[*srcNo]
568 == anchor.watchLead(*leadNo).ports()[*srcPNo];
569 else
570 if (portNo and srcNo and srcPort)
571 return validPort(*portNo) // is_linked(node).port(portNo).asSrc(srcNo).to(srcPort)
572 and validSrc (*portNo,*srcNo)
573 and anchor.watchPort(*portNo).srcPorts()[*srcNo]
574 == *srcPort;
575 else
576 if (portNo and srcNo and srcNode)
577 return validPort(*portNo) // is_linked(node).port(portNo).asSrc(srcNo).to(srcNode)
578 and validSrc (*portNo,*srcNo)
579 and contains (watch(*srcNode).ports()
580 ,anchor.watchPort(*portNo).srcPorts()[*srcNo]);
581 else
582 if (portNo and srcNo and leadNo)
583 return validPort(*portNo) // is_linked(node).port(portNo).asSrc(srcNo).toLead(leadNo)
584 and validSrc (*portNo,*srcNo)
585 and validLead(*leadNo)
586 and contains (anchor.watchLead(*leadNo).ports()
587 ,anchor.watchPort(*portNo).srcPorts()[*srcNo]);
588 else
589 if (portNo and srcNo)
590 return validPort(*portNo) // is_linked(node).port(portNo).asSrc(srcNo)
591 and validSrc (*portNo,*srcNo);
592 else
593 if (portNo and srcNode and srcPNo)
594 return validPort(*portNo) // is_linked(node).port(portNo).to(srcNode).atPort(srcPNo)
595 and validSrcP(*srcNode,*srcPNo)
596 and contains (anchor.watchPort(*portNo).srcPorts()
597 ,watch(*srcNode).ports()[*srcPNo]);
598 else
599 if (portNo and leadNo and srcPNo)
600 return validPort(*portNo) // is_linked(node).port(portNo).toLead(leadNo).atPort(srcPNo)
601 and validLead(*leadNo)
602 and validSrcP(anchor.leads()[*leadNo], *srcPNo)
603 and contains (anchor.watchPort(*portNo).srcPorts()
604 ,anchor.watchLead(*leadNo).ports()[*srcPNo]);
605 else
606 if (portNo and srcPort)
607 return validPort(*portNo) // is_linked(node).port(portNo).to(srcPort)
608 and contains (anchor.watchPort(*portNo).srcPorts()
609 ,*srcPort);
610 else
611 if (portNo and srcNode)
612 return validPort(*portNo) // is_linked(node).port(portNo).to(srcNode)
613 and find_link(watch(*srcNode).ports()
614 ,anchor.watchPort(*portNo).srcPorts());
615 else
616 if (portNo and leadNo)
617 return validPort(*portNo) // is_linked(node).port(portNo).toLead(leadNo)
618 and validLead(*leadNo)
619 and find_link(anchor.watchLead(*leadNo).ports()
620 ,anchor.watchPort(*portNo).srcPorts());
621 else
622 if (portNo)
623 return validPort(*portNo) // is_linked(node).port(portNo)
624 and not anchor.watchPort(*portNo).isSrc();
625 else
626 if (srcNode and leadNo)
627 return validLead(*leadNo) // is_linked(node).to(srcNode).asLead(leadNo)
628 and anchor.leads()[*leadNo]
629 == *srcNode;
630 else
631 if (srcNode)
632 return contains (anchor.leads() // is_linked(node).to(srcNode)
633 ,*srcNode);
634 return false;
635 }
636
637
638}} // namespace steam::engine
Adapter to dress up an existing »Lumiera Forward Iterator« as »state core«.
Abstraction: Fixed array of elements.
Definition several.hpp:166
Derived specific exceptions within Lumiera's exception hierarchy.
Definition error.hpp:193
virtual ~Port()
this is an interface
static ProcID & describe(StrView nodeSymb, StrView portSpec, ProcAttrib extAttrib=ProcAttrib{})
build and register a processing ID descriptor
ProcID(StrView nodeSymb, StrView portQual, StrView argLists, ProcAttrib)
A front-end for using printf-style formatting.
Front-end for printf-style string template interpolation.
Collection of small helpers and convenience shortcuts for diagnostics & formatting.
#define hash
unsigned int uint
Definition integral.hpp:29
Building tree expanding and backtracking evaluations within hierarchical scopes.
Construction set to assemble and operate a data processing scheme within a Render Node.
_TransformIterT< IT, FUN >::Iter transform(IT &&source, FUN processingFunc)
pipes a given Lumiera Forward Iterator through a transformation function and wraps the resulting tran...
NoUsableHashDefinition hash_value(...)
declared for metaprogramming only, never defined
»Empty« mark
Definition typelist.hpp:82
auto explore(IT &&srcSeq)
start building a IterExplorer by suitably wrapping the given iterable source.
size_t HashVal
a STL compatible hash value
Definition hash-value.h:52
ProcNodeDiagnostic watch(ProcNode &theNode)
std::string_view StrView
Definition proc-id.hpp:82
AnyPair entry(Query< TY > const &query, typename WrapReturn< TY >::Wrapper &obj)
helper to simplify creating mock table entries, wrapped correctly
Steam-Layer implementation namespace root.
auto accept_repeated(uint min, uint max, SPEC1 &&delimDef, SPEC2 &&clauseDef)
Start Syntax clause with a repeated sub-clause, with separator and repetition limit; repetitions ∊ [m...
Definition parse.hpp:919
auto accept(SPEC &&clauseDef)
===== Syntax clause builder DSL =====
Definition parse.hpp:891
auto accept()
empty Syntax clause to start further definition
Definition parse.hpp:897
auto accept_bracket(SPEC1 &&openDef, SPEC2 &&closeDef, SPEC3 &&bodyDef)
Start Syntax with a sub-clause enclosed into a bracketing construct.
Definition parse.hpp:978
auto expectResult()
Setup an assignable, recursive Syntax clause, initially empty.
Definition parse.hpp:1056
std::string_view StrView
Definition parse.hpp:174
bool has_any(IT i, IT end, FUN predicate)
Existential quantification: check if any element of a collection satisfies the given predicate.
bool isSameObject(A const &a, B const &b)
compare plain object identity, based directly on the referee's memory identities.
Definition util.hpp:421
bool contains(MAP &map, typename MAP::key_type const &key)
shortcut for containment test on a map
Definition util.hpp:230
OBJ * unConst(const OBJ *)
shortcut to save some typing when having to define const and non-const variants of member functions
Definition util.hpp:358
string join(COLL &&coll, string const &delim=", ")
enumerate a collection's contents, separated by delimiter.
bool isnil(lib::time::Duration const &dur)
Construction kit to establish a set of parameters pre-computed prior to invocation of nested nodes.
Convenience wrappers and definitions for parsing structured definitions.
Metadata interface to generate symbolic and hash ID tags for media processing steps.
Interface to the processing nodes and the Render Nodes network.
Builder to create and populate instances of the lib::Several container.
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...