diff --git a/src/lib/branch-case.hpp b/src/lib/branch-case.hpp index 1581e013d..50a0b7246 100644 --- a/src/lib/branch-case.hpp +++ b/src/lib/branch-case.hpp @@ -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 @@ -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 #include #include #include -#include - -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 - struct _MaxBufSiz; - template<> - struct _MaxBufSiz<> - { - static constexpr size_t siz = 0; - }; - template - struct _MaxBufSiz - { - static constexpr size_t siz = std::max (sizeof(T) - ,_MaxBufSiz::siz); - }; - - template - class BranchCase - { - public: - static constexpr auto TOP = sizeof...(TYPES) -1; - static constexpr auto SIZ = _MaxBufSiz::siz; - - template - using SlotType = std::tuple_element_t>; - - protected: - /** @internal default-created state is **invalid** */ - BranchCase() = default; - - - size_t branch_{0}; - - alignas(int64_t) - std::byte buffer_[SIZ]; - - template - TX& - emplace (INITS&&...inits) - { - return * new(&buffer_) TX(forward (inits)...); - } - - template - TX& - access () - { - return * std::launder (reinterpret_cast (&buffer_[0])); - } - - /** apply generic functor to the currently selected branch */ - template - auto - selectBranch (FUN&& fun) - { - if constexpr (0 < idx) - if (branch_ < idx) - return selectBranch (forward(fun)); - return fun (get()); - } - - public: - template - auto - apply (FUN&& fun) - { - return selectBranch (forward (fun)); - } - - ~BranchCase() - { - apply ([this](auto& it) - { using Elm = decay_t; - access().~Elm(); - }); - } - - template - BranchCase (size_t idx, INITS&& ...inits) - : branch_{idx} - { - apply ([&,this](auto& it) - { using Elm = decay_t; - if constexpr (std::is_constructible_v) - this->emplace (forward (inits)...); - }); - } - - BranchCase (BranchCase const& o) - { - branch_ = o.branch_; - BranchCase& unConst = const_cast (o); - unConst.apply ([this](auto& it) - { using Elm = decay_t; - this->emplace (it); - }); - } - - BranchCase (BranchCase && ro) - { - branch_ = ro.branch_; - ro.apply ([this](auto& it) - { using Elm = decay_t; - this->emplace (move (it)); - }); - } - - friend void - swap (BranchCase& o1, BranchCase o2) - { - using std::swap; - BranchCase tmp; - o1.apply ([&](auto& it) - { using Elm = decay_t; - tmp.emplace (move (o1.access())); - }); - swap (o1.branch_,o2.branch_); - o1.apply ([&](auto& it) - { using Elm = decay_t; - o1.emplace (move (o2.access())); - }); - o2.apply ([&](auto& it) - { using Elm = decay_t; - o2.emplace (move (tmp.access())); - }); - } - - BranchCase& - operator= (BranchCase ref) - { - swap (*this, ref); - return *this; - } - - template - auto - moveExtended() - { - using Extended = BranchCase; - Extended& upFaked = reinterpret_cast (*this); - return Extended (move (upFaked)); - } - - - size_t - selected() const - { - return branch_; - } - - template - SlotType& - get() - { - return access>(); - } - }; - -#if false ///////////////////////////////////////////////////////////////////////////////TODO accommodate - /** - */ - template - struct Eval - { - using Result = RES; - optional result; - size_t consumed{0}; - }; - - template - struct Connex - : util::NonAssign - { - using PFun = FUN; - PFun parse; - - using Result = typename _Fun::Ret::Result; - - Connex (FUN&& pFun) - : parse{move(pFun)} - { } - }; - - auto - buildConnex(NullType) - { - return Connex{[](StrView) -> Eval - { - return {NullType{}}; - }}; - } - using NulP = decltype(buildConnex (NullType())); - - auto - buildConnex (regex rex) - { - return Connex{[regEx = move(rex)] - (StrView toParse) -> Eval - { // 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())); - - Term - buildConnex (string const& rexDef) - { - return buildConnex (regex{rexDef}); - } - - template - auto - buildConnex (Connex const& anchor) - { - return Connex{anchor}; - } - template - auto - buildConnex (Connex && anchor) - { - return Connex{move(anchor)}; - } - - - template - auto - adaptConnex (CON&& connex, BIND&& modelBinding) - { - using RX = typename CON::Result; - using Arg = lib::meta::_FunArg; - static_assert (std::is_constructible_v, - "Model binding must accept preceding model result."); - using AdaptedRes = typename _Fun::Ret; - return Connex{[origConnex = forward(connex) - ,binding = forward(modelBinding) - ] - (StrView toParse) -> Eval - { - 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 - struct SeqModel - : tuple - { - static constexpr size_t SIZ = sizeof...(RESULTS); - using Seq = lib::meta::TySeq; - using Tup = std::tuple; - - SeqModel() = default; - - template - SeqModel (SeqModel&& seq, XX&& extraElm) - : Tup{std::tuple_cat (seq.extractTuple() - ,make_tuple (forward (extraElm)) )} - { } - - template - 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 - struct AltModel - { - - }; - - - /** Special case Product Model to represent iterative sequence */ - template - struct IterModel - { - - }; - - /** Marker-Tag for the result from a sub-expression, not to be joined */ - template - struct SubModel - { - - }; - - /** Standard case : combinator of two model branches */ - template class TAG, class R1, class R2 =void> - struct _Join - { - using Result = TAG; - }; - - /** Generic case : extend a structured model by further branch */ - template class TAG, class...RS, class R2> - struct _Join,R2> - { - using Result = TAG; - }; - - /** Special Case : absorb sub-expression without flattening */ - template class TAG, class R1, class R2> - struct _Join,R2> - { - using Result = TAG; - }; - template class TAG, class R1, class R2> - struct _Join> - { - using Result = TAG; - }; - template class TAG, class R1, class R2> - struct _Join,SubModel> - { - using Result = TAG; - }; - - /** Special Case : absorb further similar elements into IterModel */ - template - struct _Join, RES> - { - using Result = IterModel; - }; - - - /** accept sequence of two parse functions */ - template - auto - sequenceConnex (C1&& connex1, C2&& connex2) - { - using R1 = typename decay_t::Result; - using R2 = typename decay_t::Result; - using ProductResult = typename _Join::Result; - using ProductEval = Eval; - return Connex{[conL = forward(connex1) - ,conR = forward(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 Syntax; - - - template - class Parser - : public CON - { - using PFun = typename CON::PFun; - static_assert (_Fun(), "Connex must define a parse-function"); - - public: - using Connex = CON; - using Result = typename CON::Result; - -using Sigi = typename _Fun::Sig; -//lib::test::TypeDebugger buggi; -//lib::test::TypeDebugger guggi; - - static_assert (has_Sig(StrView)>() - ,"Signature of the parse-function not suitable"); - - Eval - operator() (StrView toParse) - { - return CON::parse (toParse); - } - - template - Parser (SPEC&& spec) - : CON{buildConnex (forward (spec))} - { } - -// template -// Parser (Syntax const& anchor) -// : CON{anchor} -// { } -// template -// Parser (CON const& anchor) -// : CON{anchor} -// { } - }; - - Parser(NullType) -> Parser; - Parser(regex &&) -> Parser; - Parser(regex const&) -> Parser; - Parser(string const&) -> Parser; - - template - Parser(Connex const&) -> Parser>; -// -// template -// Parser(Syntax const&) -> Parser; - - - template - class Syntax - : public Eval - { - 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 - auto - seq (SPEC&& clauseDef) - { - return accept( - sequenceConnex (move(parse_) - ,Parser{forward (clauseDef)})); - } - - private: - Eval& - eval() - { - return *this; - } - }; - - template - auto - accept (SPEC&& clauseDef) - { - return Syntax{Parser{forward (clauseDef)}}; - } - - // template - // Parser(Syntax const&) -> Parser; - -#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 + struct _MaxBuf; + + template<> + struct _MaxBuf<> + { + static constexpr size_t siz = 0; + static constexpr size_t align = 0; + }; + + template + struct _MaxBuf + { + static constexpr size_t siz = std::max (sizeof(T),_MaxBuf::siz); + static constexpr size_t align = std::max (alignof(T),_MaxBuf::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 + class BranchCase + { + public: + static constexpr auto TOP = sizeof...(TYPES) -1; + static constexpr auto SIZ = _MaxBuf::siz; + + template + using SlotType = std::tuple_element_t>; + + 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::align) + std::byte buffer_[SIZ]; + + + template + TX& + emplace (INITS&&...inits) + { + return * new(&buffer_) TX(forward (inits)...); + } + + template + TX& + access () + { + return * std::launder (reinterpret_cast (&buffer_[0])); + } + + /** apply generic functor to the currently selected branch */ + template + auto + selectBranch (FUN&& fun) + { + if constexpr (0 < idx) + if (branch_ < idx) + return selectBranch (forward(fun)); + return fun (get()); + } + + + 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 + auto + accept (FUN&& visitor) + { + return selectBranch (forward (visitor)); + } + + ~BranchCase() + { + accept ([this](auto& it) + { using Elm = decay_t; + access().~Elm(); + }); + } + + /** Standard constructor: select branch and provide initialiser */ + template + BranchCase (size_t idx, INITS&& ...inits) + : branch_{idx} + { + accept ([&,this](auto& it) + { using Elm = decay_t; + if constexpr (std::is_constructible_v) + this->emplace (forward (inits)...); + }); + } + + BranchCase (BranchCase const& o) + { + branch_ = o.branch_; + BranchCase& unConst = const_cast (o); + unConst.accept ([this](auto& it) + { using Elm = decay_t; + this->emplace (it); + }); + } + + BranchCase (BranchCase && ro) + { + branch_ = ro.branch_; + ro.accept ([this](auto& it) + { using Elm = decay_t; + this->emplace (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; + tmp.emplace (move (o1.access())); + }); + swap (o1.branch_,o2.branch_); + o1.accept ([&](auto& it) + { using Elm = decay_t; + o1.emplace (move (o2.access())); + }); + o2.accept ([&](auto& it) + { using Elm = decay_t; + o2.emplace (move (tmp.access())); + }); + } + + 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 + SlotType& + get() + { + return access>(); + } + }; + }// namespace lib #endif/*LIB_BRANCH_CASE_H*/ diff --git a/src/lib/parse.hpp b/src/lib/parse.hpp index f84a8b2b0..bc2a11b3e 100644 --- a/src/lib/parse.hpp +++ b/src/lib/parse.hpp @@ -182,18 +182,18 @@ namespace util { */ template struct AltModel - : BranchCase + : lib::BranchCase { - using _Model = BranchCase; + using _Model = lib::BranchCase; - template - using Additionally = AltModel; - - template> + template> AltModel (INIT&& init) : _Model{_Model::TOP, forward (init)} { } + template + using Additionally = AltModel; + template Additionally addBranch() diff --git a/src/lib/test/tracking-dummy.hpp b/src/lib/test/tracking-dummy.hpp index f53abbd31..3b92bffa7 100644 --- a/src/lib/test/tracking-dummy.hpp +++ b/src/lib/test/tracking-dummy.hpp @@ -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::min(); + static constexpr int DEAD = std::numeric_limits::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_; - } }; diff --git a/tests/library/branch-case-test.cpp b/tests/library/branch-case-test.cpp index 0e2977ff9..2809f7596 100644 --- a/tests/library/branch-case-test.cpp +++ b/tests/library/branch-case-test.cpp @@ -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 -//#include - -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; - - } // (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; - using ProductEval = Eval; - 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; // Note: the result type depends on the actual syntax construction - CHECK (is_Tuple()); // 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; -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; + 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 (&b1); + CHECK (1 == *p); + CHECK (phi == * reinterpret_cast(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(p+1)); + } + + + + /** @test verify selector and payload instances + * are properly handled on copy, clone, assignment and swap. + */ + void + verifyCopyAssignment() + { + using Branch = BranchCase; + 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(); -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; + 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 diff --git a/tests/library/parse-test.cpp b/tests/library/parse-test.cpp index 8483f4991..861678049 100644 --- a/tests/library/parse-test.cpp +++ b/tests/library/parse-test.cpp @@ -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; +SHOW_EXPR(showType()) + CHECK (showType() == "parse::AltModel"_expect); string s{"second"}; using A2 = A1::Additionally; +SHOW_EXPR(showType()) + CHECK (showType() == "parse::AltModel"_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; A3 model3{model2.addBranch()}; SHOW_TYPE(A3) +SHOW_EXPR(showType()) + CHECK (showType() == "parse::AltModel"_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()) + CHECK (showType() == "parse::AltModel"_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"); } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index b19695e4d..46e234a8d 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -55932,7 +55932,8 @@ - + + @@ -56359,8 +56360,7 @@ Fazit: implementierbar mit generischem λ-Visitor

- - + @@ -56385,7 +56385,8 @@ - + + @@ -56399,14 +56400,72 @@ + + + + + + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ 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 +

+ +
+
+
+ + + + + + + + +