diff --git a/src/lib/text-template-gen-node-binding.hpp b/src/lib/text-template-gen-node-binding.hpp index 8719b3bca..a52188797 100644 --- a/src/lib/text-template-gen-node-binding.hpp +++ b/src/lib/text-template-gen-node-binding.hpp @@ -49,8 +49,7 @@ ** can expand a `${value}` placeholder in that scope. ** - Attributes of enclosing scopes are also visible — unless shadowed. ** [ETD]: https://lumiera.org/documentation/design/architecture/ETD.html - ** @todo WIP-WIP-WIP 3/2024 - ** @see TextTemplate_test + ** @see TextTemplate_test::verify_ETD_binding */ @@ -60,124 +59,99 @@ #include "lib/diff/gen-node.hpp" #include "lib/text-template.hpp" -//#include "lib/format-string.hpp" -//#include "lib/format-util.hpp" -//#include "lib/regex.hpp" -//#include "lib/util.hpp" -//#include #include -//#include -//#include -//#include namespace lib { using std::string; -// using StrView = std::string_view; -// -// using util::_Fmt; -// using util::isnil; -// using util::unConst; - namespace { + namespace text_template { - } - - /* ======= Data binding for GenNode (ETD) ======= */ - - /** - * Data-binding for a tree of GenNode data (ETD). - * Attributes are accessible as keys, while iteration descends - * into the child scope of the attribute indicated in the ${for }` tag. - * @see TextTemplate_test::verify_ETD_binding() - */ - template<> - struct TextTemplate::DataSource - { - using Node = diff::GenNode; - using Rec = diff::Rec; - - Node const* data_; - DataSource* parScope_; - bool isSubScope() { return bool(parScope_); } - - DataSource(Node const& root) - : data_{&root} - , parScope_{nullptr} - { } - - - using Value = std::string; - using Iter = Rec::scopeIter; - - Node const* - findNode (string key) - { - if (data_->isNested()) - {// standard case: Attribute lookup - Rec const& record = data_->data.get(); - if (record.hasAttribute (key)) - return & record.get (key); - } - else - if ("value" == key) // special treatment for a »pseudo context« - return data_; // comprised only of a single value node - else - if (isSubScope()) - return parScope_->findNode (key); - - return nullptr; - } - - bool - contains (string key) - { - return bool(findNode (key)); - } - - Value - retrieveContent (string key) - { - return Value(findNode(key)->data); ///////////////////////////////OOO this is not correct -- need a way to render the bare content - } - - Iter - getSequence (string key) - { - if (not contains(key)) - return Iter{}; - Node const* node = findNode (key); - if (not node->isNested()) - return Iter{}; - else - return node->data.get().scope(); - } - - DataSource - openContext (Iter& iter) - { - REQUIRE (iter); - DataSource nested{*this}; - nested.parScope_ = this; - nested.data_ = & *iter; - return nested; - } - }; - - - namespace {// help the compiler with picking the proper specialisation for the data binding + /* ======= Data binding for GenNode (ETD) ======= */ - inline auto - bindDataSource(diff::GenNode const& etd) + /** + * Data-binding for a tree of GenNode data (ETD). + * Attributes are accessible as keys, while iteration descends + * into the child scope of the attribute indicated in the ${for }` tag. + * @see TextTemplate_test::verify_ETD_binding() + */ + template<> + struct DataSource { - return TextTemplate::DataSource{etd}; - } - } - - + using Node = diff::GenNode; + using Rec = diff::Rec; + + Node const* data_; + DataSource* parScope_; + bool isSubScope() { return bool(parScope_); } + + DataSource(Node const& root) + : data_{&root} + , parScope_{nullptr} + { } + + + using Value = std::string; + using Iter = Rec::scopeIter; + + Node const* + findNode (string key) + { + if (data_->isNested()) + {// standard case: Attribute lookup + Rec const& record = data_->data.get(); + if (record.hasAttribute (key)) + return & record.get (key); + } + else + if ("value" == key) // special treatment for a »pseudo context« + return data_; // comprised only of a single value node + // ask parent scope... + if (isSubScope()) + return parScope_->findNode (key); + + return nullptr; + } + + bool + contains (string key) + { + return bool(findNode (key)); + } + + Value + retrieveContent (string key) + { + return renderCompact (*findNode(key)); + } + + Iter + getSequence (string key) + { + if (not contains(key)) + return Iter{}; + Node const* node = findNode (key); + if (not node->isNested()) + return Iter{}; + else + return node->data.get().scope(); + } + + DataSource + openContext (Iter& iter) + { + REQUIRE (iter); + DataSource nested{*this}; + nested.parScope_ = this; + nested.data_ = & *iter; + return nested; + } + }; + + }// namespace text_template }// namespace lib #endif /*LIB_TEXT_TEMPLATE_GEN_NODE_BINDING_H*/ diff --git a/src/lib/text-template.hpp b/src/lib/text-template.hpp index 6b181f6a9..a06d1ed06 100644 --- a/src/lib/text-template.hpp +++ b/src/lib/text-template.hpp @@ -147,9 +147,10 @@ ** ** \par ETD Binding ** While the _Map Binding_ detailed in the preceding paragraph is mostly intended to handle - ** simple key substitutions, the more elaborate binding to `GenNode` data (ETD) is meant to - ** handle structural data, as encountered in the internal communication of components within - ** the Lumiera application — notably the »diff binding« used to populate the GUI with entities + ** simple key substitutions, the more elaborate binding to `GenNode` data (ETD), which is + ** provided in the separate header text-template-gen-node-binding.hpp, is meant to handle + ** structural data, as encountered in the internal communication of components within the + ** Lumiera application — notably the »diff binding« used to populate the GUI with entities ** represented in the _Session Model_ in Steam-Layer. The mapping is straight-forward, as the ** required concepts can be supported directly ** - Key lookup is translated into _Attribute Lookup_ — starting in the current record and @@ -158,10 +159,9 @@ ** for the iteration; thus each entity is again a `Rec` and can be represented ** recursively as a DataSource> ** - the DataSource implementation includes an _optional parent link,_ which is consulted - ** whenever _Attribute Lookup_ in the current record does not yield a result. + ** whenever _Attribute Lookup_ in the current record does not yield a result. ** [External Tree Description]: https://lumiera.org/documentation/design/architecture/ETD.html ** [Lumiera Forward Iterator]: https://lumiera.org/documentation/technical/library/iterator.html - ** @todo WIP-WIP-WIP 3/2024 ** @see TextTemplate_test ** @see text-template-gen-node-binding.hpp ** @see gnuplot-gen.hpp @@ -192,6 +192,10 @@ namespace lib { namespace error = lumiera::error; + namespace test { // declared friend for test access + class TextTemplate_test; + } + using std::string; using StrView = std::string_view; @@ -200,7 +204,7 @@ namespace lib { using util::unConst; - namespace { + namespace text_template { ///< Parser and DataSource binding for lib::TextTemplate //-----------Syntax-for-iteration-control-in-map------ const string MATCH_DATA_TOKEN = R"~(([^,;"\s]*)\s*)~"; @@ -307,17 +311,30 @@ namespace lib { return explore (util::RegexSearchIter{input, ACCEPT_MARKUP}) .transform (classify); } - } - namespace test { // declared friend for test access - class TextTemplate_test; - } + + + /** + * Binding to a specific data source. + * @note requires partial specialisation + */ + template + class DataSource; + + }//(namespace) text_template - /*****************************************//** - * Text template substitution engine + + /*************************************************//** + * Text template substitution engine. + * Can substitute `${placeholders}` by name and + * can handle conditional and iterated sections. + * Structural data for the substitution is accessed + * and navigated through a generic _Data Source binding._ + * By default, a binding for Map-of-strings is provided. + * @see TextTemplate_test */ class TextTemplate : util::MoveOnly @@ -356,15 +373,10 @@ namespace lib { /** the text template is compiled into a sequence of Actions */ using ActionSeq = std::vector; - /** processor in a parse pipeline — yields sequence of Actions */ class ActionCompiler; - /** Binding to a specific data source. - * @note requires partial specialisation */ - template - class DataSource; - + /** Iterator »State Core« to process the template instantiation */ template class InstanceCore { @@ -428,8 +440,8 @@ namespace lib { /* ======= Parser / Compiler pipeline ======= */ /** - * @remarks this is a »custom processing layer« - * to be used in an [Iter-Explorer](\ref iter-explorer.hpp)-pipeline. + * @remarks this builder component is used on top of a + * [Iter-Explorer](\ref iter-explorer.hpp)-pipeline, based on a reg-exp. * The source layer (which is assumed to comply to the »State Core« concept), * yields TagSyntax records, one for each match of the ACCEPT_MARKUP reg-exp. * The actual compilation step, which is implemented as pull-processing here, @@ -464,15 +476,15 @@ namespace lib { auto scopeClause = [&]{ return scope_.empty()? "??" : clause(scope_.top().clause); }; // Support for bracketing constructs (if / for) - auto beginIdx = [&]{ return scope_.empty()? 0 : scope_.top().begin; }; // Index of action where scope was opened - auto scopeKey = [&]{ return valid(beginIdx())? actions[beginIdx()].val : "";}; // Key controlling the if-/for-Scope - auto keyMatch = [&]{ return isnil(parseIter->key) or parseIter->key == scopeKey(); }; // Key matches in opening and closing tag - auto clauseMatch = [&](Clause c){ return not scope_.empty() and scope_.top().clause == c; }; // Kind of closing tag matches innermost scope + auto beginIdx = [&]{ return scope_.empty()? 0 : scope_.top().begin; }; // Index of action where scope was opened + auto scopeKey = [&]{ return valid(beginIdx())? actions[beginIdx()].val : "";}; // Key controlling the if-/for-Scope + auto keyMatch = [&]{ return isnil(parseIter->key) or parseIter->key == scopeKey(); }; // Key matches in opening and closing tag + auto clauseMatch = [&](Clause c){ return not scope_.empty() and scope_.top().clause == c; }; // Kind of closing tag matches innermost scope auto scopeMatch = [&](Clause c){ return clauseMatch(c) and keyMatch(); }; auto lead = [&]{ return parseIter->lead; }; - auto clashLead = [&]{ return actions[scope_.top().after - 1].val; }; // (for diagnostics: lead before a conflicting other "else") - auto abbrev = [&](auto s){ return s.length()<16? s : s.substr(s.length()-15); }; // (shorten lead display to 15 chars) + auto clashLead = [&]{ return actions[scope_.top().after - 1].val; }; // (for diagnostics: lead before a conflicting other "else") + auto abbrev = [&](auto s){ return s.length()<16? s : s.substr(s.length()-15); }; // (shorten lead display to 15 chars) // Syntax / consistency checks... auto __requireKey = [&](string descr) @@ -480,8 +492,7 @@ namespace lib { if (isnil (parseIter->key)) throw error::Invalid{_Fmt{"Tag without key: ...%s${%s |↯|}"} % abbrev(lead()) % descr - }; - }; + }; }; auto __checkBalanced = [&](Clause c) { if (not scopeMatch(c)) @@ -490,8 +501,7 @@ namespace lib { % scopeClause() % scopeKey() % abbrev(lead()) % clause(c) % parseIter->key - }; - }; + }; }; auto __checkInScope = [&] { if (scope_.empty()) throw error::Invalid{_Fmt{"Misplaced ...%s|↯|${else}"} @@ -510,18 +520,19 @@ namespace lib { // Primitives used for code generation.... auto add = [&](Code c, string v){ actions.push_back (Action{c,v});}; - auto addCode = [&](Code c) { add ( c, parseIter->key); }; // add code token and transfer key picked up by parser - auto addLead = [&] { add (TEXT, string{parseIter->lead}); }; // add TEXT token to represent the static part before this tag - auto openScope = [&](Clause c){ scope_.push (ParseCtx{c, currIDX()}); }; // start nested scope for bracketing construct (if / for) - auto closeScope = [&] { scope_.pop(); }; // close innermost nested scope + auto addCode = [&](Code c) { add ( c, parseIter->key); }; // add code token and transfer key picked up by parser + auto addLead = [&] { add (TEXT, string{parseIter->lead}); }; // add TEXT token to represent the static part before this tag + auto openScope = [&](Clause c){ scope_.push (ParseCtx{c, currIDX()}); }; // start nested scope for bracketing construct (if / for) + auto closeScope = [&] { scope_.pop(); }; // close innermost nested scope - auto linkElseToStart = [&]{ actions[beginIdx()].refIDX = currIDX(); }; // link the start position of the else-branch into opening logic code - auto markJumpInScope = [&]{ scope_.top().after = currIDX(); }; // memorise jump before else-branch for later linkage - auto linkLoopBack = [&]{ actions.back().refIDX = scope_.top().begin; }; // fill in the back-jump position at loop end - auto linkJumpToNext = [&]{ actions[scope_.top().after].refIDX = currIDX(); }; // link jump to the position after the end of the logic bracket + auto linkElseToStart = [&]{ actions[beginIdx()].refIDX = currIDX(); }; // link the start position of the else-branch into opening logic code + auto markJumpInScope = [&]{ scope_.top().after = currIDX(); }; // memorise jump before else-branch for later linkage + auto linkLoopBack = [&]{ actions.back().refIDX = scope_.top().begin; }; // fill in the back-jump position at loop end + auto linkJumpToNext = [&]{ actions[scope_.top().after].refIDX = currIDX(); }; // link jump to the position after the end of the logic bracket - auto hasElse = [&]{ return scope_.top().after != 0; }; // a jump code to link was only marked if there was an else tag + auto hasElse = [&]{ return scope_.top().after != 0; }; // a jump code to link was only marked if there was an else tag + using text_template::TagSyntax; /* === Code Generation === */ switch (parseIter->syntax) { @@ -603,7 +614,7 @@ namespace lib { inline TextTemplate::ActionSeq TextTemplate::compile (string const& spec) { - ActionSeq code = ActionCompiler().buildActions (parse (spec)); + ActionSeq code = ActionCompiler().buildActions (text_template::parse (spec)); if (isnil (code)) throw error::Invalid ("TextTemplate spec without active placeholders."); return code; @@ -612,144 +623,138 @@ namespace lib { + + /* ======= preconfigured data bindings ======= */ - template - struct TextTemplate::DataSource - { - static_assert (not sizeof(DAT), - "unable to bind this data source " - "for TextTemplate instantiation"); - - DataSource (DAT const&); - }; - - using MapS = std::map; - - /** - * Data-binding for a Map-of-strings. - * Simple keys are retrieved by direct lookup. - * For the representation of nested data sequences, - * the following conventions apply - * - the data sequence itself is represented by an index-key - * - the value associated to this index-key is a CSV sequence - * - each element in this sequence defines a key prefix - * - nested keys are then defined as `..` - * - when key decoration is enabled for a nested data source, each - * lookup for a given key is first tried with the prefix, then as-is. - * Consequently, all data in the sequence must be present in the original - * map, stored under the decorated keys. - * @note multiply nested sequences are _not supported._ - * While it _is_ possible to have nested loops, the resulting sets - * of keys must be disjoint and data must be present in the base map. - * @see TextTemplate_test::verify_Map_binding() - */ - template<> - struct TextTemplate::DataSource - { - MapS const * data_{nullptr}; - string keyPrefix_{}; - - bool isNested() { return not isnil (keyPrefix_); } - - DataSource() = default; - DataSource(MapS const& map) - : data_{&map} - { } - - - using Value = std::string_view; - using Iter = decltype(iterNestedKeys("","")); - - bool - contains (string key) - { - return (isNested() and util::contains (*data_, keyPrefix_+key)) - or util::contains (*data_, key); - } - - Value - retrieveContent (string key) - { - MapS::const_iterator elm; - if (isNested()) - { - elm = data_->find (keyPrefix_+key); - if (elm == data_->end()) - elm = data_->find (key); - } - else - elm = data_->find (key); - ENSURE (elm != data_->end()); - return elm->second; - } - - Iter - getSequence (string key) - { - if (not contains(key)) - return Iter{}; - else - return iterNestedKeys (key, retrieveContent(key)); - } - - DataSource - openContext (Iter& iter) - { - REQUIRE (iter); - DataSource nested{*this}; - nested.keyPrefix_ += *iter; - return nested; - } - }; - - using PairS = std::pair; - - template<> - struct TextTemplate::DataSource - : TextTemplate::DataSource - { - std::shared_ptr spec_; - - DataSource (string const& dataSpec) - : spec_{new MapS} - { - data_ = spec_.get(); - explore (iterBindingSeq (dataSpec)) - .foreach([this](PairS const& bind){ spec_->insert (bind); }); - } + namespace text_template { + + template + struct DataSource + { + static_assert (not sizeof(DAT), + "unable to bind this data source " + "for TextTemplate instantiation"); - DataSource - openContext (Iter& iter) - { - DataSource nested(*this); - auto nestedBase = DataSource::openContext (iter); - nested.keyPrefix_ = nestedBase.keyPrefix_; - return nested; - } - }; - - namespace {// help the compiler with picking the proper specialisation for the data binding + DataSource (DAT const&); + }; - template> > - inline auto - bindDataSource(STR const& spec) - { - return TextTemplate::DataSource{spec}; - } + using MapS = std::map; - inline auto - bindDataSource(MapS const& map) - { - return TextTemplate::DataSource{map}; - } - - /* Why this approach? couldn't we use CTAD? - * - for one, there are various compiler bugs related to nested templates and CTAD - * - moreover I am unable to figure out how to write a deduction guide for an - * user provided specialisation, given possibly within another header. + /** + * Data-binding for a Map-of-strings. + * Simple keys are retrieved by direct lookup. + * For the representation of nested data sequences, + * the following conventions apply + * - the data sequence itself is represented by an index-key + * - the value associated to this index-key is a CSV sequence + * - each element in this sequence defines a key prefix + * - nested keys are then defined as `..` + * - when key decoration is enabled for a nested data source, each + * lookup for a given key is first tried with the prefix, then as-is. + * Consequently, all data in the sequence must be present in the original + * map, stored under the decorated keys. + * @note multiply nested sequences are _not supported._ + * While it _is_ possible to have nested loops, the resulting sets + * of keys must be disjoint and data must be present in the base map. + * @see TextTemplate_test::verify_Map_binding() */ - } + template<> + struct DataSource + { + MapS const * data_{nullptr}; + string keyPrefix_{}; + + bool isSubScope() { return not isnil (keyPrefix_); } + + DataSource() = default; + DataSource(MapS const& map) + : data_{&map} + { } + + + using Value = std::string_view; + using Iter = decltype(iterNestedKeys("","")); + + bool + contains (string key) + { + return (isSubScope() and util::contains (*data_, keyPrefix_+key)) + or util::contains (*data_, key); + } + + Value + retrieveContent (string key) + { + MapS::const_iterator elm; + if (isSubScope()) + { + elm = data_->find (keyPrefix_+key); + if (elm == data_->end()) + elm = data_->find (key); + } + else + elm = data_->find (key); + ENSURE (elm != data_->end()); + return elm->second; + } + + Iter + getSequence (string key) + { + if (not contains(key)) + return Iter{}; + else + return iterNestedKeys (key, retrieveContent(key)); + } + + DataSource + openContext (Iter& iter) + { + REQUIRE (iter); + DataSource nested{*this}; + nested.keyPrefix_ += *iter; + return nested; + } + }; + + + using PairS = std::pair; + + /** Adapter for the Map-Data-Source to consume a string spec (for testing) */ + template<> + struct DataSource + : DataSource + { + std::shared_ptr spec_; + + DataSource (string const& dataSpec) + : spec_{new MapS} + { + data_ = spec_.get(); + explore (iterBindingSeq (dataSpec)) + .foreach([this](PairS const& bind){ spec_->insert (bind); }); + } + + DataSource + openContext (Iter& iter) + { + DataSource nested(*this); + auto nestedBase = DataSource::openContext (iter); + nested.keyPrefix_ = nestedBase.keyPrefix_; + return nested; + } + }; + + /** + * Deduction Guide: help the compiler with picking the proper specialisation + * for a test-data source defined through a string spec or char literal + */ + template> > + DataSource(STR const&) -> DataSource; + + }// namespace text_template + @@ -834,7 +839,7 @@ namespace lib { TextTemplate::InstanceCore::instantiateNext() { return actionIter_? actionIter_->instantiate(*this) - : StrView{}; + : Value{}; } /** @@ -859,7 +864,7 @@ namespace lib { inline typename SRC::Value TextTemplate::InstanceCore::getContent (string key) { - static StrView nil{""}; + static Value nil{}; return dataSrc_.contains(key)? dataSrc_.retrieveContent(key) : nil; } @@ -957,7 +962,7 @@ namespace lib { inline auto TextTemplate::submit (DAT const& data) const { - return explore (InstanceCore{actions_, bindDataSource(data)}); + return explore (InstanceCore{actions_, text_template::DataSource(data)}); } /** submit data and materialise rendered results into a single string */ diff --git a/tests/15library.tests b/tests/15library.tests index 9f0cf40c1..87b191763 100644 --- a/tests/15library.tests +++ b/tests/15library.tests @@ -780,7 +780,7 @@ return: 0 END -PLANNED "text template substitution" TextTemplate_test < @@ -42,7 +38,9 @@ using std::regex_search; using std::smatch; using util::_Fmt; using util::join; +using lib::diff::Rec; using lib::diff::MakeRec; +using lib::diff::GenNode; namespace lib { @@ -51,6 +49,9 @@ namespace test { using MapS = std::map; using LERR_(ITER_EXHAUST); + using text_template::ACCEPT_MARKUP; + using text_template::TagSyntax; + /***************************************************************************//** * @test verify a minimalistic text substitution engine with flexible @@ -79,9 +80,7 @@ namespace test { } - /** @test simple point-and-shot usage... - * @todo WIP 4/24 ✔ define ⟶ ✔ implement - */ + /** @test simple point-and-shot usage... */ void simpeUsage() { @@ -100,7 +99,6 @@ namespace test { * - 3 ≙ end token * - 4 ≙ some logic token ("if" or "for") * - 5 ≙ a key or key path - * @todo WIP 4/24 ✔ define ⟶ ✔ implement */ void verify_parsing() @@ -197,7 +195,7 @@ namespace test { // Parse matches of this regexp into well defined syntax elements - auto parser = parse (input); + auto parser = text_template::parse (input); CHECK (not isnil(parser)); CHECK (parser->syntax == TagSyntax::KEYID); CHECK (parser->lead == "one "_expect); @@ -359,9 +357,7 @@ for} tail... - /** @test Compile a template and instantiate with various data bindings. - * @todo WIP 4/24 ✔ define ⟶ ✔ implement - */ + /** @test Compile a template and instantiate with various data bindings. */ void verify_instantiation() { @@ -381,8 +377,7 @@ for} tail... /** @test Segments of the text-template can be included - * conditionally, based on interpretation of a controlling key - * @todo WIP 4/24 ✔ define ⟶ ✔ implement + * conditionally, based on interpretation of a controlling key. */ void verify_conditional() @@ -420,9 +415,8 @@ for} tail... * - for this test we use the Map-binding, which synthesises * key prefixes and expects bindings for those decorated keys * - typically, keys in inner scopes will shadow outer keys, - * as is here demonstrated with the "x" key at top level + * as is demonstrated here with the "x" key at top level * - loops and conditionals can be nested - * @todo WIP 4/24 ✔ define ⟶ ✔ implement */ void verify_iteration() @@ -450,8 +444,10 @@ for} tail... } - /** @test TODO - * @todo WIP 4/24 🔁 define ⟶ implement + + /** @test build a data binding to a map-of-strings, + * and verify all the operations used internally + * by the text-template engine to navigate the data. */ void verify_Map_binding() @@ -462,14 +458,14 @@ for} tail... ,{"i.q.a","22"} ,{"i.q.aa","222"}}; - auto binding = bindDataSource (data); - CHECK (meta::typeStr(binding) == "TextTemplate::DataSource, void>"_expect ); + auto binding = text_template::DataSource{data}; + CHECK (meta::typeStr(binding) == "text_template::DataSource, void>"_expect ); CHECK ( binding.contains("a")); CHECK (not binding.contains("b")); CHECK (binding.retrieveContent("a") == "5"_expect ); CHECK (binding.retrieveContent("i") == "p,q,r"_expect ); CHECK (binding.retrieveContent("i.q.aa") == "222"_expect ); - CHECK (not binding.isNested()); + CHECK (not binding.isSubScope()); auto it = binding.getSequence("i"); CHECK (it); @@ -477,7 +473,7 @@ for} tail... CHECK (meta::typeStr(it) == "IterExplorer, string> > > >"_expect ); auto subBind = binding.openContext(it); - CHECK (subBind.isNested()); + CHECK (subBind.isSubScope()); CHECK ((meta::is_same())); CHECK ( subBind.contains("a")); CHECK (not subBind.contains("b")); @@ -494,7 +490,7 @@ for} tail... CHECK (subBind.retrieveContent("a") == "11"_expect ); // ...rather need to open a new sub-ctx explicitly subBind = binding.openContext(it); - CHECK (subBind.isNested()); + CHECK (subBind.isSubScope()); CHECK (subBind.contains("a")); CHECK (subBind.contains("aa")); CHECK (subBind.retrieveContent("a") == "22"_expect ); @@ -519,8 +515,15 @@ for} tail... } - /** @test TODO - * @todo WIP 4/24 🔁 define ⟶ implement + /** @test represent the same logical structure as in the + * [preceding test](\ref #verify_Map_binding), + * yet this time as a tree of GenNodes + * - value bindings are translated into attribute access + * - the iteration now requires an actually nested scope, + * holding a sequence of child nodes + * - each of these nodes constitutes a »data entity« + * - when accessing keys from _within_ such a nested scope, attributes + * of enclosing scopes are visible, unless shadowed by local definition. */ void verify_ETD_binding() @@ -535,96 +538,63 @@ for} tail... .set("a", 22) .set("aa", 222) .genNode() + , MakeRec() + /*——empty——*/ + .genNode() )) .genNode(); -SHOW_EXPR(root) - auto binding = bindDataSource (root); -SHOW_TYPE(decltype(binding)) -SHOW_EXPR(meta::typeStr(binding)) -SHOW_EXPR(binding.contains("a")) -SHOW_EXPR(binding.contains("b")) -SHOW_EXPR(binding.retrieveContent("a")) -SHOW_EXPR(binding.retrieveContent("i")) -#if false ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// -SHOW_EXPR(binding.retrieveContent("i.q.aa")) -SHOW_EXPR(binding.isNested()) - CHECK (meta::typeStr(binding) == "TextTemplate::DataSource, void>"_expect ); + + auto binding = text_template::DataSource{root}; + CHECK (meta::typeStr(binding) == "text_template::DataSource"_expect ); CHECK ( binding.contains("a")); CHECK (not binding.contains("b")); - CHECK (binding.retrieveContent("a") == "5"_expect ); - CHECK (binding.retrieveContent("i") == "p,q,r"_expect ); - CHECK (binding.retrieveContent("i.q.aa") == "222"_expect ); - CHECK (not binding.isNested()); + CHECK (binding.retrieveContent("a") == "5"_expect ); + CHECK (binding.retrieveContent("i") == "{|{a=11}, {a=22, aa=222}, {}}"_expect ); + CHECK (not binding.isSubScope()); auto it = binding.getSequence("i"); -SHOW_EXPR(meta::typeStr(it)) -SHOW_EXPR(bool(it)) -SHOW_EXPR(*it) CHECK (it); - CHECK (*it == "i.p."_expect ); - CHECK (meta::typeStr(it) == "IterExplorer, string> > > >"_expect ); + CHECK (renderCompact(*it) == "{a=11}"); + CHECK (*it == root.data.get().get("i").data.get().child(0)); auto subBind = binding.openContext(it); -SHOW_EXPR(meta::typeStr(subBind)) -SHOW_EXPR(bool(std::is_same())) -SHOW_EXPR(subBind.isNested()) -SHOW_EXPR(subBind.contains("a")) -SHOW_EXPR(subBind.contains("b")) -SHOW_EXPR(subBind.contains("aa")) -SHOW_EXPR(subBind.contains("i")) -SHOW_EXPR(subBind.retrieveContent("i")) -SHOW_EXPR(subBind.retrieveContent("a")) - CHECK (subBind.isNested()); + CHECK (subBind.isSubScope()); CHECK ((meta::is_same())); CHECK ( subBind.contains("a")); CHECK (not subBind.contains("b")); CHECK (not subBind.contains("aa")); CHECK ( subBind.contains("i")); - CHECK (subBind.retrieveContent("i") == "p,q,r"_expect ); - CHECK (subBind.retrieveContent("a") == "11"_expect ); + CHECK (subBind.retrieveContent("i") == "{|{a=11}, {a=22, aa=222}, {}}"_expect ); + CHECK (subBind.retrieveContent("a") == "11"_expect ); + ++it; -SHOW_EXPR(bool(it)) -SHOW_EXPR(*it) CHECK (it); - CHECK (*it == "i.q."_expect ); -SHOW_EXPR(subBind.retrieveContent("a")) - CHECK (subBind.retrieveContent("a") == "11"_expect ); + CHECK (renderCompact(*it) == "{a=22, aa=222}"); + CHECK (subBind.retrieveContent("a") == "11"_expect ); + subBind = binding.openContext(it); -SHOW_EXPR(subBind.isNested()) -SHOW_EXPR(subBind.contains("a")) -SHOW_EXPR(subBind.contains("aa")) -SHOW_EXPR(subBind.retrieveContent("a")) -SHOW_EXPR(subBind.retrieveContent("aa")) -SHOW_EXPR(subBind.retrieveContent("i.p.a")) -SHOW_EXPR(subBind.retrieveContent("i.q.a")) - CHECK (subBind.isNested()); + CHECK (subBind.isSubScope()); CHECK (subBind.contains("a")); CHECK (subBind.contains("aa")); - CHECK (subBind.retrieveContent("a") == "22"_expect ); - CHECK (subBind.retrieveContent("aa") == "222"_expect); - CHECK (subBind.retrieveContent("i.p.a") == "11"_expect ); - CHECK (subBind.retrieveContent("i.q.a") == "22"_expect ); + CHECK (subBind.retrieveContent("a") == "22"_expect ); + CHECK (subBind.retrieveContent("aa") == "222"_expect); + ++it; -SHOW_EXPR(bool(it)) CHECK (it); -SHOW_EXPR(*it) - CHECK (*it == "i.r."_expect ); + CHECK (renderCompact(*it) == "{}"); + subBind = binding.openContext(it); -SHOW_EXPR(subBind.contains("a")) -SHOW_EXPR(subBind.contains("aa")) -SHOW_EXPR(subBind.retrieveContent("a")) -SHOW_EXPR(subBind.retrieveContent("i.p.a")) -SHOW_EXPR(subBind.retrieveContent("i.q.a")) CHECK ( subBind.contains("a")); CHECK (not subBind.contains("aa")); - CHECK (subBind.retrieveContent("a") == "5"_expect ); - CHECK (subBind.retrieveContent("i.p.a") == "11"_expect ); - CHECK (subBind.retrieveContent("i.q.a") == "22"_expect ); + CHECK (subBind.retrieveContent("a") == "5"_expect ); + ++it; -SHOW_EXPR(bool(it)) CHECK (isnil (it)); VERIFY_ERROR (ITER_EXHAUST, *it); -#endif + + + TextTemplate tt{"${for i}a=${a} ${if aa}and aa=${aa} ${endif}${endfor}."}; + CHECK (tt.render(root) == "a=11 a=22 and aa=222 a=5 ."_expect); } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index ddefc3c42..e763f9d00 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -112472,8 +112472,8 @@ std::cout << tmpl.render({"what", "World"}) << s - - + + @@ -112496,13 +112496,21 @@ std::cout << tmpl.render({"what", "World"}) << s - + + + + +

