Library: try out building a variant-model on top

* the implementation of this ''Sum Type'' got quite technical and complicated;
   thus better to be extracted as separate library component
 * use this as base for the `AltModel`
 * make a usage sketch, invoking only the model interactions required
This commit is contained in:
Fischlurch 2025-01-20 23:55:42 +01:00
parent 8c046ee2ea
commit d052edf91d
6 changed files with 949 additions and 204 deletions

566
src/lib/branch-case.hpp Normal file
View file

@ -0,0 +1,566 @@
/*
BRANCH-CASE.hpp - helpers for parsing textual specifications
Copyright (C)
2024, Hermann Vosseler <Ichthyostega@web.de>
  **Lumiera** is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version. See the file COPYING for further details.
*/
/** @file branch-case.hpp
** Convenience wrappers and definitions for parsing structured definitions.
** Whenever a specification syntax entails nested structures, extracting contents
** with regular expressions alone becomes tricky. Without much sophistication, a
** directly implemented simple recursive descent parser is often less brittle and
** easier to understand and maintain. With some helper abbreviations, notably
** a combinator scheme to work from building blocks, a hand-written solution
** can benefit from taking short-cuts, especially related to result bindings.
*/
#ifndef LIB_BRANCH_CASE_H
#define LIB_BRANCH_CASE_H
#include "lib/iter-adapter.hpp"
#include "lib/meta/function.hpp"
#include "lib/meta/trait.hpp"
#include "lib/regex.hpp"
#include <optional>
#include <utility>
#include <cstddef>
#include <tuple>
#include <array>
namespace util {
namespace parse {
using std::move;
using std::forward;
using std::optional;
using lib::meta::_Fun;
using lib::meta::has_Sig;
using lib::meta::NullType;
using std::decay_t;
using std::tuple;
using std::array;
using StrView = std::string_view;
template<typename...TYPES>
struct _MaxBufSiz;
template<>
struct _MaxBufSiz<>
{
static constexpr size_t siz = 0;
};
template<typename T, typename...TYPES>
struct _MaxBufSiz<T,TYPES...>
{
static constexpr size_t siz = std::max (sizeof(T)
,_MaxBufSiz<TYPES...>::siz);
};
template<typename...TYPES>
class BranchCase
{
public:
static constexpr auto TOP = sizeof...(TYPES) -1;
static constexpr auto SIZ = _MaxBufSiz<TYPES...>::siz;
template<size_t idx>
using SlotType = std::tuple_element_t<idx, tuple<TYPES...>>;
protected:
/** @internal default-created state is **invalid** */
BranchCase() = default;
size_t branch_{0};
alignas(int64_t)
std::byte buffer_[SIZ];
template<typename TX, typename...INITS>
TX&
emplace (INITS&&...inits)
{
return * new(&buffer_) TX(forward<INITS> (inits)...);
}
template<typename TX>
TX&
access ()
{
return * std::launder (reinterpret_cast<TX*> (&buffer_[0]));
}
/** apply generic functor to the currently selected branch */
template<size_t idx, class FUN>
auto
selectBranch (FUN&& fun)
{
if constexpr (0 < idx)
if (branch_ < idx)
return selectBranch<idx-1> (forward<FUN>(fun));
return fun (get<idx>());
}
public:
template<class FUN>
auto
apply (FUN&& fun)
{
return selectBranch<TOP> (forward<FUN> (fun));
}
~BranchCase()
{
apply ([this](auto& it)
{ using Elm = decay_t<decltype(it)>;
access<Elm>().~Elm();
});
}
template<typename...INITS>
BranchCase (size_t idx, INITS&& ...inits)
: branch_{idx}
{
apply ([&,this](auto& it)
{ using Elm = decay_t<decltype(it)>;
if constexpr (std::is_constructible_v<Elm,INITS...>)
this->emplace<Elm> (forward<INITS> (inits)...);
});
}
BranchCase (BranchCase const& o)
{
branch_ = o.branch_;
BranchCase& unConst = const_cast<BranchCase&> (o);
unConst.apply ([this](auto& it)
{ using Elm = decay_t<decltype(it)>;
this->emplace<Elm> (it);
});
}
BranchCase (BranchCase && ro)
{
branch_ = ro.branch_;
ro.apply ([this](auto& it)
{ using Elm = decay_t<decltype(it)>;
this->emplace<Elm> (move (it));
});
}
friend void
swap (BranchCase& o1, BranchCase o2)
{
using std::swap;
BranchCase tmp;
o1.apply ([&](auto& it)
{ using Elm = decay_t<decltype(it)>;
tmp.emplace<Elm> (move (o1.access<Elm>()));
});
swap (o1.branch_,o2.branch_);
o1.apply ([&](auto& it)
{ using Elm = decay_t<decltype(it)>;
o1.emplace<Elm> (move (o2.access<Elm>()));
});
o2.apply ([&](auto& it)
{ using Elm = decay_t<decltype(it)>;
o2.emplace<Elm> (move (tmp.access<Elm>()));
});
}
BranchCase&
operator= (BranchCase ref)
{
swap (*this, ref);
return *this;
}
template<typename...MORE>
auto
moveExtended()
{
using Extended = BranchCase<TYPES...,MORE...>;
Extended& upFaked = reinterpret_cast<Extended&> (*this);
return Extended (move (upFaked));
}
size_t
selected() const
{
return branch_;
}
template<size_t idx>
SlotType<idx>&
get()
{
return access<SlotType<idx>>();
}
};
#if false ///////////////////////////////////////////////////////////////////////////////TODO accommodate
/**
*/
template<class RES>
struct Eval
{
using Result = RES;
optional<RES> result;
size_t consumed{0};
};
template<class FUN>
struct Connex
: util::NonAssign
{
using PFun = FUN;
PFun parse;
using Result = typename _Fun<PFun>::Ret::Result;
Connex (FUN&& pFun)
: parse{move(pFun)}
{ }
};
auto
buildConnex(NullType)
{
return Connex{[](StrView) -> Eval<NullType>
{
return {NullType{}};
}};
}
using NulP = decltype(buildConnex (NullType()));
auto
buildConnex (regex rex)
{
return Connex{[regEx = move(rex)]
(StrView toParse) -> Eval<smatch>
{ // skip leading whitespace...
size_t pre = leadingWhitespace (toParse);
toParse = toParse.substr(pre);
auto result{matchAtStart (toParse,regEx)};
size_t consumed = result? pre+result->length() : 0;
return {move(result), consumed};
}};
}
using Term = decltype(buildConnex (std::declval<regex>()));
Term
buildConnex (string const& rexDef)
{
return buildConnex (regex{rexDef});
}
template<class FUN>
auto
buildConnex (Connex<FUN> const& anchor)
{
return Connex{anchor};
}
template<class FUN>
auto
buildConnex (Connex<FUN> && anchor)
{
return Connex{move(anchor)};
}
template<class CON, class BIND>
auto
adaptConnex (CON&& connex, BIND&& modelBinding)
{
using RX = typename CON::Result;
using Arg = lib::meta::_FunArg<BIND>;
static_assert (std::is_constructible_v<Arg,RX>,
"Model binding must accept preceding model result.");
using AdaptedRes = typename _Fun<BIND>::Ret;
return Connex{[origConnex = forward<CON>(connex)
,binding = forward<BIND>(modelBinding)
]
(StrView toParse) -> Eval<AdaptedRes>
{
auto eval = origConnex.parse (toParse);
if (eval.result)
return {binding (move (*eval.result))};
else
return {std::nullopt};
}};
}
/* ===== building structured models ===== */
/**
* Product Model : results from a conjunction of parsing clauses,
* which are to be accepted in sequence, one after the other.
*/
template<typename...RESULTS>
struct SeqModel
: tuple<RESULTS...>
{
static constexpr size_t SIZ = sizeof...(RESULTS);
using Seq = lib::meta::TySeq<RESULTS...>;
using Tup = std::tuple<RESULTS...>;
SeqModel() = default;
template<typename...XS, typename XX>
SeqModel (SeqModel<XS...>&& seq, XX&& extraElm)
: Tup{std::tuple_cat (seq.extractTuple()
,make_tuple (forward<XX> (extraElm)) )}
{ }
template<typename X1, typename X2>
SeqModel (X1&& res1, X2&& res2)
: Tup{move(res1), move(res2)}
{ }
Tup&& extractTuple() { return move(*this); }
};
/**
* Sum Model : results from a disjunction of parsing clauses,
* which are are tested and accepted as alternatives, one at least.
*/
template<typename...CASES>
struct AltModel
{
};
/** Special case Product Model to represent iterative sequence */
template<typename RES>
struct IterModel
{
};
/** Marker-Tag for the result from a sub-expression, not to be joined */
template<typename RES>
struct SubModel
{
};
/** Standard case : combinator of two model branches */
template<template<class...> class TAG, class R1, class R2 =void>
struct _Join
{
using Result = TAG<R1,R2>;
};
/** Generic case : extend a structured model by further branch */
template<template<class...> class TAG, class...RS, class R2>
struct _Join<TAG,TAG<RS...>,R2>
{
using Result = TAG<RS...,R2>;
};
/** Special Case : absorb sub-expression without flattening */
template<template<class...> class TAG, class R1, class R2>
struct _Join<TAG,SubModel<R1>,R2>
{
using Result = TAG<R1,R2>;
};
template<template<class...> class TAG, class R1, class R2>
struct _Join<TAG,R1, SubModel<R2>>
{
using Result = TAG<R1,R2>;
};
template<template<class...> class TAG, class R1, class R2>
struct _Join<TAG,SubModel<R1>,SubModel<R2>>
{
using Result = TAG<R1,R2>;
};
/** Special Case : absorb further similar elements into IterModel */
template<class RES>
struct _Join<IterModel, IterModel<RES>, RES>
{
using Result = IterModel<RES>;
};
/** accept sequence of two parse functions */
template<class C1, class C2>
auto
sequenceConnex (C1&& connex1, C2&& connex2)
{
using R1 = typename decay_t<C1>::Result;
using R2 = typename decay_t<C2>::Result;
using ProductResult = typename _Join<SeqModel, R1, R2>::Result;
using ProductEval = Eval<ProductResult>;
return Connex{[conL = forward<C1>(connex1)
,conR = forward<C2>(connex2)
]
(StrView toParse) -> ProductEval
{
auto eval1 = conL.parse (toParse);
if (eval1.result)
{
size_t posAfter1 = eval1.consumed;
StrView restInput = toParse.substr(posAfter1);
auto eval2 = conR.parse (restInput);
if (eval2.result)
{
uint consumedOverall = posAfter1 + eval2.consumed;
return ProductEval{ProductResult{move(*eval1.result)
,move(*eval2.result)}
,consumedOverall
};
}
}
return ProductEval{std::nullopt};
}};
}
template<class PAR>
class Syntax;
template<class CON>
class Parser
: public CON
{
using PFun = typename CON::PFun;
static_assert (_Fun<PFun>(), "Connex must define a parse-function");
public:
using Connex = CON;
using Result = typename CON::Result;
using Sigi = typename _Fun<PFun>::Sig;
//lib::test::TypeDebugger<Sigi> buggi;
//lib::test::TypeDebugger<Result> guggi;
static_assert (has_Sig<PFun, Eval<Result>(StrView)>()
,"Signature of the parse-function not suitable");
Eval<Result>
operator() (StrView toParse)
{
return CON::parse (toParse);
}
template<typename SPEC>
Parser (SPEC&& spec)
: CON{buildConnex (forward<SPEC> (spec))}
{ }
// template<class PAR>
// Parser (Syntax<PAR> const& anchor)
// : CON{anchor}
// { }
// template<class PAR>
// Parser (CON const& anchor)
// : CON{anchor}
// { }
};
Parser(NullType) -> Parser<NulP>;
Parser(regex &&) -> Parser<Term>;
Parser(regex const&) -> Parser<Term>;
Parser(string const&) -> Parser<Term>;
template<class FUN>
Parser(Connex<FUN> const&) -> Parser<Connex<FUN>>;
//
// template<class PAR>
// Parser(Syntax<PAR> const&) -> Parser<typename PAR::Connex>;
template<class PAR>
class Syntax
: public Eval<typename PAR::Result>
{
PAR parse_;
public:
using Connex = typename PAR::Connex;
using Result = typename PAR::Result;
bool success() const { return bool(Syntax::result); }
bool hasResult() const { return bool(Syntax::result); }
Result& getResult() { return * Syntax::result; }
Result&& extractResult(){ return move(getResult()); }
Syntax()
: parse_{NullType()}
{ }
explicit
Syntax (PAR&& parser)
: parse_{move (parser)}
{ }
explicit
operator bool() const
{
return success();
}
Syntax&&
parse (StrView toParse)
{
eval() = parse_(toParse);
return move(*this);
}
Connex const&
getConny() const
{
return parse_;
}
template<typename SPEC>
auto
seq (SPEC&& clauseDef)
{
return accept(
sequenceConnex (move(parse_)
,Parser{forward<SPEC> (clauseDef)}));
}
private:
Eval<Result>&
eval()
{
return *this;
}
};
template<typename SPEC>
auto
accept (SPEC&& clauseDef)
{
return Syntax{Parser{forward<SPEC> (clauseDef)}};
}
// template<class PAR>
// Parser(Syntax<PAR> const&) -> Parser<typename PAR::Connex>;
#endif /////////////////////////////////////////////////////////////////////////////////////TODO accommodate
}// namespace parse
//using parse::accept;
}// namespace util
namespace lib {
}// namespace lib
#endif/*LIB_BRANCH_CASE_H*/

