diff --git a/src/lib/meta/trait.hpp b/src/lib/meta/trait.hpp index 0b33c83cc..a4f341e7a 100644 --- a/src/lib/meta/trait.hpp +++ b/src/lib/meta/trait.hpp @@ -336,6 +336,7 @@ namespace meta { template struct is_StringLike : __or_< is_basically + , is_basically , is_convertible > { }; diff --git a/src/lib/text-template.hpp b/src/lib/text-template.hpp index 791ef3296..2bf3bdbc7 100644 --- a/src/lib/text-template.hpp +++ b/src/lib/text-template.hpp @@ -133,7 +133,7 @@ namespace lib { const string MATCH_LOGIC_TOK = "if|for"; const string MATCH_END_TOK = "end\\s*"; const string MATCH_ELSE_TOK = "else"; - const string MATCH_SYNTAX = "("+MATCH_ELSE_TOK+")|(?:("+MATCH_END_TOK+")?("+MATCH_LOGIC_TOK+")\\s+)?("+MATCH_KEY_PATH+")"; + const string MATCH_SYNTAX = "("+MATCH_ELSE_TOK+")|(?:("+MATCH_END_TOK+")?("+MATCH_LOGIC_TOK+")\\s*)?("+MATCH_KEY_PATH+")?"; const string MATCH_FIELD = "\\$\\{\\s*(?:"+MATCH_SYNTAX+")\\s*\\}"; const string MATCH_ESCAPE = R"~((\\\$))~"; @@ -199,6 +199,9 @@ namespace lib { .transform (classify); } } + namespace test { // declared friend for test access + class TextTemplate_test; + } @@ -289,6 +292,8 @@ namespace lib { template static string apply (string spec, DAT const& data); + + friend class test::TextTemplate_test; }; @@ -309,9 +314,10 @@ namespace lib { */ template class TextTemplate::ActionCompiler + : public PAR { Idx idx_{0}; - Action currToken_{}; + Action currToken_{TEXT, initLead()}; optional post_{nullopt}; public: @@ -348,16 +354,16 @@ namespace lib { { //...throws if exhausted TagSyntax& tag = PAR::yield(); auto isState = [this](Code c){ return c == currToken_.code; }; - auto nextState = [this] { - StrView lead = tag.tail; - PAR::iterNext(); - // first expose intermittent text before next tag - if (PAR::checkPoint()) - lead = PAR::yield().lead; - else // expose tail after final match - post_ = lead; - return Action{TEXT, lead}; - }; + auto nextState = [this, &tag] { + StrView lead = tag.tail; + PAR::iterNext(); + // first expose intermittent text before next tag + if (PAR::checkPoint()) + lead = PAR::yield().lead; + else // expose tail after final match + post_ = lead; + return Action{TEXT, string{lead}}; + }; switch (tag.syntax) { case TagSyntax::ESCAPE: return nextState(); @@ -366,13 +372,46 @@ namespace lib { return nextState(); return Action{KEY, tag.key}; case TagSyntax::IF: + if (isState (COND)) + return nextState(); + ///////////////////////////////////////////////////OOO push IF-clause here + return Action{COND, tag.key}; case TagSyntax::END_IF: + ///////////////////////////////////////////////////OOO verify and pop IF-clause here + return nextState(); case TagSyntax::FOR: + if (isState (ITER)) + return nextState(); + ///////////////////////////////////////////////////OOO push FOR-clause here + return Action{ITER, tag.key}; case TagSyntax::END_FOR: + ///////////////////////////////////////////////////OOO verify and pop FOR-clause here + return nextState(); + case TagSyntax::ELSE: + if (true) /////////////////////////////////////////OOO derive IF or FOR from context + { + if (isState (JUMP)) + return nextState(); + ///////////////////////////////////////////////////OOO actual IF-else implementation + return Action{JUMP}; + } + else + { + if (isState (LOOP)) + return nextState(); + ///////////////////////////////////////////////////OOO actual FOR-else implementation + return Action{LOOP}; + } default: NOTREACHED ("uncovered TagSyntax keyword while compiling a TextTemplate."); } } + + string + initLead() ///< first Action must present the literal text before the first tag + { + return string{PAR::checkPoint()? PAR::yield().lead : ""}; + } }; diff --git a/tests/library/text-template-test.cpp b/tests/library/text-template-test.cpp index 1e9c2a9d3..d76040e0d 100644 --- a/tests/library/text-template-test.cpp +++ b/tests/library/text-template-test.cpp @@ -48,6 +48,7 @@ namespace lib { namespace test { using MapS = std::map; + using LERR_(ITER_EXHAUST); /***************************************************************************//** @@ -193,12 +194,49 @@ namespace test { == "${two}, \\$, ${if high}"_expect); - auto render = [](TagSyntax& tag) -> string - { return _Fmt{"▶%s‖%d|%s‖▷"} % string{tag.lead} % uint(tag.syntax) % tag.key; }; - auto wau = parse(input) - .transform(render); -SHOW_EXPR(util::join(wau)) + // Parse matches of this regexp into well defined syntax elements + auto parser = parse(input); + CHECK (not isnil(parser)); + CHECK (parser->syntax == TagSyntax::KEYID); + CHECK (parser->lead == "one "_expect); + CHECK (parser->key == "two"_expect); + ++parser; + CHECK (parser); + CHECK (parser->syntax == TagSyntax::ESCAPE); + CHECK (parser->lead == " three "_expect); + CHECK (parser->key == ""_expect); + ++parser; + CHECK (parser); + CHECK (parser->syntax == TagSyntax::IF); + CHECK (parser->lead == "\\${four} "_expect); + CHECK (parser->key == "high"_expect); + ++parser; + CHECK (isnil (parser)); + VERIFY_ERROR (ITER_EXHAUST, *parser); + VERIFY_ERROR (ITER_EXHAUST, ++parser); + + + // Generate sequence of Action tokens from parsing results + auto render = [](TextTemplate::Action const& act) -> string + { return _Fmt{"‖%d|↷%d‖▷%s"} % uint(act.code) % act.refIDX % act.val; }; +SHOW_EXPR(util::join(parse(input) + .processingLayer() + .transform(render) + , "▶")) + input = R"~( + Prefix-1 ${some.key} next one is \${escaped} + Prefix-2 ${if cond1} active ${else} inactive ${end if +}Prefix-3 ${if cond2} active2${end if cond2} more + Prefix-4 ${for data} fixed ${embedded} + Pre-5 ${if nested}nested-active${ + else }nested-inactive${ end + if nested}loop-suffix${else}${end +for} tail... +)~"; + auto compiler = parse(input) + .processingLayer(); +SHOW_EXPR(util::join(compiler.transform(render),"▶\n▶")) } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 3cc909ea4..a0a04bdf9 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -112544,9 +112544,7 @@ std::cout << tmpl.render({"what", "World"}) << s - - - +

als Einstellung des konkreten Templates? ⟹ das wäre einfach, aber unpraktisch für den Client @@ -112741,9 +112739,7 @@ std::cout << tmpl.render({"what", "World"}) << s - - - +

...dieser enthält die Informationen aus dem RegExp-Match bereits semantisch aufgeschlüsselt @@ -112754,9 +112750,7 @@ std::cout << tmpl.render({"what", "World"}) << s - - - +

...das heißt, die einzelne Auswertung ist keine pure function — aber der Seiteneffekt-Stat verbleibt in der Pipeline selber und merkt sich den Endpunkt des vorausgehenden Matches @@ -112777,14 +112771,14 @@ std::cout << tmpl.render({"what", "World"}) << s - - + + - + - + @@ -112802,7 +112796,7 @@ std::cout << tmpl.render({"what", "World"}) << s - + @@ -112815,7 +112809,7 @@ std::cout << tmpl.render({"what", "World"}) << s - + @@ -112836,7 +112830,7 @@ std::cout << tmpl.render({"what", "World"}) << s - + @@ -113122,9 +113116,7 @@ std::cout << tmpl.render({"what", "World"}) << s - - - +

man könnte wohl was basteln mit den Funktionen position(i) und length(i) @@ -113136,14 +113128,12 @@ std::cout << tmpl.render({"what", "World"}) << s - + - + - - - +

...und diese soll irgendwie auf eine Pipeline aufbauen. Das bedeutet, die Lösung sollte möglichst in der Verarbeitung selber zugänglich sein, und nicht über eine externe Zusatz-Information oder einen Seiteneffekt. Es wäre denkbar, auf das Ende des letzten Match aufzubauen — allerdings noch viel schöner wäre es, wenn der letzte Match den Quell-String komplett ausschöpft, so daß gar kein Rest übrig bleibt @@ -113164,8 +113154,8 @@ std::cout << tmpl.render({"what", "World"}) << s - - + + @@ -113212,9 +113202,7 @@ std::cout << tmpl.render({"what", "World"}) << s - - - +

erst in einem zweiten Schritt wird explizit eine spezifische Action für diese Syntax emittiert @@ -113230,9 +113218,7 @@ std::cout << tmpl.render({"what", "World"}) << s - - - +

in diesem speziellen Fall wird das verbleibende Postfix @@ -113249,7 +113235,21 @@ std::cout << tmpl.render({"what", "World"}) << s - + + + + + + + + + + + + + + + @@ -113257,10 +113257,10 @@ std::cout << tmpl.render({"what", "World"}) << s - + - + @@ -113353,6 +113353,21 @@ std::cout << tmpl.render({"what", "World"}) << s + + + + + + + + + + + + + + +