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:
parent
d052edf91d
commit
4f676f7213
6 changed files with 510 additions and 779 deletions
|
|
@ -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,540 +74,196 @@
|
|||
#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 {
|
||||
|
||||
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 {
|
||||
|
||||
using std::move;
|
||||
using std::forward;
|
||||
using std::decay_t;
|
||||
using std::tuple;
|
||||
|
||||
|
||||
namespace {// Metaprogramming helper
|
||||
|
||||
template<typename...TYPES>
|
||||
struct _MaxBuf;
|
||||
|
||||
template<>
|
||||
struct _MaxBuf<>
|
||||
{
|
||||
static constexpr size_t siz = 0;
|
||||
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 = _MaxBuf<TYPES...>::siz;
|
||||
|
||||
template<size_t idx>
|
||||
using SlotType = std::tuple_element_t<idx, tuple<TYPES...>>;
|
||||
|
||||
protected:
|
||||
BranchCase() = default; ///< @internal default-created state is **invalid**
|
||||
|
||||
/** selector field to designate the chosen branch */
|
||||
size_t branch_{0};
|
||||
|
||||
/** 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)
|
||||
{
|
||||
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:
|
||||
/**
|
||||
* 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
|
||||
accept (FUN&& visitor)
|
||||
{
|
||||
return selectBranch<TOP> (forward<FUN> (visitor));
|
||||
}
|
||||
|
||||
~BranchCase()
|
||||
{
|
||||
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}
|
||||
{
|
||||
accept ([&,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.accept ([this](auto& it)
|
||||
{ using Elm = decay_t<decltype(it)>;
|
||||
this->emplace<Elm> (it);
|
||||
});
|
||||
}
|
||||
|
||||
BranchCase (BranchCase && ro)
|
||||
{
|
||||
branch_ = ro.branch_;
|
||||
ro.accept ([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;
|
||||
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.accept ([&](auto& it)
|
||||
{ using Elm = decay_t<decltype(it)>;
|
||||
o1.emplace<Elm> (move (o2.access<Elm>()));
|
||||
});
|
||||
o2.accept ([&](auto& it)
|
||||
{ using Elm = decay_t<decltype(it)>;
|
||||
o2.emplace<Elm> (move (tmp.access<Elm>()));
|
||||
});
|
||||
}
|
||||
|
||||
BranchCase&
|
||||
operator= (BranchCase ref)
|
||||
{
|
||||
swap (*this, ref);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
size_t
|
||||
selected() const
|
||||
{
|
||||
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()
|
||||
{
|
||||
return access<SlotType<idx>>();
|
||||
}
|
||||
};
|
||||
|
||||
}// namespace lib
|
||||
#endif/*LIB_BRANCH_CASE_H*/
|
||||
|
|
|
|||
|
|
@ -182,18 +182,18 @@ namespace util {
|
|||
*/
|
||||
template<typename...CASES>
|
||||
struct AltModel
|
||||
: BranchCase<CASES...>
|
||||
: lib::BranchCase<CASES...>
|
||||
{
|
||||
using _Model = BranchCase<CASES...>;
|
||||
using _Model = lib::BranchCase<CASES...>;
|
||||
|
||||
template<typename EX>
|
||||
using Additionally = AltModel<CASES...,EX>;
|
||||
|
||||
template<typename INIT, typename =lib::meta::disable_if_self<AltModel,INIT>>
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
checksum() -= val_;
|
||||
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)
|
||||
operator= (Dummy oDummy) ///< accepts both lvalues and rvalues
|
||||
{
|
||||
if (&oDummy != this)
|
||||
{
|
||||
swap (*this, oDummy);
|
||||
oDummy.setVal(0);
|
||||
}
|
||||
swap (*this, oDummy);
|
||||
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_;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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ß hier zuerst die Model-Mechanik entwickeln">
|
||||
<node COLOR="#338800" CREATED="1737235517170" ID="ID_1134480788" MODIFIED="1737430437282" TEXT="muß 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 λ-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ö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ßte nur für das tmp-Objekt auch den Selektor setzen"/>
|
||||
<node CREATED="1737430576847" ID="ID_1830352266" MODIFIED="1737430594871" TEXT="denn auch fü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 ⟹ 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 ⟹ 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ü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üfen"/>
|
||||
<node CREATED="1737430363788" ID="ID_413000106" MODIFIED="1737430414023" TEXT="ja: das ist portabel gemäß Standard">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
weil ein Objekt ohne VTable mit seinem ersten Member beginnen muß, size_t der »slot«-Größ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">
|
||||
|
|
|
|||
Loading…
Reference in a new issue