+ ein situativ-funktionales Daten-Binding +

+ +
- - + + - - + + @@ -112521,8 +112529,8 @@ std::cout << tmpl.render({"what", "World"}) << s - - + + @@ -112618,8 +112626,8 @@ std::cout << tmpl.render({"what", "World"}) << s - - + + @@ -112661,8 +112669,8 @@ std::cout << tmpl.render({"what", "World"}) << s - - + + @@ -112736,8 +112744,9 @@ std::cout << tmpl.render({"what", "World"}) << s - - + + + @@ -112857,8 +112866,9 @@ std::cout << tmpl.render({"what", "World"}) << s - - + + + @@ -112928,7 +112938,7 @@ std::cout << tmpl.render({"what", "World"}) << s - + @@ -113479,9 +113489,9 @@ std::cout << tmpl.render({"what", "World"}) << s - - - + + + @@ -113541,7 +113551,7 @@ std::cout << tmpl.render({"what", "World"}) << s - + @@ -113565,7 +113575,7 @@ std::cout << tmpl.render({"what", "World"}) << s - + @@ -113598,8 +113608,9 @@ std::cout << tmpl.render({"what", "World"}) << s - - + + + @@ -113633,6 +113644,20 @@ std::cout << tmpl.render({"what", "World"}) << s + + + + + + + + + + + + + + @@ -113640,15 +113665,16 @@ std::cout << tmpl.render({"what", "World"}) << s - - - - - + + + + + + - + @@ -113673,7 +113699,7 @@ std::cout << tmpl.render({"what", "World"}) << s - + @@ -113686,23 +113712,114 @@ std::cout << tmpl.render({"what", "World"}) << s - + - - - + + + + - - - - + + + + + + + + + + + + + + + + +

