Library: test and documentation for the new variant-helper

So this turned out to be much more challenging than expected,
due to the fact that, with this design, typing information is
only available at compile-time. The key trick was to use a
''double-dispatch'' based on a generic lambda. In the end,
this could be rounded out to be self-contained library helper,
which is even fully copyable and assignable and properly
invokes all payload constructors and destructors.

The flip side is that such a design is obviously very flexible
and direct regarding the parser model-bindings, and it should
be fairly well optimisable, since the structure is entirely
static and without any virtual dispatch.

Proper handling of payload lifecycle was verified using
a tracking test object with checksum.
This commit is contained in:
Fischlurch 2025-01-21 03:53:29 +01:00
parent d052edf91d
commit 4f676f7213
6 changed files with 510 additions and 779 deletions

View file

@ -1,5 +1,5 @@
/*
BRANCH-CASE.hpp - helpers for parsing textual specifications
BRANCH-CASE.hpp - variant-like data type to capture different result types
Copyright (C)
2024, Hermann Vosseler <Ichthyostega@web.de>
@ -13,13 +13,60 @@
/** @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.
** A _Sum Type_ (variant) to capture values from a branched evaluation.
** While a _Product Type_ (tuple) holds a combination of individually typed values,
** a _Sum Type_ can hold any of these types, but only one at a time. Such a structure
** is needed when capturing results from an opaque (function-like) evaluation, which
** may yield different and incompatible result types, depending on circumstances.
** For illustration, this might be a _success_ and a _failure_ branch, but it might
** also be the data model from parsing a syntax with alternative branches, where
** each branch is bound to generate a different result object.
**
** ## Technicalities
** While the basic concept is simple, a C++ implementation of this scheme poses some
** challenges, due to the fact that precise type information is only given at compile
** time. For the implementation presented here, an additional requirement was not to
** use any virtual (run-time) dispatch and also not to rely on heap storage. So the
** implementation must embody all payload data into a buffer within the BranchCase
** object, while storing an additional selector-mark to identify the number of the
** actual case represented by this instance.
**
** The type is parametrised similar to a tuple, i.e. with a variadic type sequence.
** Each position in this sequence corresponds to one branch of the result system.
** The selected branch must be identified at instance creation, while also providing
** a proper initialiser for the branch's value. This design has several ramifications:
** - the object can not be default created, because an _empty_ state would not be valid
** - since type parameters can be arbitrary, we can not rely on _covariance_ for a
** return type; this implies that the embodied branch data value can only be
** accessed and retrieved when the invoker knows the branch-number at compile time.
** Usually this is not a serious limitation though, since code using such an evaluation
** must employ additional contextual knowledge anyway in order to draw any tangible
** conclusions. So the receiving code typically will have a branching and matching
** structure, based on probing the branch-selector value. Another situation might be
** that the data types from several branches are actually compatible, while the
** evaluation as such is written in a totally generic fashion and can thus not
** exploit such knowledge. This is typically the case when parsing a syntax;
** each branch might provide a regular expression match, but the receiving
** logic of course must know how to interpret the matching capture groups.
**
** In any case, every possible branch for access must be somehow instantiated at
** compile time, because this is the only way to use the type information. This
** observation leads to a further access-scheme, based on a _visitor-functor._
** Typically this will be a generic (templated) lambda function, which must be able
** to really handle all the possible data types; a recursive evaluation is generated
** at compile time, which checks all possible branch-numbers and invokes only that
** branch holding the value applicable at runtime. This scheme is used as foundation
** to implement most of the internal functionality, notably the constructors,
** copy-constructors and the swap function, thereby turning BranchCase into
** a fully copyable, movable and assignable type (limited by the capabilities
** of the payload types of course).
** @warning This is a low-level implementation facility. The implementation itself
** can not check any type safety at runtime and will thus access the payload buffer
** blindly as instructed. When used within a proper framework however, full
** type safety can be achieved, based on the fact that any instance is always
** tied to one valid branch provided to the compiler.
** @see BranchCase_test
** @see parser.hpp "usage example"
*/
@ -27,66 +74,72 @@
#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 "lib/meta/util.hpp"
#include <optional>
#include <utility>
#include <cstddef>
#include <tuple>
#include <array>
namespace util {
namespace parse {
namespace lib {
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;
namespace {// Metaprogramming helper
template<typename...TYPES>
struct _MaxBufSiz;
struct _MaxBuf;
template<>
struct _MaxBufSiz<>
struct _MaxBuf<>
{
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);
static constexpr size_t align = 0;
};
template<typename T, typename...TYPES>
struct _MaxBuf<T,TYPES...>
{
static constexpr size_t siz = std::max (sizeof(T),_MaxBuf<TYPES...>::siz);
static constexpr size_t align = std::max (alignof(T),_MaxBuf<TYPES...>::align);
};
}//(End)Meta-helper
/*********************************************************************//**
* A _Sum Type_ to hold alternative results from a branched evaluation.
* @tparam TYPES sequence of all the types corresponding to all branches
* @remark an instance is locked into a specific branch, as designated
* by the index in the type sequence. The payload object is
* placed inline, into an opaque buffer. You need to know the
* branch-number in order to re-access the (typed) content.
*/
template<typename...TYPES>
class BranchCase
{
public:
static constexpr auto TOP = sizeof...(TYPES) -1;
static constexpr auto SIZ = _MaxBufSiz<TYPES...>::siz;
static constexpr auto SIZ = _MaxBuf<TYPES...>::siz;
template<size_t idx>
using SlotType = std::tuple_element_t<idx, tuple<TYPES...>>;
protected:
/** @internal default-created state is **invalid** */
BranchCase() = default;
BranchCase() = default; ///< @internal default-created state is **invalid**
/** selector field to designate the chosen branch */
size_t branch_{0};
alignas(int64_t)
/** opaque inline storage buffer
* with suitable size and alignment */
alignas(_MaxBuf<TYPES...>::align)
std::byte buffer_[SIZ];
template<typename TX, typename...INITS>
TX&
emplace (INITS&&...inits)
@ -112,27 +165,35 @@ namespace util {
return fun (get<idx>());
}
public:
/**
* Accept a _visitor-functor_ (double dispatch).
* @note the functor or lambda must be generic and indeed
* able to handle every possible branch type.
* @warning can only return single type for all branches.
*/
template<class FUN>
auto
apply (FUN&& fun)
accept (FUN&& visitor)
{
return selectBranch<TOP> (forward<FUN> (fun));
return selectBranch<TOP> (forward<FUN> (visitor));
}
~BranchCase()
{
apply ([this](auto& it)
accept ([this](auto& it)
{ using Elm = decay_t<decltype(it)>;
access<Elm>().~Elm();
});
}
/** Standard constructor: select branch and provide initialiser */
template<typename...INITS>
BranchCase (size_t idx, INITS&& ...inits)
: branch_{idx}
{
apply ([&,this](auto& it)
accept ([&,this](auto& it)
{ using Elm = decay_t<decltype(it)>;
if constexpr (std::is_constructible_v<Elm,INITS...>)
this->emplace<Elm> (forward<INITS> (inits)...);
@ -143,7 +204,7 @@ namespace util {
{
branch_ = o.branch_;
BranchCase& unConst = const_cast<BranchCase&> (o);
unConst.apply ([this](auto& it)
unConst.accept ([this](auto& it)
{ using Elm = decay_t<decltype(it)>;
this->emplace<Elm> (it);
});
@ -152,27 +213,28 @@ namespace util {
BranchCase (BranchCase && ro)
{
branch_ = ro.branch_;
ro.apply ([this](auto& it)
ro.accept ([this](auto& it)
{ using Elm = decay_t<decltype(it)>;
this->emplace<Elm> (move (it));
});
}
friend void
swap (BranchCase& o1, BranchCase o2)
swap (BranchCase& o1, BranchCase& o2)
{
using std::swap;
BranchCase tmp;
o1.apply ([&](auto& it)
tmp.branch_ = o1.branch_;
o1.accept ([&](auto& it)
{ using Elm = decay_t<decltype(it)>;
tmp.emplace<Elm> (move (o1.access<Elm>()));
});
swap (o1.branch_,o2.branch_);
o1.apply ([&](auto& it)
o1.accept ([&](auto& it)
{ using Elm = decay_t<decltype(it)>;
o1.emplace<Elm> (move (o2.access<Elm>()));
});
o2.apply ([&](auto& it)
o2.accept ([&](auto& it)
{ using Elm = decay_t<decltype(it)>;
o2.emplace<Elm> (move (tmp.access<Elm>()));
});
@ -185,15 +247,6 @@ namespace util {
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
@ -201,6 +254,9 @@ namespace util {
return branch_;
}
/** re-access the value, using compile-time slot-index param.
* @warning must use the correct slot-idx (unchecked!)
*/
template<size_t idx>
SlotType<idx>&
get()
@ -209,358 +265,5 @@ namespace util {
}
};
#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

@ -182,18 +182,18 @@ namespace util {
*/
template<typename...CASES>
struct AltModel
: BranchCase<CASES...>
: lib::BranchCase<CASES...>
{
using _Model = BranchCase<CASES...>;
template<typename EX>
using Additionally = AltModel<CASES...,EX>;
using _Model = lib::BranchCase<CASES...>;
template<typename INIT, typename =lib::meta::disable_if_self<AltModel,INIT>>
AltModel (INIT&& init)
: _Model{_Model::TOP, forward<INIT> (init)}
{ }
template<typename EX>
using Additionally = AltModel<CASES...,EX>;
template<typename EX>
Additionally<EX>
addBranch()

View file

@ -52,10 +52,23 @@ namespace test{
static long _local_checksum;
static bool _throw_in_ctor;
void
init()
{
checksum() += val_;
if (_throw_in_ctor)
throw val_;
}
public:
static constexpr int DEFUNCT = std::numeric_limits<int>::min();
static constexpr int DEAD = std::numeric_limits<int>::max();
virtual ~Dummy() ///< can act as interface
{
if (val_ != DEFUNCT)
checksum() -= val_;
val_ = DEAD;
}
Dummy ()
@ -66,20 +79,27 @@ namespace test{
: val_(v)
{ init(); }
friend void
swap (Dummy& dum1, Dummy& dum2) ///< checksum neutral
{
std::swap (dum1.val_, dum2.val_);
}
Dummy (Dummy const& o)
: Dummy{o.val_}
{ }
Dummy (Dummy && oDummy) noexcept
: Dummy(0)
{
swap (*this, oDummy);
oDummy.val_ = DEFUNCT;
}
Dummy&
operator= (Dummy && oDummy)
{
if (&oDummy != this)
operator= (Dummy oDummy) ///< accepts both lvalues and rvalues
{
swap (*this, oDummy);
oDummy.setVal(0);
}
return *this;
}
@ -104,12 +124,6 @@ namespace test{
val_ = newVal;
}
friend void
swap (Dummy& dum1, Dummy& dum2) ///< checksum neutral
{
std::swap (dum1.val_, dum2.val_);
}
static long&
checksum()
{
@ -121,16 +135,6 @@ namespace test{
{
_throw_in_ctor = indeed;
}
private:
void
init()
{
checksum() += val_;
if (_throw_in_ctor)
throw val_;
}
};

View file

@ -18,55 +18,21 @@
#include "lib/test/run.hpp"
#include "lib/test/test-helper.hpp"
//#include "lib/test/test-helper.hpp"
#include "lib/test/tracking-dummy.hpp"
#include "lib/branch-case.hpp"
//#include "lib/iter-explorer.hpp"
//#include "lib/format-util.hpp"
#include "lib/meta/tuple-helper.hpp"
#include "lib/format-obj.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 lib {
namespace test{
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"
/********************************************************//**
* @test verify a _Sum Type_ to hold alternative model types
* for several result branches of an evaluation.
* @see parse.hpp "usage example"
*/
class BranchCase_test : public Test
{
@ -74,178 +40,150 @@ namespace test {
virtual void
run (Arg)
{
simpleBlah();
acceptAlternatives();
simpleUsage();
demonstrateStorage();
verifyCopyAssignment();
}
/** @test TODO just blah. */
/** @test create one alternative and access embedded model value. */
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()
simpleUsage()
{
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 branch{1, 42}; // construct for second branch (#1) to hold ushort(42)
CHECK (1 == branch.selected());
CHECK (42 == branch.get<1>()); // direct access with known branch-nr
CHECK ('*' == branch.get<0>()); // Warning: no protection against accessing the wrong branch
int val{-5};
auto visitor = [&](auto& it){ val = it;};
branch.accept (visitor);
CHECK (42 == val);
}
/** @test demonstrate expected storage layout...
* - the selector field always coincides with the object itself
* - the storage buffer starts after the `size_t` selector
*/
void
demonstrateStorage()
{
using Branch = BranchCase<ushort,double>;
CHECK (sizeof(double)+sizeof(size_t) <= sizeof(Branch));
CHECK (sizeof(double) == Branch::SIZ);
double phi{(1+sqrt(5))/2};
Branch b1{1,phi};
CHECK (1 == b1.selected());
CHECK (phi == b1.get<1>());
auto p = reinterpret_cast<size_t*> (&b1);
CHECK (1 == *p);
CHECK (phi == * reinterpret_cast<double*>(p+1));
// force-place a differently constructed object at the same location
new(p) Branch{0,42};
CHECK (0 == b1.selected());
CHECK (42 == b1.get<0>());
CHECK (0 == *p);
CHECK (42 == * reinterpret_cast<ushort*>(p+1));
}
/** @test verify selector and payload instances
* are properly handled on copy, clone, assignment and swap.
*/
void
verifyCopyAssignment()
{
using Branch = BranchCase<char,string>;
CHECK (sizeof(string)+sizeof(size_t) <= sizeof(Branch));
// use generic to-String visitor to display contents
auto render = [](auto const& it) -> string { return util::toString(it); };
Branch b1{1, "evil"};
CHECK ( 1 == b1.TOP );
CHECK ( 1 == b1.selected());
CHECK ("evil" == b1.get<1>());
CHECK ("evil" == b1.accept(render));
Branch b2{0,42};
CHECK ( 0 == b2.selected());
CHECK ('*' == b2.get<0>());
CHECK ("*" == b2.accept(render));
Branch b3{b1};
SHOW_EXPR(b3.selected());
SHOW_EXPR(b3.get<1>());
SHOW_EXPR(b3.get<0>());
CHECK (1 == b3.selected());
CHECK ("evil" == b3.accept(render));
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>());
CHECK ( 0 == b3.selected());
CHECK ("*" == b3.accept(render));
CHECK ("*" == b2.accept(render));
CHECK ("evil" == b1.accept(render));
b3 = move(b1);
CHECK ( 1 == b3.selected() );
CHECK ( 0 == b2.selected() );
CHECK ("evil" == b3.accept(render));
CHECK ("*" == b2.accept(render));
CHECK ("" == b1.accept(render)); // ◁——————————— warning: moved-away string is "implementation defined"
swap (b3,b2);
CHECK ( 0 == b3.selected());
CHECK ( 1 == b2.selected());
CHECK ("*" == b3.accept(render));
CHECK ("evil" == b2.accept(render));
CHECK ("" == b1.accept(render));
//_______________________________
// verify proper payload lifecycle
seedRand();
Dummy::checksum() = 0;
{ // track instances by checksum...
Dummy dummy;
auto rr = dummy.getVal();
CHECK (rr == Dummy::checksum());
CHECK (rr > 0);
using BB = BranchCase<string,Dummy>;
BB bb1{1, dummy};
CHECK (bb1.get<1>().getVal() == rr);
CHECK (2*rr == Dummy::checksum()); // got two instances due to copy-init
BB bb2{0, "dummy"};
CHECK (2*rr == Dummy::checksum());
swap (bb1,bb2);
CHECK (bb1.get<0>() == "dummy");
CHECK (bb2.get<1>().getVal() == rr);
CHECK (2*rr == Dummy::checksum());
bb1 = bb2;
CHECK (bb1.get<1>().getVal() == rr);
CHECK (3*rr == Dummy::checksum()); // assignment by copy
bb2 = move(bb1); // move-assignment
CHECK (2*rr == Dummy::checksum()); // existing instance destroyed properly
CHECK (bb2.get<1>().getVal() == rr);
CHECK (bb1.get<1>().getVal() == Dummy::DEFUNCT);
bb2 = BB{1,Dummy()}; // wipes out the other copy
auto rr2 = bb2.get<1>().getVal(); // but implants a different one
CHECK (rr+rr2 == Dummy::checksum());
CHECK (rr == dummy.getVal());
}// leave scope: invoke dtors here
CHECK (0 == Dummy::checksum());
}
};
LAUNCHER (BranchCase_test, "unit common");
}}} // namespace util::parse::test
}} // namespace lib::test

View file

@ -35,6 +35,7 @@ namespace util {
namespace parse{
namespace test {
using lib::test::showType;
using lib::meta::is_Tuple;
using std::get;
// using util::join;
@ -212,7 +213,10 @@ namespace test {
}
/** @test TODO define alternative syntax structures to match by parse. */
/** @test TODO WIP define alternative syntax structures to match by parse.
* - first demonstrate how a model with alternative branches can be
* populated and gradually extended while searching for a match.
*/
void
acceptAlternatives()
{
@ -221,27 +225,50 @@ namespace test {
using R3 = double;
using A1 = AltModel<R1>;
SHOW_EXPR(showType<A1>())
CHECK (showType<A1>() == "parse::AltModel<char>"_expect);
string s{"second"};
using A2 = A1::Additionally<R2>;
SHOW_EXPR(showType<A2>())
CHECK (showType<A2>() == "parse::AltModel<char, string>"_expect);
A2 model2{s};
SHOW_EXPR(sizeof(A2));
CHECK (sizeof(A2) >= sizeof(string)+sizeof(size_t));
SHOW_EXPR(model2.SIZ);
CHECK (model2.SIZ == sizeof(string));
SHOW_EXPR(model2.TOP);
CHECK (model2.TOP == 1);
SHOW_EXPR(model2.selected())
CHECK (model2.selected() == 1);
SHOW_EXPR(model2.get<1>())
CHECK (model2.get<1>() == "second");
using A3 = A2::Additionally<R3>;
A3 model3{model2.addBranch<R3>()};
SHOW_TYPE(A3)
SHOW_EXPR(showType<A3>())
CHECK (showType<A3>() == "parse::AltModel<char, string, double>"_expect);
SHOW_EXPR(sizeof(A3));
CHECK (sizeof(A3) == sizeof(A2));
SHOW_EXPR(model3.SIZ);
SHOW_EXPR(model3.TOP);
CHECK (model3.TOP == 2);
SHOW_EXPR(model3.selected())
CHECK (model3.selected() == 1);
SHOW_EXPR(model3.get<1>())
CHECK (model3.get<1>() == "second");
auto res = move(model3);
SHOW_TYPE(decltype(res))
SHOW_EXPR(showType<decltype(res)>())
CHECK (showType<decltype(res)>() == "parse::AltModel<char, string, double>"_expect);
SHOW_EXPR(sizeof(res))
CHECK (sizeof(res) == sizeof(A2));
SHOW_EXPR(res.selected())
CHECK (res.selected() == 1);
SHOW_EXPR(res.get<1>())
CHECK (res.get<1>() == "second");
}
};

View file

@ -55932,7 +55932,8 @@
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1737234396680" ID="ID_679636766" MODIFIED="1737235494542" TEXT="Alternativ-Kombinator bauen">
<icon BUILTIN="flag-yellow"/>
<node CREATED="1737235517170" ID="ID_1134480788" MODIFIED="1737235532164" TEXT="mu&#xdf; hier zuerst die Model-Mechanik entwickeln">
<node COLOR="#338800" CREATED="1737235517170" ID="ID_1134480788" MODIFIED="1737430437282" TEXT="mu&#xdf; hier zuerst die Model-Mechanik entwickeln">
<icon BUILTIN="button_ok"/>
<node CREATED="1737235560972" ID="ID_968357989" MODIFIED="1737235564743" TEXT="Datenstruktur">
<node CREATED="1737235591344" ID="ID_96667610" MODIFIED="1737235611568" TEXT="Selektor-Feld mit Index des Falls"/>
<node CREATED="1737235565816" ID="ID_1766417786" MODIFIED="1737235627609" TEXT="gemeinsamer Variant-Pufferspeicher"/>
@ -56359,8 +56360,7 @@
<b>Fazit</b>: implementierbar mit generischem &#955;-Visitor
</p>
</body>
</html>
</richcontent>
</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">
@ -56385,7 +56385,8 @@
<node COLOR="#338800" CREATED="1737400165799" ID="ID_1938031717" MODIFIED="1737400253142" TEXT="beides realisierbar indem man emplace() vom Slot-Idx trennt">
<icon BUILTIN="idea"/>
</node>
<node COLOR="#338800" CREATED="1737400212247" ID="ID_1273590576" MODIFIED="1737400253142" TEXT="Big-Five + swap">
<node COLOR="#338800" CREATED="1737400212247" ID="ID_1273590576" MODIFIED="1737430994581" TEXT="Big-Five + swap">
<arrowlink COLOR="#7ec79d" DESTINATION="ID_628431022" ENDARROW="Default" ENDINCLINATION="8;-37;" ID="Arrow_ID_949299271" STARTARROW="None" STARTINCLINATION="119;17;"/>
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1737400227088" ID="ID_389977912" MODIFIED="1737400253142" TEXT="Extension in erweiterten Branch">
@ -56399,14 +56400,72 @@
<icon BUILTIN="idea"/>
</node>
</node>
<node COLOR="#435e98" CREATED="1737430658252" ID="ID_1384746938" MODIFIED="1737430672746" TEXT="kann Alignment differenziert setzen (meta-programming)">
<icon BUILTIN="idea"/>
</node>
<node BACKGROUND_COLOR="#cae0ac" COLOR="#338800" CREATED="1737430474573" ID="ID_628431022" LINK="https://stackoverflow.com/a/3279550/444796" MODIFIED="1737430994581" TEXT="Instanz-Lifecycle funktioniert dank copy-and-swap-idiom">
<linktarget COLOR="#7ec79d" DESTINATION="ID_628431022" ENDARROW="Default" ENDINCLINATION="8;-37;" ID="Arrow_ID_949299271" SOURCE="ID_1273590576" STARTARROW="None" STARTINCLINATION="119;17;"/>
<icon BUILTIN="idea"/>
<node CREATED="1737430561377" ID="ID_515306512" MODIFIED="1737430576235" TEXT="mu&#xdf;te nur f&#xfc;r das tmp-Objekt auch den Selektor setzen"/>
<node CREATED="1737430576847" ID="ID_1830352266" MODIFIED="1737430594871" TEXT="denn auch f&#xfc;r ein moved-from-Objekt wird der Destruktor aufgerufen"/>
<node COLOR="#338800" CREATED="1737430595492" ID="ID_36654365" MODIFIED="1737430618280" TEXT="per tracking-Dummy verifiziert">
<arrowlink COLOR="#48bd80" DESTINATION="ID_375538458" ENDARROW="Default" ENDINCLINATION="171;11;" ID="Arrow_ID_526994655" STARTARROW="None" STARTINCLINATION="100;83;"/>
<icon BUILTIN="yes"/>
</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 COLOR="#338800" CREATED="1737417020140" ID="ID_1230396959" MODIFIED="1737430419638" TEXT="sehr komplex &#x27f9; in Hilfskomponente auslagern">
<icon BUILTIN="button_ok"/>
<node BACKGROUND_COLOR="#c8c0b6" CREATED="1737417038777" HGAP="28" ID="ID_158901588" MODIFIED="1737430931534" TEXT="lib/branch-case.hpp" VSHIFT="-10">
<font BOLD="true" NAME="SansSerif" SIZE="12"/>
<node BACKGROUND_COLOR="#e6c5c5" COLOR="#7846cd" CREATED="1737430684997" HGAP="41" ID="ID_104419590" MODIFIED="1737430921056" STYLE="fork" TEXT="Puh" VSHIFT="-11">
<edge COLOR="#68278e" STYLE="sharp_linear" WIDTH="thin"/>
<font NAME="SansSerif" SIZE="11"/>
<icon BUILTIN="smiley-angry"/>
<node COLOR="#8b4371" CREATED="1737430759180" ID="ID_1287127407" MODIFIED="1737430862615" TEXT="ned schee">
<font NAME="SansSerif" SIZE="8"/>
</node>
<node COLOR="#7f679b" CREATED="1737430794945" ID="ID_649388804" MODIFIED="1737430853698" STYLE="fork" TEXT="aber jetzt soweit sauber">
<edge COLOR="#68278e" STYLE="sharp_linear" WIDTH="thin"/>
<font NAME="SansSerif" SIZE="8"/>
</node>
<node COLOR="#5643a1" CREATED="1737430812167" ID="ID_957189686" MODIFIED="1737430873431" STYLE="fork" TEXT="erscheint mir orthogonal">
<edge COLOR="#68278e" STYLE="sharp_linear" WIDTH="thin"/>
<font NAME="SansSerif" SIZE="8"/>
</node>
<node COLOR="#5568be" CREATED="1737430832517" ID="ID_117353585" MODIFIED="1737430885972" STYLE="fork" TEXT="(und gr&#xfc;ndlich dokumentiert)">
<edge COLOR="#68278e" STYLE="sharp_linear" WIDTH="thin"/>
<font NAME="SansSerif" SIZE="8"/>
</node>
</node>
</node>
<node COLOR="#338800" CREATED="1737417051936" ID="ID_1310118315" MODIFIED="1737430348882" TEXT="BranchCase_test">
<icon BUILTIN="button_ok"/>
<node COLOR="#435e98" CREATED="1737418936874" ID="ID_241142755" MODIFIED="1737418941305" TEXT="simpleUsage"/>
<node COLOR="#435e98" CREATED="1737430288998" ID="ID_969378075" MODIFIED="1737430290869" TEXT="demonstrateStorage">
<node CREATED="1737430351843" ID="ID_1772903378" MODIFIED="1737430363153" TEXT="in-object-Layout explizit pr&#xfc;fen"/>
<node CREATED="1737430363788" ID="ID_413000106" MODIFIED="1737430414023" TEXT="ja: das ist portabel gem&#xe4;&#xdf; Standard">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<p>
weil ein Objekt ohne VTable mit seinem ersten Member beginnen mu&#223;, size_t der &#187;slot&#171;-Gr&#246;&#223;e entspricht und der Storage-Puffer stets direkt dahinter ist
</p>
</body>
</html></richcontent>
</node>
</node>
<node COLOR="#435e98" CREATED="1737430295677" ID="ID_129475614" MODIFIED="1737430297716" TEXT="verifyCopyAssignment">
<node BACKGROUND_COLOR="#c8c0b6" CREATED="1737430298855" ID="ID_375538458" MODIFIED="1737430612713" TEXT="hier auch den tracking-dummy verwenden!">
<linktarget COLOR="#48bd80" DESTINATION="ID_375538458" ENDARROW="Default" ENDINCLINATION="171;11;" ID="Arrow_ID_526994655" SOURCE="ID_36654365" STARTARROW="None" STARTINCLINATION="100;83;"/>
<icon BUILTIN="idea"/>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#d004b0" CREATED="1737430322353" ID="ID_644600846" MODIFIED="1737430338430" TEXT="tja... wer testet, der findet">
<icon BUILTIN="smiley-oh"/>
</node>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1737417060920" ID="ID_644848240" MODIFIED="1737417078580" TEXT="auf dieser Basis das AltModel definieren">