View file

@ -27,7 +27,7 @@
#define LIB_PARSE_H
#include "lib/iter-adapter.hpp"
#include "lib/branch-case.hpp"
#include "lib/meta/function.hpp"
#include "lib/meta/trait.hpp"
#include "lib/regex.hpp"
@ -53,158 +53,6 @@ namespace util {
using StrView = std::string_view;
template<typename...TYPES>
struct _MaxBufSiz;
template<>
struct _MaxBufSiz<>
{
static constexpr size_t siz = 0;
};
template<typename T, typename...TYPES>
struct _MaxBufSiz<T,TYPES...>
{
static constexpr size_t siz = std::max (sizeof(T)
,_MaxBufSiz<TYPES...>::siz);
};
template<typename...TYPES>
class BranchCase
{
public:
static constexpr auto TOP = sizeof...(TYPES) -1;
static constexpr auto SIZ = _MaxBufSiz<TYPES...>::siz;
template<size_t idx>
using SlotType = std::tuple_element_t<idx, tuple<TYPES...>>;
protected:
size_t branch_{0};
alignas(int64_t)
std::byte buffer_[SIZ];
template<typename TX, typename...INITS>
TX&
emplace (INITS&&...inits)
{
return * new(&buffer_) TX(forward<INITS> (inits)...);
}
template<typename TX>
TX&
access ()
{
return * std::launder (reinterpret_cast<TX*> (&buffer_[0]));
}
/** apply generic functor to the currently selected branch */
template<size_t idx, class FUN>
auto
selectBranch (FUN&& fun)
{
if constexpr (0 < idx)
if (branch_ < idx)
return selectBranch<idx-1> (forward<FUN>(fun));
return fun (get<idx>());
}
BranchCase() = default;
public:
template<class FUN>
auto
apply (FUN&& fun)
{
return selectBranch<TOP> (forward<FUN> (fun));
}
~BranchCase()
{
apply ([this](auto& it)
{ using Elm = decay_t<decltype(it)>;
access<Elm>().~Elm();
});
}
template<typename...INITS>
BranchCase (size_t idx, INITS&& ...inits)
{
branch_ = idx;
apply ([&,this](auto& it)
{ using Elm = decay_t<decltype(it)>;
emplace<Elm> (forward<INITS> (inits)...);
});
}
BranchCase (BranchCase const& o)
{
branch_ = o.branch_;
BranchCase& unConst = const_cast<BranchCase&> (o);
unConst.apply ([this](auto& it)
{ using Elm = decay_t<decltype(it)>;
this->emplace<Elm> (it);
});
}
BranchCase (BranchCase && ro)
{
branch_ = ro.branch_;
ro.apply ([this](auto& it)
{ using Elm = decay_t<decltype(it)>;
this->emplace<Elm> (move (it));
});
}
friend void
swap (BranchCase& o1, BranchCase o2)
{
using std::swap;
BranchCase tmp;
o1.apply ([&](auto& it)
{ using Elm = decay_t<decltype(it)>;
tmp.emplace<Elm> (move (o1.access<Elm>()));
});
swap (o1.branch_,o2.branch_);
o1.apply ([&](auto& it)
{ using Elm = decay_t<decltype(it)>;
o1.emplace<Elm> (move (o2.access<Elm>()));
});
o2.apply ([&](auto& it)
{ using Elm = decay_t<decltype(it)>;
o2.emplace<Elm> (move (tmp.access<Elm>()));
});
}
BranchCase&
operator= (BranchCase ref)
{
swap (*this, ref);
return *this;
}
template<typename...MORE>
auto
moveExtended()
{
using Extended = BranchCase<TYPES...,MORE...>;
Extended& upFaked = reinterpret_cast<Extended&> (*this);
return Extended (move (upFaked));
}
size_t
selected() const
{
return branch_;
}
template<size_t idx>
SlotType<idx>&
get()
{
return access<SlotType<idx>>();
}
};
/**
*/
@ -334,8 +182,25 @@ namespace util {
*/
template<typename...CASES>
struct AltModel
: BranchCase<CASES...>
{
using _Model = BranchCase<CASES...>;
template<typename EX>
using Additionally = AltModel<CASES...,EX>;
template<typename INIT, typename =lib::meta::disable_if_self<AltModel,INIT>>
AltModel (INIT&& init)
: _Model{_Model::TOP, forward<INIT> (init)}
{ }
template<typename EX>
Additionally<EX>
addBranch()
{
Additionally<EX>& upFaked = reinterpret_cast<Additionally<EX>&> (*this);
return {move (upFaked)};
}
};

View file

@ -7,7 +7,12 @@ return: 0
END
TEST "CmdlineWrapper_test" CmdlineWrapper_test <<END
TEST "Package result alternatives" BranchCase_test <<END
return: 0
END
TEST "passing the commandline" CmdlineWrapper_test <<END
out-lit: wrapping cmdline:...
out-lit: -->
out-lit: wrapping cmdline:
@ -43,7 +48,7 @@ return: 0
END
TEST "CustomSharedPtr_test" CustomSharedPtr_test <<END
TEST "Adapted shared pointer" CustomSharedPtr_test <<END
return: 0
END

View file

@ -0,0 +1,251 @@
/*
BranchCase(Test) - verify parsing textual specifications
Copyright (C)
2024, Hermann Vosseler <Ichthyostega@web.de>
  **Lumiera** is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version. See the file COPYING for further details.
* *****************************************************************/
/** @file branch-case-test.cpp
** unit test \ref BranchCase_test
*/
#include "lib/test/run.hpp"
#include "lib/test/test-helper.hpp"
#include "lib/branch-case.hpp"
//#include "lib/iter-explorer.hpp"
//#include "lib/format-util.hpp"
#include "lib/meta/tuple-helper.hpp"
#include "lib/test/diagnostic-output.hpp"//////////////////TODO
//#include "lib/util.hpp"
//#include <vector>
//#include <memory>
namespace util {
namespace parse{
namespace test {
using lib::meta::is_Tuple;
using std::get;
// using util::join;
// using util::isnil;
// using std::vector;
// using std::shared_ptr;
// using std::make_shared;
// using LERR_(ITER_EXHAUST);
// using LERR_(INDEX_BOUNDS);
namespace { // test fixture
// const uint NUM_ELMS = 10;
// using Numz = vector<uint>;
} // (END)fixture
/************************************************************************//**
* @test verify helpers and shortcuts for simple recursive descent parsing
* of structured data and specifications.
*
* @see parse.hpp
* @see proc-node.cpp "usage example"
*/
class BranchCase_test : public Test
{
virtual void
run (Arg)
{
simpleBlah();
acceptAlternatives();
}
/** @test TODO just blah. */
void
simpleBlah ()
{
}
#if false ////////////////////////////////////////////////////////////////////////////TODO accommodate
/** @test define a terminal symbol to match by parse. */
void
acceptTerminal()
{
// set up a parser function to accept some token as terminal
auto parse = Parser{"hello (\\w+) world"};
string toParse{"hello vile world of power"};
auto eval = parse (toParse);
CHECK (eval.result);
auto res = *eval.result; // ◁——————————— the »result model« of a terminal parse is the RegExp-Matcher
CHECK (res.ready() and not res.empty());
CHECK (res.size() == "2"_expect );
CHECK (res.position() == "0"_expect );
CHECK (res.str() == "hello vile world"_expect );
CHECK (res[1] == "vile"_expect );
CHECK (res.suffix() == " of power"_expect );
auto syntax = Syntax{move (parse)}; // Build a syntax clause from the simple terminal symbol parser
CHECK (not syntax.hasResult());
syntax.parse (toParse);
CHECK (syntax.success()); // Syntax clause holds an implicit state from the last parse
CHECK (syntax.getResult()[1] == "vile"_expect);
// shorthand notation to start building a syntax
auto syntax2 = accept ("(\\w+) world");
CHECK (not syntax2.hasResult());
syntax2.parse (toParse);
CHECK (not syntax2.success());
string bye{"cruel world"};
syntax2.parse (bye);
CHECK (syntax2.success());
CHECK (syntax2.getResult()[1] == "cruel"_expect);
// going full circle: extract parser def from syntax
// using Conn = decltype(syntax2)::Connex;
// Conn conny{syntax2};
// auto parse2 = Parser{conny};
auto parse2 = Parser{syntax2.getConny()};
CHECK (eval.result->str(1) == "vile");
eval = parse2 (toParse);
CHECK (not eval.result);
eval = parse2 (bye);
CHECK (eval.result->str(1) == "cruel");
}
/** @test define a sequence of syntax structures to match by parse. */
void
acceptSequential()
{
// Demonstration: how sequence combinator works....
auto term1 = buildConnex ("hello");
auto term2 = buildConnex ("world");
auto parseSeq = [&](StrView toParse)
{
using R1 = decltype(term1)::Result;
using R2 = decltype(term2)::Result;
using ProductResult = std::tuple<R1,R2>;
using ProductEval = Eval<ProductResult>;
auto eval1 = term1.parse (toParse);
if (eval1.result)
{
uint end1 = eval1.consumed;
StrView restInput = toParse.substr(end1);
auto eval2 = term2.parse (restInput);
if (eval2.result)
{
uint consumedOverall = end1 + eval2.consumed;
return ProductEval{ProductResult{move(*eval1.result)
,move(*eval2.result)}
,consumedOverall
};
}
}
return ProductEval{std::nullopt};
};
string s1{"hello millions"};
string s2{"hello world"};
string s3{" hello world trade "};
auto e1 = parseSeq(s1);
CHECK (not e1.result); // Syntax 'hello'>>'world' does not accept "hello millions"
auto e2 = parseSeq(s2);
CHECK ( e2.result);
using SeqRes = std::decay_t<decltype(*e2.result)>; // Note: the result type depends on the actual syntax construction
CHECK (is_Tuple<SeqRes>()); // Result model from sequence is the tuple of terminal results
auto& [r1,r2] = *e2.result;
CHECK (r1.str() == "hello"_expect);
CHECK (r2.str() == "world"_expect);
CHECK (term2.parse(" world").result); // Note: leading whitespace skipped by the basic terminal parsers
CHECK (term2.parse("\n \t world ").result);
CHECK (not term2.parse(" old ").result);
// DSL parse clause builder: a sequence of terminals...
auto syntax = accept("hello").seq("world");
// Perform the same parse as demonstrated above....
CHECK (not syntax.hasResult());
syntax.parse(s1);
CHECK (not syntax.success());
syntax.parse(s2);
CHECK (syntax);
SeqRes seqModel = syntax.getResult();
CHECK (get<0>(seqModel).str() == "hello"_expect);
CHECK (get<1>(seqModel).str() == "world"_expect);
// can build extended clause from existing one
auto syntax2 = syntax.seq("trade");
CHECK (not syntax2.hasResult());
syntax2.parse(s2);
CHECK (not syntax2.success());
syntax2.parse(s3);
CHECK (syntax2.success());
auto seqModel2 = syntax2.getResult(); // Note: model of consecutive sequence is flattened into a single tuple
CHECK (get<0>(seqModel2).str() == "hello"_expect);
CHECK (get<1>(seqModel2).str() == "world"_expect);
CHECK (get<2>(seqModel2).str() == "trade"_expect);
}
#endif /////////////////////////////////////////////////////////////////////////////////////TODO accommodate
/** @test TODO define alternative syntax structures to match by parse. */
void
acceptAlternatives()
{
using Branch = BranchCase<char,ushort>;
SHOW_EXPR(sizeof(Branch));
Branch b1{1, 42};
SHOW_EXPR(b1.selected());
SHOW_EXPR(b1.SIZ);
SHOW_EXPR(b1.TOP);
SHOW_EXPR(b1.get<1>());
SHOW_EXPR(b1.get<0>());
Branch b2{0,'x'};
SHOW_EXPR(b2.selected());
SHOW_EXPR(b2.get<1>());
SHOW_EXPR(b2.get<0>());
Branch b3{b1};
SHOW_EXPR(b3.selected());
SHOW_EXPR(b3.get<1>());
SHOW_EXPR(b3.get<0>());
b3 = b2;
SHOW_EXPR(b3.selected());
SHOW_EXPR(b3.get<1>());
SHOW_EXPR(b3.get<0>());
auto bx = b1.moveExtended<string>();
SHOW_EXPR(sizeof(bx))
SHOW_EXPR(bx.SIZ);
SHOW_EXPR(bx.TOP);
SHOW_EXPR(bx.selected());
SHOW_EXPR(bx.get<1>());
SHOW_EXPR(bx.get<0>());
}
};
LAUNCHER (BranchCase_test, "unit common");
}}} // namespace util::parse::test

View file

@ -216,33 +216,32 @@ namespace test {
void
acceptAlternatives()
{
using Branch = BranchCase<char,ushort>;
SHOW_EXPR(sizeof(Branch));
Branch b1{1, 42};
SHOW_EXPR(b1.selected());
SHOW_EXPR(b1.SIZ);
SHOW_EXPR(b1.TOP);
SHOW_EXPR(b1.get<1>());
SHOW_EXPR(b1.get<0>());
Branch b2{0,'x'};
SHOW_EXPR(b2.selected());
SHOW_EXPR(b2.get<1>());
SHOW_EXPR(b2.get<0>());
Branch b3{b1};
SHOW_EXPR(b3.selected());
SHOW_EXPR(b3.get<1>());
SHOW_EXPR(b3.get<0>());
b3 = b2;
SHOW_EXPR(b3.selected());
SHOW_EXPR(b3.get<1>());
SHOW_EXPR(b3.get<0>());
auto bx = b1.moveExtended<string>();
SHOW_EXPR(sizeof(bx))
SHOW_EXPR(bx.SIZ);
SHOW_EXPR(bx.TOP);
SHOW_EXPR(bx.selected());
SHOW_EXPR(bx.get<1>());
SHOW_EXPR(bx.get<0>());
using R1 = char;
using R2 = string;
using R3 = double;
using A1 = AltModel<R1>;
string s{"second"};
using A2 = A1::Additionally<R2>;
A2 model2{s};
SHOW_EXPR(sizeof(A2));
SHOW_EXPR(model2.SIZ);
SHOW_EXPR(model2.TOP);
SHOW_EXPR(model2.selected())
SHOW_EXPR(model2.get<1>())
using A3 = A2::Additionally<R3>;
A3 model3{model2.addBranch<R3>()};
SHOW_TYPE(A3)
SHOW_EXPR(sizeof(A3));
SHOW_EXPR(model3.SIZ);
SHOW_EXPR(model3.TOP);
SHOW_EXPR(model3.selected())
SHOW_EXPR(model3.get<1>())
auto res = move(model3);
SHOW_TYPE(decltype(res))
SHOW_EXPR(sizeof(res))
SHOW_EXPR(res.selected())
SHOW_EXPR(res.get<1>())
}
};

View file

@ -55960,19 +55960,21 @@
<node CREATED="1737238022259" ID="ID_508598643" MODIFIED="1737238026071" TEXT="up-Copy">
<node CREATED="1737238037257" ID="ID_1788967087" MODIFIED="1737238050739" TEXT="gegeben: AltModel&lt;N&gt;"/>
<node CREATED="1737238051966" ID="ID_384770143" MODIFIED="1737238076015" TEXT="zu bauen: AltModel&lt;N+1&gt; mit kopierten Daten und gleichem Selektor"/>
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1737238094329" ID="ID_1160578555" MODIFIED="1737238106552" TEXT="braucht Trampolin f&#xfc;r konkrete Kopie">
<node COLOR="#5b280f" CREATED="1737238094329" FOLDED="true" ID="ID_1160578555" MODIFIED="1737417012015" TEXT="braucht Trampolin f&#xfc;r konkrete Kopie">
<icon BUILTIN="messagebox_warning"/>
<icon BUILTIN="button_cancel"/>
<node CREATED="1737238147756" ID="ID_1597388632" MODIFIED="1737238159884" TEXT="statisches Array von Funktionspointern"/>
<node CREATED="1737238206786" ID="ID_784811" MODIFIED="1737238220532" TEXT="generische Signatur void(void*,void*)"/>
<node BACKGROUND_COLOR="#fafe99" COLOR="#fa002a" CREATED="1737238386277" ID="ID_499140874" MODIFIED="1737238392082" TEXT="ineffizient">
<node BACKGROUND_COLOR="#dfd986" COLOR="#ad014c" CREATED="1737238386277" ID="ID_499140874" MODIFIED="1737416856790" TEXT="ineffizient">
<icon BUILTIN="broken-line"/>
<node CREATED="1737238394241" ID="ID_1786923431" MODIFIED="1737238413594" TEXT="entweder haben wir N Teil-Arrays (Speicherverschwendung)"/>
<node CREATED="1737238414398" ID="ID_1897860224" MODIFIED="1737238431951" TEXT="oder eine verkettete Liste von Closures (Laufzeitverschwendung)"/>
<node CREATED="1737238441426" ID="ID_896531017" MODIFIED="1737238466027" TEXT="und im schlimmsten Fall auch noch N consecutive copies"/>
</node>
</node>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1737238578224" ID="ID_524632421" MODIFIED="1737238587563" TEXT="zu optimieren">
<node COLOR="#5b280f" CREATED="1737238578224" FOLDED="true" ID="ID_524632421" MODIFIED="1737417010319" TEXT="zu optimieren">
<icon BUILTIN="yes"/>
<icon BUILTIN="button_cancel"/>
<node CREATED="1737238589671" ID="ID_1560551329" MODIFIED="1737238601745" TEXT="Idee: Model-Binding in zwei Schritten">
<icon BUILTIN="idea"/>
<node CREATED="1737238606619" ID="ID_1441095840" MODIFIED="1737238917781" TEXT="Teilergebnis + Selektor-Nr durchreichen">
@ -55990,7 +55992,7 @@
</node>
</node>
</node>
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1737242122676" ID="ID_486125380" MODIFIED="1737242129558" TEXT="es ist nicht so einfach">
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1737242122676" ID="ID_486125380" MODIFIED="1737416836436" TEXT="es ist nicht so einfach">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1737242181419" ID="ID_699910063" MODIFIED="1737242197252" TEXT="wir geben nun mal &#xfc;ber eine Kette verschachtelter Funktoren zur&#xfc;ck"/>
<node CREATED="1737242567334" ID="ID_1304062723" MODIFIED="1737242586617" TEXT="sobald der erzeugende Scope verlassen wird, ist nur noch ein Summen-Typ bekannt"/>
@ -56024,14 +56026,15 @@
</html></richcontent>
</node>
</node>
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1737244421643" ID="ID_1284583110" MODIFIED="1737245500026" TEXT="intermedi&#xe4;rer Ergebnis-Typ: BranchResultClosure">
<node COLOR="#5b280f" CREATED="1737244421643" ID="ID_1284583110" MODIFIED="1737416868792" TEXT="intermedi&#xe4;rer Ergebnis-Typ: BranchResultClosure">
<icon BUILTIN="help"/>
<icon BUILTIN="button_cancel"/>
<node CREATED="1737245502249" ID="ID_1174923923" MODIFIED="1737245508829" TEXT="noch nicht klar ob notwendig">
<node CREATED="1737308551745" ID="ID_1909384704" MODIFIED="1737308569043" TEXT="denn Closure &#x2259; dynamischer Dispatch"/>
<node CREATED="1737308586765" ID="ID_1360651233" MODIFIED="1737308606555" TEXT="die ganze sonstiger &#xbb;Parser-Code-Insel&#xab; ist rein-statitsch typisiert"/>
<node CREATED="1737308671866" ID="ID_1034537096" MODIFIED="1737308702736" TEXT="Zahl der Branches klein &#x27f9; statische Impl im Vorteil"/>
</node>
<node CREATED="1737245511448" ID="ID_396620339" MODIFIED="1737245792925" TEXT="Alternativ: R&#xfc;ck&#xfc;bersetzung Selector &#x27fc; Typ-Kontext durch rekursive Funktionen">
<node CREATED="1737245511448" ID="ID_396620339" MODIFIED="1737416947927" TEXT="Alternativ: R&#xfc;ck&#xfc;bersetzung Selector &#x27fc; Typ-Kontext durch rekursive Funktionen">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
@ -56040,21 +56043,29 @@
</p>
</body>
</html></richcontent>
<arrowlink COLOR="#5c6ba3" DESTINATION="ID_724398426" ENDARROW="Default" ENDINCLINATION="134;0;" ID="Arrow_ID_961310703" STARTARROW="None" STARTINCLINATION="-244;9;"/>
</node>
</node>
</node>
</node>
<node COLOR="#435e98" CREATED="1737416878486" ID="ID_724398426" MODIFIED="1737417001411" TEXT="versuche Zugang zum aktiven Zweig via rekursive Funktion">
<arrowlink COLOR="#3dad9b" DESTINATION="ID_1738176168" ENDARROW="Default" ENDINCLINATION="-15;-187;" ID="Arrow_ID_387275034" STARTARROW="None" STARTINCLINATION="-287;14;"/>
<linktarget COLOR="#5c6ba3" DESTINATION="ID_724398426" ENDARROW="Default" ENDINCLINATION="134;0;" ID="Arrow_ID_961310703" SOURCE="ID_396620339" STARTARROW="None" STARTINCLINATION="-244;9;"/>
<icon BUILTIN="yes"/>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1737245801576" ID="ID_1738176168" MODIFIED="1737400259658" TEXT="Skizze Datentyp">
<icon BUILTIN="pencil"/>
<node CREATED="1737308328478" ID="ID_993665414" MODIFIED="1737308352116" TEXT="erst mal einen uninitialised byte-Puffer in Basisklasse"/>
</node>
<node COLOR="#338800" CREATED="1737245801576" ID="ID_1738176168" MODIFIED="1737416989050" TEXT="Skizze Datentyp">
<linktarget COLOR="#3dad9b" DESTINATION="ID_1738176168" ENDARROW="Default" ENDINCLINATION="-15;-187;" ID="Arrow_ID_387275034" SOURCE="ID_724398426" STARTARROW="None" STARTINCLINATION="-287;14;"/>
<icon BUILTIN="button_ok"/>
<node CREATED="1737308328478" ID="ID_993665414" MODIFIED="1737358352116" TEXT="erst mal einen uninitialised byte-Puffer in Basisklasse"/>
<node CREATED="1737308370978" ID="ID_1202779435" MODIFIED="1737308383699" TEXT="typisierter Summentyp dar&#xfc;bergelegt"/>
<node CREATED="1737308384535" ID="ID_1448196900" MODIFIED="1737308394626" TEXT="Anordnung der Typ-Parameter: R&#xfc;ckw&#xe4;rts gehend">
<node CREATED="1737308397038" ID="ID_977244135" MODIFIED="1737308410928" TEXT="also der letzte / h&#xf6;chste Zweig zuerst"/>
<node CREATED="1737308411588" ID="ID_1539400054" MODIFIED="1737308423294" TEXT="damit rekursiv-delegierende Implementierung m&#xf6;glich"/>
</node>
<node CREATED="1737309034680" ID="ID_1283731567" MODIFIED="1737309052170" TEXT="Code-Struktur &#xfc;berlegen">
<node COLOR="#338800" CREATED="1737309034680" ID="ID_1283731567" MODIFIED="1737416599812" TEXT="Code-Struktur &#xfc;berlegen">
<icon BUILTIN="button_ok"/>
<node CREATED="1737309053966" ID="ID_1359722232" MODIFIED="1737309190105" TEXT="Destuktor aufrufen ?">
<node BACKGROUND_COLOR="#fefc4e" COLOR="#351d75" CREATED="1737309093248" ID="ID_321904108" MODIFIED="1737309152195" TEXT="&#xd83e;&#xdc46; Invariante: stets ein Zweig belegt">
<font BOLD="true" NAME="SansSerif" SIZE="12"/>
@ -56065,7 +56076,7 @@
<node CREATED="1737309245044" ID="ID_1229816718" MODIFIED="1737309255638" TEXT="er m&#xfc;&#xdf;te n&#xe4;mlich den Typ im Buffer kennen"/>
<node CREATED="1737309258330" ID="ID_414435446" MODIFIED="1737309277292" TEXT="sonst ist es mehr wie eine Member-Komponente"/>
</node>
<node CREATED="1737309306808" ID="ID_1247810717" MODIFIED="1737309311710" TEXT="zwei Alternativen...">
<node COLOR="#435e98" CREATED="1737309306808" FOLDED="true" ID="ID_1247810717" MODIFIED="1737416659077" TEXT="zwei Alternativen...">
<node CREATED="1737309312755" ID="ID_1765693019" MODIFIED="1737323819640" TEXT="inkrementeller Dekorator">
<icon BUILTIN="full-1"/>
<node CREATED="1737309344095" ID="ID_1218238487" MODIFIED="1737309351610" TEXT="Buffer ist tats&#xe4;chlich Basis"/>
@ -56086,8 +56097,8 @@
<icon BUILTIN="full-2"/>
<node CREATED="1737309866960" ID="ID_1933240925" MODIFIED="1737309878323" TEXT="es gibt nur ein Objekt, das den Buffer direkt (private) h&#xe4;lt"/>
<node CREATED="1737309879188" ID="ID_1269779979" MODIFIED="1737309905040" TEXT="f&#xfc;r jeden Aufruf wird rekursiv eine Typ-Projektion dar&#xfc;bergelegt f&#xfc;r die Zugriffe"/>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1737309983889" ID="ID_217989124" MODIFIED="1737309993912" TEXT="Machbarkeit nicht klar &#x27f9; Prototyping">
<icon BUILTIN="pencil"/>
<node COLOR="#435e98" CREATED="1737309983889" ID="ID_217989124" MODIFIED="1737416577611" TEXT="Machbarkeit nicht klar &#x27f9; Prototyping">
<icon BUILTIN="yes"/>
<node CREATED="1737309995672" ID="ID_317094642" MODIFIED="1737310007874" TEXT="brauche rekursiv getypte Hilfsfunktionen">
<node CREATED="1737310409584" ID="ID_1587656830" MODIFIED="1737310412828" TEXT="emplace"/>
<node CREATED="1737310413472" ID="ID_190878994" MODIFIED="1737310416083" TEXT="destroy"/>
@ -56137,19 +56148,20 @@
</html></richcontent>
<icon BUILTIN="forward"/>
</node>
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1737317456624" ID="ID_846126551" MODIFIED="1737317483349" TEXT="grunds&#xe4;tzliches Problem: Variant-Returntype nicht m&#xf6;glich">
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1737317456624" ID="ID_846126551" MODIFIED="1737416545012" TEXT="grunds&#xe4;tzliches Problem: Variant-Returntype nicht m&#xf6;glich">
<icon BUILTIN="messagebox_warning"/>
<node COLOR="#5b280f" CREATED="1737317489124" ID="ID_233312745" MODIFIED="1737317528208" TEXT="Funktion kann keinen vom Parameter abh&#xe4;ngigen R&#xfc;ckgabetyp haben">
<icon BUILTIN="closed"/>
</node>
<node BACKGROUND_COLOR="#fafe99" COLOR="#fa002a" CREATED="1737317639568" ID="ID_526309958" MODIFIED="1737317717888" TEXT="diese Einschr&#xe4;nkung betrifft jedes Konstrukt f&#xfc;r einen Summen-Typ (in C++)">
<node BACKGROUND_COLOR="#e6ce96" COLOR="#fa002a" CREATED="1737317639568" ID="ID_526309958" MODIFIED="1737416557265" TEXT="diese Einschr&#xe4;nkung betrifft jedes Konstrukt f&#xfc;r einen Summen-Typ (in C++)">
<arrowlink COLOR="#d2005b" DESTINATION="ID_1314826159" ENDARROW="Default" ENDINCLINATION="58;-95;" ID="Arrow_ID_1280018433" STARTARROW="None" STARTINCLINATION="-542;20;"/>
<icon BUILTIN="clanbomber"/>
</node>
</node>
</node>
</node>
<node CREATED="1737323796942" ID="ID_1671873045" MODIFIED="1737323808699" TEXT="Diskussion">
</node>
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1737323796942" ID="ID_1671873045" MODIFIED="1737416667635" TEXT="Diskussion">
<icon BUILTIN="yes"/>
<node CREATED="1737323821990" ID="ID_1562513052" MODIFIED="1737323951827" TEXT="die Variante-2 erschien mir zun&#xe4;chst attraktiver">
<richcontent TYPE="NOTE"><html>
@ -56190,8 +56202,7 @@
...weil praktisch alle Kern-Methoden nun in zweifacher Ausfertigung gecodet werden m&#252;ssen: einmal rekursiv, und einmal f&#252;r den Abschlu&#223;
</p>
</body>
</html>
</richcontent>
</html></richcontent>
</node>
<node CREATED="1737392934472" ID="ID_642107000" MODIFIED="1737393084265" TEXT="zudem erzwingt Variante-1 eine umgekehrte Ordnung der Typ-Parameter">
<richcontent TYPE="NOTE"><html>
@ -56207,8 +56218,7 @@
<icon BUILTIN="yes"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1737317603366" ID="ID_1314826159" MODIFIED="1737317708664" TEXT="grunds&#xe4;tzliche Einschr&#xe4;nkung: Zugriff erfordert statisch bekannten Typ">
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1737317603366" FOLDED="true" ID="ID_1314826159" MODIFIED="1737416679265" TEXT="grunds&#xe4;tzliche Einschr&#xe4;nkung: Zugriff erfordert statisch bekannten Typ">
<linktarget COLOR="#d2005b" DESTINATION="ID_1314826159" ENDARROW="Default" ENDINCLINATION="58;-95;" ID="Arrow_ID_1280018433" SOURCE="ID_526309958" STARTARROW="None" STARTINCLINATION="-542;20;"/>
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1737317726468" ID="ID_1400201299" MODIFIED="1737317872607" TEXT="jeder &#xbb;Varant&#xab;-Type (und auch Union) unterliegt dieser Einschr&#xe4;nkung"/>
@ -56341,9 +56351,28 @@
</node>
</node>
</node>
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1737416683769" ID="ID_529397551" MODIFIED="1737416793839" STYLE="fork">
<richcontent TYPE="NODE"><html>
<head/>
<body>
<p>
<b>Fazit</b>: implementierbar mit generischem &#955;-Visitor
</p>
</body>
</html>
</richcontent>
<font NAME="SansSerif" SIZE="12"/>
<icon BUILTIN="forward"/>
<node COLOR="#78442f" CREATED="1737416738497" ID="ID_1423783090" MODIFIED="1737416781408" TEXT="nicht sch&#xf6;n">
<icon BUILTIN="smiley-oh"/>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1737395464420" ID="ID_1924549712" MODIFIED="1737400254287" STYLE="fork" TEXT="Konstruktor und Zuweisung">
<icon BUILTIN="pencil"/>
<node COLOR="#78442f" CREATED="1737416754887" ID="ID_1690525518" MODIFIED="1737416781408" TEXT="mehr so grade noch die Kurve gekratzt">
<icon BUILTIN="smiley-oh"/>
</node>
</node>
</node>
<node COLOR="#338800" CREATED="1737395464420" ID="ID_1924549712" MODIFIED="1737416078316" TEXT="Konstruktor und Zuweisung">
<icon BUILTIN="button_ok"/>
<node COLOR="#338800" CREATED="1737395472148" ID="ID_1934503205" MODIFIED="1737400253142" TEXT="zun&#xe4;chst einmal: Konstruktor immer auf den TOP-Slot">
<icon BUILTIN="button_ok"/>
</node>
@ -56362,10 +56391,40 @@
<node COLOR="#338800" CREATED="1737400227088" ID="ID_389977912" MODIFIED="1737400253142" TEXT="Extension in erweiterten Branch">
<icon BUILTIN="button_ok"/>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1737416084226" ID="ID_485111355" MODIFIED="1737416132543" TEXT="mu&#xdf; Lambda f&#xfc;r alle Zweige compilierbar machen">
<icon BUILTIN="broken-line"/>
<node CREATED="1737416138738" ID="ID_591886707" MODIFIED="1737416157124" TEXT="&#xe4;rgerlich &#x2014; stehe mit dem R&#xfc;cken zur Wand hier"/>
<node CREATED="1737416487683" ID="ID_136717181" MODIFIED="1737416503156" TEXT="das Lambda wird f&#xfc;r alle Typen instantiiert"/>
<node CREATED="1737416505524" ID="ID_456255326" MODIFIED="1737416522220" TEXT="abdichten mit std::is_constructible">
<icon BUILTIN="idea"/>
</node>
</node>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1737417020140" ID="ID_1230396959" MODIFIED="1737417037546" TEXT="sehr komplex &#x27f9; in Hilfskomponente auslagern">
<icon BUILTIN="pencil"/>
<node CREATED="1737417038777" ID="ID_158901588" MODIFIED="1737417051363" TEXT="lib/branch-case.hpp"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1737417051936" ID="ID_1310118315" MODIFIED="1737417056450" TEXT="BranchCase_test">
<icon BUILTIN="flag-yellow"/>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1737417060920" ID="ID_644848240" MODIFIED="1737417078580" TEXT="auf dieser Basis das AltModel definieren">
<icon BUILTIN="pencil"/>
<node COLOR="#338800" CREATED="1737417079594" ID="ID_883955221" MODIFIED="1737417091878" TEXT="erst mal die Einzelschritte durchspielen">
<icon BUILTIN="button_ok"/>
<node COLOR="#435e98" CREATED="1737417093057" ID="ID_1036480281" MODIFIED="1737417121178" TEXT="Typ konstruieren"/>
<node COLOR="#435e98" CREATED="1737417099856" ID="ID_820603798" MODIFIED="1737417121178" TEXT="einen Branch belegen"/>
<node COLOR="#435e98" CREATED="1737417104216" ID="ID_188551502" MODIFIED="1737417121178" TEXT="danach noch ein up-Copy"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1737417129349" ID="ID_1317057698" MODIFIED="1737417137972" TEXT="Struktur f&#xfc;r den Kombinator darstellen">
<icon BUILTIN="flag-yellow"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1737417144217" ID="ID_375516701" MODIFIED="1737417153090" TEXT="in einen Connex-Builder verpacken">
<icon BUILTIN="flag-yellow"/>
</node>
</node>
</node>
<node CREATED="1737048820482" ID="ID_235554745" MODIFIED="1737048832524" TEXT="generisches Model-Binding"/>
</node>
</node>