+   template<typename TYPES> +

+

+   template<typename TY> +

+

+   Variant<TYPES>::Buff<TY>::operator string()  const +

+

+   { +

+

+     return util::typedString (this->access()); +

+

+   } +

+ +
+ +
+ + + + +

+ OK ... nicht ganz das was wir brauchen +

+ +
+
+
+ + + + + + +

+ ...seinerzeit bin ich da mit meinem Design an Grenzen gestoßen und konnte (oder wollte) mich nicht aus der Zwanslage befreien; das Problem ist, daß die Dispatch-Funktion selber wieder eine virtuelle Funktion sein muß, die auf der »inneren Hülle« des Variant-Record definiert ist (damit dann die äußere Hülle ohne weitere Vorkenntnisse den Visitor nach innen schieben kann). Mit dem bestehenden Code ist man also in der Signatur des Visitors nicht frei, sondern hat nur wenige, festgelegte Varianten zus Auswahl (für die jeweils ein kompletter Pfad nach innen definiert ist). +

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

+ habe eine dritte Visitor-Variante eingeführt: den »Renderer« +

+ +
+ + + +

+ das ist dann ein VisitorConstFunc<string> +

+ +
+ +
+ + + + + + + @@ -113724,7 +113841,7 @@ std::cout << tmpl.render({"what", "World"}) << s - + @@ -113733,9 +113850,10 @@ std::cout << tmpl.render({"what", "World"}) << s - - - + + + + @@ -113767,7 +113885,7 @@ std::cout << tmpl.render({"what", "World"}) << s - + @@ -113799,7 +113917,7 @@ std::cout << tmpl.render({"what", "World"}) << s - + @@ -113817,6 +113935,7 @@ std::cout << tmpl.render({"what", "World"}) << s + @@ -113835,19 +113954,20 @@ std::cout << tmpl.render({"what", "World"}) << s - - + + + - - + + - + @@ -113874,12 +113994,14 @@ std::cout << tmpl.render({"what", "World"}) << s - + + - - + + + - + @@ -113888,13 +114010,14 @@ std::cout << tmpl.render({"what", "World"}) << s - - - + + + - + + @@ -113940,45 +114063,55 @@ std::cout << tmpl.render({"what", "World"}) << s - + + - - + + - + - + - - - - - - - - + + + + + + + + + + + + + - - + + + + + + @@ -114085,13 +114218,15 @@ std::cout << tmpl.render({"what", "World"}) << s - + - - - + + + + + + -