Library: extract Graphviz-DOT generation helpers
...these were developed driven by the immediate need to visualise ''random generated computation patterns'' for ''Scheduler load testing.'' The abstraction level of this DSL is low and structures closely match some clauses of the DOT language; this approach may not yet be adequate to generate more complex graph structures and was extracted as a starting point for further refinements....
This commit is contained in:
parent
1c4b1a2973
commit
76f250a5cf
3 changed files with 211 additions and 549 deletions
|
|
@ -22,9 +22,41 @@
|
|||
|
||||
/** @file dot-gen.hpp
|
||||
** Support for generation of Graphviz-DOT code for structure visualisation.
|
||||
** The [dot language] offers a simple notation to represent structural information
|
||||
** as diagrams of abstract graphs and networks. The *Graphviz* layout programs
|
||||
** translate these into automatically arranged diagrams graphs, relying on basic
|
||||
** layout schemes like directed and undirected graphs, force-directed placement,
|
||||
** radial arrangements, clustered graphs or squarified treemap layout. These
|
||||
** visualisations can be rendered as images, vector graphic SVG or PDF and
|
||||
** a lot of further formats.
|
||||
**
|
||||
** The namespace lib::dot_gen contains a set of integrated builder DSL functions
|
||||
** to simplify the task of syntax generation; notable it is possible to set up
|
||||
** several [sections](\ref lib::dot_gen::Section), which can then be gradually
|
||||
** populated with definition clauses while traversing a data structure.
|
||||
**
|
||||
** @todo 11/2023 this is an initial draft, shaped by the immediate need to visualise
|
||||
** [random generated](\ref vault::gear::test::TestChainLoad) computation
|
||||
** patterns for Scheduler [load testing](\ref SchedulerStress_test).
|
||||
** The abstraction level of this DSL is low and structures closely match
|
||||
** some clauses of the DOT language; this approach may not yet be adequate
|
||||
** to generate more complex graph structures and was extracted as a starting
|
||||
** point for further refinements.
|
||||
**
|
||||
** ## Usage
|
||||
** The top-level entrance point is lib::dot_get::digraph(), allowing to combine a
|
||||
** series of lib::dot_gen::Section definitions into a DOT script, which can then
|
||||
** be retrieved by string conversion (or sent to standard output)
|
||||
** - Section is an accumulator of lines with DOT language specs
|
||||
** - Code is a string with syntax, used as base for some pre-configured terms
|
||||
** - Node defines a variable name, but can be augmented with attributes to
|
||||
** build a _node-statement_
|
||||
** - Scope is meant as a device to group several nodes together, typically to
|
||||
** form a cluster or stratum in the generated layout
|
||||
**
|
||||
** @see TestChainLoad_test
|
||||
** @see SchedulerStress_test
|
||||
** [dot language]: https://graphviz.org/doc/info/lang.html
|
||||
*/
|
||||
|
||||
|
||||
|
|
@ -32,407 +64,182 @@
|
|||
#define LIB_DOT_GEN_H
|
||||
|
||||
|
||||
#include "vault/common.hpp"
|
||||
#include "lib/test/test-helper.hpp"
|
||||
#include "lib/format-util.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
//#include "vault/gear/job.h"
|
||||
//#include "vault/gear/activity.hpp"
|
||||
//#include "vault/gear/nop-job-functor.hpp"
|
||||
//#include "lib/time/timevalue.hpp"
|
||||
//#include "lib/meta/variadic-helper.hpp"
|
||||
//#include "lib/meta/function.hpp"
|
||||
//#include "lib/wrapper.hpp"
|
||||
#include "lib/format-util.hpp" /////////////////TODO used only for dot generation
|
||||
#include "lib/util.hpp" /////////////////TODO used only for dot generation
|
||||
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
//#include <string>
|
||||
//#include <deque>
|
||||
#include <memory>
|
||||
#include <sstream> /////////////////TODO used only for dot generation
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector> /////////////////TODO used only for dot generation
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace dot_gen { ///< Collection of builder DSL functions to generate Graphviz-DOT code
|
||||
|
||||
// using std::string;
|
||||
// using std::function;
|
||||
// using lib::time::TimeValue;
|
||||
// using lib::time::Time;
|
||||
// using lib::time::FSecs;
|
||||
// using lib::time::Offset;
|
||||
// using lib::meta::RebindVariadic;
|
||||
using util::toString; /////////////////TODO used only for dot generation
|
||||
using util::isnil; /////////////////TODO used only for dot generation
|
||||
using util::max;
|
||||
using util::unConst;
|
||||
// using std::forward;
|
||||
using util::toString;
|
||||
using util::isnil;
|
||||
using std::string;
|
||||
using std::swap;
|
||||
using std::move;
|
||||
using boost::hash_combine;
|
||||
|
||||
|
||||
namespace {// Diagnostic markers
|
||||
// const string MARK_INC{"IncSeq"};
|
||||
// const string MARK_SEQ{"Seq"};
|
||||
const size_t DEFAULT_FAN = 16;
|
||||
const size_t DEFAULT_SIZ = 256;
|
||||
|
||||
// using SIG_JobDiagnostic = void(Time, int32_t);
|
||||
}
|
||||
|
||||
|
||||
namespace dot {
|
||||
|
||||
struct Code : string
|
||||
{
|
||||
using string::string;
|
||||
Code(string const& c) : string{c} { }
|
||||
Code(string && c) : string{move(c)}{ }
|
||||
};
|
||||
|
||||
struct Section
|
||||
{
|
||||
std::vector<string> lines;
|
||||
|
||||
Section (string name)
|
||||
: lines{"// "+name}
|
||||
{ }
|
||||
|
||||
Section&&
|
||||
operator+= (Code const& code)
|
||||
{
|
||||
lines.emplace_back(code);
|
||||
return move(*this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to generate DOT-Graphviz rendering of topology
|
||||
*/
|
||||
class DotOut
|
||||
{
|
||||
std::ostringstream buff_;
|
||||
|
||||
static uint const IDENT_STEP = 2;
|
||||
public:
|
||||
void
|
||||
putLine (string line, uint indent=0)
|
||||
{
|
||||
if (indent)
|
||||
buff_ << string(indent,' ');
|
||||
buff_ << line
|
||||
<< '\n';
|
||||
}
|
||||
|
||||
void
|
||||
put (Code const& code)
|
||||
{
|
||||
buff_ << code;
|
||||
}
|
||||
|
||||
void
|
||||
put (Section const& sect)
|
||||
{
|
||||
for (string const& line : sect.lines)
|
||||
putLine (line, IDENT_STEP);
|
||||
}
|
||||
|
||||
template<class P, class...PS>
|
||||
void
|
||||
put (P const& part, PS const& ...parts)
|
||||
{
|
||||
put (part);
|
||||
putLine ("");
|
||||
put (parts...);
|
||||
}
|
||||
|
||||
/** retrieve complete code generated thus far */
|
||||
operator string() const
|
||||
{
|
||||
return buff_.str();
|
||||
}
|
||||
};
|
||||
|
||||
struct Node : Code
|
||||
{
|
||||
Node (size_t id)
|
||||
: Code{"N"+toString(id)}
|
||||
{ }
|
||||
|
||||
Node&&
|
||||
addAttrib (string def)
|
||||
{
|
||||
if (back() != ']')
|
||||
append ("[");
|
||||
else
|
||||
{
|
||||
resize (length()-2);
|
||||
append (", ");
|
||||
}
|
||||
append (def+" ]");
|
||||
return move(*this);
|
||||
}
|
||||
|
||||
Node&&
|
||||
label (size_t i)
|
||||
{
|
||||
return addAttrib ("label="+toString(i));
|
||||
}
|
||||
|
||||
Node&&
|
||||
style (Code const& code)
|
||||
{
|
||||
if (not isnil(code))
|
||||
addAttrib (code);
|
||||
return move(*this);
|
||||
}
|
||||
};
|
||||
|
||||
struct Scope : Code
|
||||
{
|
||||
Scope (size_t id)
|
||||
: Code{"{ /*"+toString(id)+"*/ }"}
|
||||
{ }
|
||||
|
||||
Scope&&
|
||||
add (Code const& code)
|
||||
{
|
||||
resize(length()-1);
|
||||
append (code+" }");
|
||||
return move(*this);
|
||||
}
|
||||
|
||||
Scope&&
|
||||
rank (string rankSetting)
|
||||
{
|
||||
return add(Code{"rank="+rankSetting});
|
||||
}
|
||||
};
|
||||
|
||||
inline Code
|
||||
connect (size_t src, size_t dest)
|
||||
/** markup to generate a piece of code */
|
||||
struct Code : string
|
||||
{
|
||||
return Code{Node(src) +" -> "+ Node(dest)};
|
||||
}
|
||||
|
||||
template<class...COD>
|
||||
inline DotOut
|
||||
digraph (COD ...parts)
|
||||
{
|
||||
DotOut script;
|
||||
script.putLine (Code{"digraph {"});
|
||||
script.put (parts...);
|
||||
script.putLine (Code{"}"});
|
||||
return script;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A Generator for synthetic Render Jobs for Scheduler load testing.
|
||||
* @tparam maxFan maximal fan-in/out from a node, also limits maximal parallel strands.
|
||||
* @see TestChainLoad_test
|
||||
*/
|
||||
template<size_t numNodes =DEFAULT_SIZ, size_t maxFan =DEFAULT_FAN>
|
||||
class TestChainLoad
|
||||
: util::MoveOnly
|
||||
{
|
||||
|
||||
public:
|
||||
struct Node
|
||||
: util::MoveOnly
|
||||
{
|
||||
using _Arr = std::array<Node*, maxFan>;
|
||||
using Iter = typename _Arr::iterator;
|
||||
|
||||
/** Table with connections to other Node records */
|
||||
struct Tab : _Arr
|
||||
{
|
||||
Iter after = _Arr::begin();
|
||||
Iter end() { return after; }
|
||||
friend Iter end(Tab& tab) { return tab.end(); }
|
||||
|
||||
void clear() { after = _Arr::begin(); } ///< @warning pointer data in array not cleared
|
||||
|
||||
size_t size() const { return unConst(this)->end()-_Arr::begin(); }
|
||||
bool empty() const { return 0 == size(); }
|
||||
|
||||
Iter
|
||||
add(Node* n)
|
||||
{
|
||||
if (after != _Arr::end())
|
||||
{
|
||||
*after = n;
|
||||
return after++;
|
||||
}
|
||||
NOTREACHED ("excess node linkage");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
size_t hash;
|
||||
size_t level{0}, repeat{0};
|
||||
Tab pred{0}, succ{0};
|
||||
|
||||
Node(size_t seed =0)
|
||||
: hash{seed}
|
||||
{ }
|
||||
|
||||
void
|
||||
clear()
|
||||
{
|
||||
hash = 0;
|
||||
level = repeat = 0;
|
||||
pred.clear();
|
||||
succ.clear();
|
||||
}
|
||||
|
||||
Node&
|
||||
addPred (Node* other)
|
||||
{
|
||||
REQUIRE (other);
|
||||
pred.add (other);
|
||||
other->succ.add (this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Node&
|
||||
addSucc (Node* other)
|
||||
{
|
||||
REQUIRE (other);
|
||||
succ.add (other);
|
||||
other->pred.add (this);
|
||||
return *this;
|
||||
}
|
||||
Node& addPred(Node& other) { return addPred(&other); }
|
||||
Node& addSucc(Node& other) { return addSucc(&other); }
|
||||
|
||||
size_t
|
||||
calculate()
|
||||
{
|
||||
for (Node*& entry: pred)
|
||||
if (entry)
|
||||
hash_combine (hash, entry->hash);
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
using NodeTab = typename Node::Tab;
|
||||
using NodeStorage = std::array<Node, numNodes>;
|
||||
using CtrlRule = std::function<size_t(size_t, double)>;
|
||||
|
||||
std::unique_ptr<NodeStorage> nodes_;
|
||||
|
||||
CtrlRule seedingRule_ {[](size_t, double){ return 0; }};
|
||||
CtrlRule expansionRule_{[](size_t, double){ return 0; }};
|
||||
CtrlRule reductionRule_{[](size_t, double){ return 0; }};
|
||||
|
||||
public:
|
||||
TestChainLoad()
|
||||
: nodes_{new NodeStorage}
|
||||
{ }
|
||||
|
||||
|
||||
size_t size() const { return nodes_->size(); }
|
||||
size_t topLevel() const { return nodes_->back().level; }
|
||||
size_t getSeed() const { return nodes_->front().hash; }
|
||||
size_t getHash() const { return nodes_->back().hash; }
|
||||
|
||||
|
||||
/* ===== topology control ===== */
|
||||
|
||||
/**
|
||||
* Use current configuration and seed to (re)build Node connectivity.
|
||||
*/
|
||||
TestChainLoad
|
||||
buildToplolgy()
|
||||
{
|
||||
NodeTab a,b, // working data for generation
|
||||
*curr{&a}, // the current set of nodes to carry on
|
||||
*next{&b}; // the next set of nodes connected to current
|
||||
Node* node = &nodes_->front();
|
||||
size_t level{0};
|
||||
size_t expectedLevel = max (1u, numNodes/maxFan);
|
||||
|
||||
// prepare building blocks for the topology generation...
|
||||
auto height = [&](double level)
|
||||
{
|
||||
return level/expectedLevel;
|
||||
};
|
||||
auto spaceLeft = [&]{ return next->size() < maxFan; };
|
||||
auto addNode = [&]{
|
||||
Node* n = *next->add (node++);
|
||||
n->clear();
|
||||
n->level = level;
|
||||
return n;
|
||||
};
|
||||
auto apply = [&](CtrlRule& rule, Node* n)
|
||||
{
|
||||
return rule (n->hash, height(level));
|
||||
};
|
||||
|
||||
addNode(); // prime next with root node
|
||||
// visit all further nodes and establish links
|
||||
while (node < &nodes_->back())
|
||||
{
|
||||
++level;
|
||||
curr->clear();
|
||||
swap (next, curr);
|
||||
size_t toReduce{0};
|
||||
Node* r;
|
||||
REQUIRE (spaceLeft());
|
||||
for (Node* o : *curr)
|
||||
{ // follow-up on all Nodes in current level...
|
||||
o->calculate();
|
||||
size_t toSeed = apply (seedingRule_, o);
|
||||
size_t toExpand = apply (expansionRule_,o);
|
||||
while (0 < toSeed and spaceLeft())
|
||||
{ // start a new chain from seed
|
||||
Node* n = addNode();
|
||||
n->hash = this->getSeed();
|
||||
--toSeed;
|
||||
}
|
||||
while (0 < toExpand and spaceLeft())
|
||||
{ // fork out secondary chain from o
|
||||
Node* n = addNode();
|
||||
o->addSucc(n);
|
||||
--toExpand;
|
||||
}
|
||||
if (not toReduce and spaceLeft())
|
||||
{ // carry-on the chain from o
|
||||
r = addNode();
|
||||
toReduce = apply (reductionRule_, o);
|
||||
}
|
||||
else
|
||||
--toReduce;
|
||||
ENSURE (r);
|
||||
r->addPred(o);
|
||||
}
|
||||
}
|
||||
ENSURE (node == &nodes_->back());
|
||||
// connect ends of all remaining chains to top-Node
|
||||
node->clear();
|
||||
node->level = ++level;
|
||||
for (Node* o : *next)
|
||||
node->addPred(o);
|
||||
//
|
||||
return move(*this);
|
||||
}
|
||||
|
||||
private:
|
||||
using string::string;
|
||||
Code(string const& c) : string{c} { }
|
||||
Code(string && c) : string{move(c)}{ }
|
||||
};
|
||||
|
||||
|
||||
/** Accumulator to collect lines of DOT code */
|
||||
struct Section
|
||||
{
|
||||
std::vector<string> lines;
|
||||
|
||||
Section (string name)
|
||||
: lines{"// "+name}
|
||||
{ }
|
||||
|
||||
Section&&
|
||||
operator+= (Code const& code)
|
||||
{
|
||||
lines.emplace_back(code);
|
||||
return move(*this);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace lib
|
||||
|
||||
/** Helper to collect DOT-Graphviz code for output */
|
||||
class DotOut
|
||||
{
|
||||
std::ostringstream buff_;
|
||||
|
||||
static uint const IDENT_STEP = 2;
|
||||
public:
|
||||
void
|
||||
putLine (string line, uint indent=0)
|
||||
{
|
||||
if (indent)
|
||||
buff_ << string(indent,' ');
|
||||
buff_ << line
|
||||
<< '\n';
|
||||
}
|
||||
|
||||
void
|
||||
put (Code const& code)
|
||||
{
|
||||
buff_ << code;
|
||||
}
|
||||
|
||||
void
|
||||
put (Section const& sect)
|
||||
{
|
||||
for (string const& line : sect.lines)
|
||||
putLine (line, IDENT_STEP);
|
||||
}
|
||||
|
||||
template<class P, class...PS>
|
||||
void
|
||||
put (P const& part, PS const& ...parts)
|
||||
{
|
||||
put (part);
|
||||
putLine ("");
|
||||
put (parts...);
|
||||
}
|
||||
|
||||
/** retrieve complete code generated thus far */
|
||||
operator string() const
|
||||
{
|
||||
return buff_.str();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** generate a Node name or a node_statement
|
||||
* defining attributes of that node. All variables
|
||||
* use the format `N<number>`. */
|
||||
struct Node : Code
|
||||
{
|
||||
Node (size_t id)
|
||||
: Code{"N"+toString(id)}
|
||||
{ }
|
||||
|
||||
Node&&
|
||||
addAttrib (string def)
|
||||
{
|
||||
if (back() != ']')
|
||||
append ("[");
|
||||
else
|
||||
{
|
||||
resize (length()-2);
|
||||
append (", ");
|
||||
}
|
||||
append (def+" ]");
|
||||
return move(*this);
|
||||
}
|
||||
|
||||
Node&&
|
||||
label (size_t i)
|
||||
{
|
||||
return addAttrib ("label="+toString(i));
|
||||
}
|
||||
|
||||
Node&&
|
||||
style (Code const& code)
|
||||
{
|
||||
if (not isnil(code))
|
||||
addAttrib (code);
|
||||
return move(*this);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** accumulator to collect nodes grouped into a scope */
|
||||
struct Scope : Code
|
||||
{
|
||||
Scope (size_t id)
|
||||
: Code{"{ /*"+toString(id)+"*/ }"}
|
||||
{ }
|
||||
|
||||
Scope&&
|
||||
add (Code const& code)
|
||||
{
|
||||
resize(length()-1);
|
||||
append (code+" }");
|
||||
return move(*this);
|
||||
}
|
||||
|
||||
Scope&&
|
||||
rank (string rankSetting)
|
||||
{
|
||||
return add(Code{"rank="+rankSetting});
|
||||
}
|
||||
};
|
||||
|
||||
/** generate a directed node connectivity clause */
|
||||
inline Code
|
||||
connect (size_t src, size_t dest)
|
||||
{
|
||||
return Code{Node(src) +" -> "+ Node(dest)};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Entrance-point: generate a graph spec in DOT-Language.
|
||||
* @param parts a sequence of Section or Code objects to be combined and rendered
|
||||
* @return DotOut object holding the script rendered into a stringstream-buffer
|
||||
*/
|
||||
template<class...COD>
|
||||
inline DotOut
|
||||
digraph (COD ...parts)
|
||||
{
|
||||
DotOut script;
|
||||
script.putLine (Code{"digraph {"});
|
||||
script.put (parts...);
|
||||
script.putLine (Code{"}"});
|
||||
return script;
|
||||
}
|
||||
|
||||
|
||||
}} // namespace lib::dot_gen
|
||||
#endif /*LIB_DOT_GEN_H*/
|
||||
|
|
|
|||
|
|
@ -61,7 +61,10 @@
|
|||
** [scheduled as Render Jobs](\ref TestChainLoad::scheduleJobs).
|
||||
**
|
||||
** ## Observation tools
|
||||
** - jaleck
|
||||
** The generated topology can be visualised as a graph, using the Graphviz-DOT language.
|
||||
** Nodes are rendered from bottom to top, organised into strata according to the time-level
|
||||
** and showing predecessor -> successor connectivity. Seed nodes are distinguished by
|
||||
** circular shape.
|
||||
**
|
||||
** @see TestChainLoad_test
|
||||
** @see SchedulerStress_test
|
||||
|
|
@ -83,8 +86,7 @@
|
|||
//#include "lib/meta/function.hpp"
|
||||
//#include "lib/wrapper.hpp"
|
||||
#include "lib/iter-explorer.hpp"
|
||||
#include "lib/format-util.hpp" /////////////////TODO used only for dot generation
|
||||
#include "lib/util.hpp" /////////////////TODO used only for dot generation
|
||||
#include "lib/dot-gen.hpp"
|
||||
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <functional>
|
||||
|
|
@ -92,9 +94,7 @@
|
|||
//#include <string>
|
||||
//#include <deque>
|
||||
#include <memory>
|
||||
#include <sstream> /////////////////TODO used only for dot generation
|
||||
#include <string>
|
||||
#include <vector> /////////////////TODO used only for dot generation
|
||||
#include <array>
|
||||
|
||||
|
||||
|
|
@ -109,173 +109,19 @@ namespace test {
|
|||
// using lib::time::FSecs;
|
||||
// using lib::time::Offset;
|
||||
// using lib::meta::RebindVariadic;
|
||||
using util::toString; /////////////////TODO used only for dot generation
|
||||
using util::isnil; /////////////////TODO used only for dot generation
|
||||
using util::max;
|
||||
using util::unConst;
|
||||
// using std::forward;
|
||||
using std::string;
|
||||
// using std::string;
|
||||
using std::swap;
|
||||
using std::move;
|
||||
using boost::hash_combine;
|
||||
|
||||
namespace dot = lib::dot_gen;
|
||||
|
||||
namespace {// Diagnostic markers
|
||||
// const string MARK_INC{"IncSeq"};
|
||||
// const string MARK_SEQ{"Seq"};
|
||||
namespace { // Default definitions for topology generation
|
||||
const size_t DEFAULT_FAN = 16;
|
||||
const size_t DEFAULT_SIZ = 256;
|
||||
|
||||
// using SIG_JobDiagnostic = void(Time, int32_t);
|
||||
}
|
||||
|
||||
|
||||
namespace dot {
|
||||
|
||||
struct Code : string
|
||||
{
|
||||
using string::string;
|
||||
Code(string const& c) : string{c} { }
|
||||
Code(string && c) : string{move(c)}{ }
|
||||
};
|
||||
|
||||
struct Section
|
||||
{
|
||||
std::vector<string> lines;
|
||||
|
||||
Section (string name)
|
||||
: lines{"// "+name}
|
||||
{ }
|
||||
|
||||
Section&&
|
||||
operator+= (Code const& code)
|
||||
{
|
||||
lines.emplace_back(code);
|
||||
return move(*this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to generate DOT-Graphviz rendering of topology
|
||||
*/
|
||||
class DotOut
|
||||
{
|
||||
std::ostringstream buff_;
|
||||
|
||||
static uint const IDENT_STEP = 2;
|
||||
public:
|
||||
void
|
||||
putLine (string line, uint indent=0)
|
||||
{
|
||||
if (indent)
|
||||
buff_ << string(indent,' ');
|
||||
buff_ << line
|
||||
<< '\n';
|
||||
}
|
||||
|
||||
void
|
||||
put (Code const& code)
|
||||
{
|
||||
buff_ << code;
|
||||
}
|
||||
|
||||
void
|
||||
put (Section const& sect)
|
||||
{
|
||||
for (string const& line : sect.lines)
|
||||
putLine (line, IDENT_STEP);
|
||||
}
|
||||
|
||||
template<class P, class...PS>
|
||||
void
|
||||
put (P const& part, PS const& ...parts)
|
||||
{
|
||||
put (part);
|
||||
putLine ("");
|
||||
put (parts...);
|
||||
}
|
||||
|
||||
/** retrieve complete code generated thus far */
|
||||
operator string() const
|
||||
{
|
||||
return buff_.str();
|
||||
}
|
||||
};
|
||||
|
||||
struct Node : Code
|
||||
{
|
||||
Node (size_t id)
|
||||
: Code{"N"+toString(id)}
|
||||
{ }
|
||||
|
||||
Node&&
|
||||
addAttrib (string def)
|
||||
{
|
||||
if (back() != ']')
|
||||
append ("[");
|
||||
else
|
||||
{
|
||||
resize (length()-2);
|
||||
append (", ");
|
||||
}
|
||||
append (def+" ]");
|
||||
return move(*this);
|
||||
}
|
||||
|
||||
Node&&
|
||||
label (size_t i)
|
||||
{
|
||||
return addAttrib ("label="+toString(i));
|
||||
}
|
||||
|
||||
Node&&
|
||||
style (Code const& code)
|
||||
{
|
||||
if (not isnil(code))
|
||||
addAttrib (code);
|
||||
return move(*this);
|
||||
}
|
||||
};
|
||||
|
||||
struct Scope : Code
|
||||
{
|
||||
Scope (size_t id)
|
||||
: Code{"{ /*"+toString(id)+"*/ }"}
|
||||
{ }
|
||||
|
||||
Scope&&
|
||||
add (Code const& code)
|
||||
{
|
||||
resize(length()-1);
|
||||
append (code+" }");
|
||||
return move(*this);
|
||||
}
|
||||
|
||||
Scope&&
|
||||
rank (string rankSetting)
|
||||
{
|
||||
return add(Code{"rank="+rankSetting});
|
||||
}
|
||||
};
|
||||
|
||||
inline Code
|
||||
connect (size_t src, size_t dest)
|
||||
{
|
||||
return Code{Node(src) +" -> "+ Node(dest)};
|
||||
}
|
||||
|
||||
template<class...COD>
|
||||
inline DotOut
|
||||
digraph (COD ...parts)
|
||||
{
|
||||
DotOut script;
|
||||
script.putLine (Code{"digraph {"});
|
||||
script.put (parts...);
|
||||
script.putLine (Code{"}"});
|
||||
return script;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -96425,6 +96425,15 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
|||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1700147510434" ID="ID_1659345454" MODIFIED="1700151160461" TEXT="Extrahieren und reorganisieren">
|
||||
<icon BUILTIN="pencil"/>
|
||||
<node COLOR="#338800" CREATED="1700147703792" ID="ID_1662535458" MODIFIED="1700151155878" TEXT="separater Header: lib/dot-gen.hpp">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700151162860" ID="ID_479859970" MODIFIED="1700151187644" TEXT="Graph-generierungs-Code in eigenen Operator extrahieren">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1700105473288" ID="ID_363744899" MODIFIED="1700105544295" TEXT="Operatoren">
|
||||
|
|
|
|||
Loading…
Reference in a new issue