diff --git a/src/lib/text-template.hpp b/src/lib/text-template.hpp index e50abe568..ba548abd6 100644 --- a/src/lib/text-template.hpp +++ b/src/lib/text-template.hpp @@ -131,6 +131,20 @@ namespace lib { template using ExploreIter = decltype (lib::explore (std::declval())); + //-----------Syntax-for-iteration-control-in-map------ + const string MATCH_DATA_TOKEN { R"~(([^,;"\s]*)\s*)~"}; + const string MATCH_DELIMITER { R"~((?:^|,|;)\s*)~" }; + const regex ACCEPT_DATA_ELM {MATCH_DELIMITER + MATCH_DATA_TOKEN}; + + inline auto + iterNestedKeys (string key, string const& iterDef) + { + return explore (util::RegexSearchIter{iterDef, ACCEPT_DATA_ELM}) + .transform ([&](smatch mat){ return key+"."+string{mat[1]}; }); + } + + + //-----------Syntax-for-TextTemplate-tags-------- const string MATCH_SINGLE_KEY = "[A-Za-z_]+\\w*"; const string MATCH_KEY_PATH = MATCH_SINGLE_KEY+"(?:\\."+MATCH_SINGLE_KEY+")*"; const string MATCH_LOGIC_TOK = "if|for"; @@ -283,8 +297,12 @@ namespace lib { StrView& yield() const; void iterNext(); - void instantiateNext(); + StrView instantiateNext(); + StrView reInstatiate (Idx =Idx(-1)); StrView getContent(string key); + bool conditional (string key); + bool openIteration (string key); + bool loopFurther(); }; template @@ -507,7 +525,7 @@ namespace lib { struct TextTemplate::DataSource { MapS const * data_; - using Iter = std::string_view; + using Iter = decltype(iterNestedKeys("","")); bool contains (string key) @@ -522,6 +540,19 @@ namespace lib { ENSURE (elm != data_->end()); return elm->second; } + + Iter + getSequence (string key) + { + UNIMPLEMENTED ("extract data sequence from definition key"); + } + + DataSource + openContext (Iter& iter) + { + REQUIRE (iter); + UNIMPLEMENTED ("open a nested sub-data-ctx based on the given iterator"); + } }; @@ -545,13 +576,16 @@ namespace lib { case KEY: return core.getContent (val); case COND: - return ""; + return core.conditional (val)? core.reInstatiate() // next is the conditional content + : core.reInstatiate(refIDX); // points to start of else-block (or after) case JUMP: - return ""; + return core.reInstatiate(refIDX); case ITER: - return ""; + return core.openIteration(val)? core.reInstatiate() // looping initiated => continue with next + : core.reInstatiate(refIDX); // points to start of else-block (or after) case LOOP: - return ""; + return core.loopFurther() ? core.reInstatiate(refIDX+1) // start with one after the loop opening + : core.reInstatiate(); // continue with next -> jump over else-block default: NOTREACHED ("uncovered Activity verb in activation function."); } @@ -593,25 +627,104 @@ namespace lib { TextTemplate::InstanceCore::iterNext() { ++actionIter_; - instantiateNext(); + rendered_ = instantiateNext(); } - template - inline void - TextTemplate::InstanceCore::instantiateNext() - { - rendered_ = actionIter_? actionIter_->instantiate(*this) - : StrView{}; - } + /** Instantiate next Action token and expose its rendering */ template inline StrView - TextTemplate::InstanceCore::getContent(string key) + TextTemplate::InstanceCore::instantiateNext() + { + return actionIter_? actionIter_->instantiate(*this) + : StrView{}; + } + + /** + * relocate to another Action token and continue instantiation there + * @param nextCode index number of the next token; + * when not given, then iterate one step ahead + * @return the rendering produced by the selected next Action token + */ + template + inline StrView + TextTemplate::InstanceCore::reInstatiate (Idx nextCode) + { + if (nextCode == Idx(-1)) + ++actionIter_; + else + actionIter_.setIDX (nextCode); + return instantiateNext(); + } + + /** retrieve a data value from the data source for the indiated key */ + template + inline StrView + TextTemplate::InstanceCore::getContent (string key) { static StrView nil{""}; return dataSrc_.contains(key)? dataSrc_.retrieveContent(key) : nil; } + /** retrieve a data value for the key and interpret it as boolean expression */ + template + inline bool + TextTemplate::InstanceCore::conditional (string key) + { + return not util::isNo (string{getContent (key)}); + } + + /** + * Attempt to open data sequence by evaluating the entrance key. + * Data is retrieved for the key and evaluated to produce a collection of + * data entities to be iterated; each of these will be handled as a data scope + * nested into the current data scope. This implies to push a #NestedCtx into the + * #ctxStack_, store aside the current data source and replace it with the new + * data source for the nested scope. If iteration can not be initiated, all of + * the initialisation is reverted and the previous scope is reinstated. + * @return `true` if the nested context exists and the first element is available, + * `false` if iteration is not possible and the original context was restored + */ + template + inline bool + TextTemplate::InstanceCore::openIteration (string key) + { + if (dataSrc_.contains(key)) + if (DataCtxIter dataIter = dataSrc_.getSequence(key)) + { + ctxStack_.push (NestedCtx{move (dataIter) + ,dataSrc_}); + dataSrc_ = dataSrc_.openContext (ctxStack_.top().first); + return true; + } + return false; + } + + /** + * Possibly continue the iteration within an already established nested scope. + * If iteration to the next element is possible, it is expanded into the nested scope, + * else, when reaching iteration end, the enclosing scope is reinstated + * @return `true` if the next iterated element is available, `false` after iteration end + */ + template + inline bool + TextTemplate::InstanceCore::loopFurther() + { + DataCtxIter& dataIter = ctxStack_.top().first; + ++dataIter; + if (dataIter) + { // open next nested context *from enclosing context* + dataSrc_ = ctxStack_.top().second.openContext (dataIter); + return true; + } + else + { // restore original data context + std::swap (dataSrc_, ctxStack_.top().second); + ctxStack_.pop(); + return false; + } + } + diff --git a/src/lib/util.cpp b/src/lib/util.cpp index 7f686aeae..185a0098e 100644 --- a/src/lib/util.cpp +++ b/src/lib/util.cpp @@ -118,6 +118,14 @@ namespace util { return regex_match (textForm, trueTokens); } + + bool + isNo (string const& textForm) noexcept + { + return isnil (textForm) + or regex_match (textForm, falseTokens); + } + } // namespace util diff --git a/src/lib/util.hpp b/src/lib/util.hpp index 936571b9b..dffb47a4e 100644 --- a/src/lib/util.hpp +++ b/src/lib/util.hpp @@ -465,6 +465,12 @@ namespace util { */ bool isYes (string const&) noexcept; + /** check if the given text is empty or can be interpreted as rejection (bool `false`)- + * @remarks this function fishes for the known `false` tokens; + * any other non-empty content counts as _not no._ + */ + bool isNo (string const&) noexcept; + } // namespace util diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 25263a6d2..7637a9f5d 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -112564,10 +112564,12 @@ std::cout << tmpl.render({"what", "World"}) << s + + @@ -112731,8 +112733,8 @@ std::cout << tmpl.render({"what", "World"}) << s - - + + @@ -112820,11 +112822,15 @@ std::cout << tmpl.render({"what", "World"}) << s - + + + - + + + @@ -112852,24 +112858,24 @@ std::cout << tmpl.render({"what", "World"}) << s - + - + - + - + - + @@ -112878,14 +112884,25 @@ std::cout << tmpl.render({"what", "World"}) << s - + + + + + + - - + + + + + + + + @@ -112976,7 +112993,8 @@ std::cout << tmpl.render({"what", "World"}) << s - + + @@ -113000,6 +113018,34 @@ std::cout << tmpl.render({"what", "World"}) << s + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -113062,8 +113108,8 @@ std::cout << tmpl.render({"what", "World"}) << s - - + + @@ -113366,7 +113412,15 @@ std::cout << tmpl.render({"what", "World"}) << s - + + + + + + + + + @@ -113374,6 +113428,40 @@ std::cout << tmpl.render({"what", "World"}) << s + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +