2024-03-18 22:32:49 +01:00
|
|
|
|
/*
|
|
|
|
|
|
TextTemplate(Test) - verify the minimalistic text substitution engine
|
|
|
|
|
|
|
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
|
|
|
|
Copyright (C)
|
|
|
|
|
|
2024, Hermann Vosseler <Ichthyostega@web.de>
|
2024-03-18 22:32:49 +01:00
|
|
|
|
|
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
|
|
|
|
**Lumiera** is free software; you can redistribute it and/or modify it
|
|
|
|
|
|
under the terms of the GNU General Public License as published by the
|
|
|
|
|
|
Free Software Foundation; either version 2 of the License, or (at your
|
|
|
|
|
|
option) any later version. See the file COPYING for further details.
|
2024-03-18 22:32:49 +01:00
|
|
|
|
|
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
|
|
|
|
* *****************************************************************/
|
2024-03-18 22:32:49 +01:00
|
|
|
|
|
|
|
|
|
|
/** @file text-template-test.cpp
|
|
|
|
|
|
** unit test \ref TextTemplate_test
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "lib/test/run.hpp"
|
2024-03-28 01:12:55 +01:00
|
|
|
|
#include "lib/test/test-helper.hpp"
|
2024-03-18 22:32:49 +01:00
|
|
|
|
#include "lib/text-template.hpp"
|
2024-03-27 02:07:32 +01:00
|
|
|
|
#include "lib/text-template-gen-node-binding.hpp"
|
2024-03-18 22:32:49 +01:00
|
|
|
|
|
2024-03-19 02:42:43 +01:00
|
|
|
|
#include <map>
|
2024-03-18 22:32:49 +01:00
|
|
|
|
|
|
|
|
|
|
//using std::array;
|
2024-03-22 01:35:31 +01:00
|
|
|
|
using std::regex_search;
|
|
|
|
|
|
using std::smatch;
|
2024-03-23 02:54:55 +01:00
|
|
|
|
using util::_Fmt;
|
2024-03-25 17:25:54 +01:00
|
|
|
|
using util::join;
|
2024-03-28 01:12:55 +01:00
|
|
|
|
using lib::diff::Rec;
|
2024-03-27 18:26:55 +01:00
|
|
|
|
using lib::diff::MakeRec;
|
2024-03-28 01:12:55 +01:00
|
|
|
|
using lib::diff::GenNode;
|
2024-03-18 22:32:49 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace lib {
|
|
|
|
|
|
namespace test {
|
|
|
|
|
|
|
2024-03-19 02:42:43 +01:00
|
|
|
|
using MapS = std::map<string, string>;
|
2024-03-23 23:47:30 +01:00
|
|
|
|
using LERR_(ITER_EXHAUST);
|
2024-03-19 02:42:43 +01:00
|
|
|
|
|
2024-03-28 01:12:55 +01:00
|
|
|
|
using text_template::ACCEPT_MARKUP;
|
|
|
|
|
|
using text_template::TagSyntax;
|
|
|
|
|
|
|
2024-03-18 22:32:49 +01:00
|
|
|
|
|
|
|
|
|
|
/***************************************************************************//**
|
|
|
|
|
|
* @test verify a minimalistic text substitution engine with flexible
|
|
|
|
|
|
* data binding, used for tool integration and script generation
|
|
|
|
|
|
* - cover the core parsing and templating functionality,
|
|
|
|
|
|
* using a direct binding
|
|
|
|
|
|
* - demonstrate the default-binding for a data map
|
|
|
|
|
|
* - cover the binding to Lumiera's »External Tree Description«
|
|
|
|
|
|
* @see text-template.hpp
|
|
|
|
|
|
* @see GnuplotGen_test
|
|
|
|
|
|
*/
|
|
|
|
|
|
class TextTemplate_test : public Test
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
virtual void
|
|
|
|
|
|
run (Arg)
|
|
|
|
|
|
{
|
2024-03-24 23:05:16 +01:00
|
|
|
|
simpeUsage();
|
2024-03-22 01:35:31 +01:00
|
|
|
|
verify_parsing();
|
2024-03-18 22:32:49 +01:00
|
|
|
|
verify_instantiation();
|
|
|
|
|
|
verify_conditional();
|
|
|
|
|
|
verify_iteration();
|
|
|
|
|
|
verify_Map_binding();
|
|
|
|
|
|
verify_ETD_binding();
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-03-28 01:12:55 +01:00
|
|
|
|
/** @test simple point-and-shot usage... */
|
2024-03-18 22:32:49 +01:00
|
|
|
|
void
|
|
|
|
|
|
simpeUsage()
|
|
|
|
|
|
{
|
2024-03-19 02:42:43 +01:00
|
|
|
|
MapS snaps{{"whatever", "cruel world"}
|
|
|
|
|
|
,{"greeting", "farewell"}};
|
|
|
|
|
|
CHECK (TextTemplate::apply ("${greeting} ${whatever} ↯", snaps)
|
|
|
|
|
|
== "farewell cruel world ↯"_expect);
|
2024-03-18 22:32:49 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-03-24 23:05:16 +01:00
|
|
|
|
/** @test parsing of tag markup and compilation into a sequence of Action-codes
|
2024-03-22 01:35:31 +01:00
|
|
|
|
* @note the regular expression \ref ACCEPT_FIELD is comprised of several
|
|
|
|
|
|
* alternatives and optional parts, which are marked by 5 sub-expressions
|
2024-03-22 17:50:26 +01:00
|
|
|
|
* - 1 ≙ an escaped field (which should not be processed)
|
|
|
|
|
|
* - 2 ≙ else token (which must be solitary)
|
|
|
|
|
|
* - 3 ≙ end token
|
|
|
|
|
|
* - 4 ≙ some logic token ("if" or "for")
|
|
|
|
|
|
* - 5 ≙ a key or key path
|
2024-03-22 01:35:31 +01:00
|
|
|
|
*/
|
|
|
|
|
|
void
|
|
|
|
|
|
verify_parsing()
|
|
|
|
|
|
{
|
|
|
|
|
|
smatch mat;
|
|
|
|
|
|
string input;
|
|
|
|
|
|
CHECK (not regex_search (input, mat, ACCEPT_MARKUP));
|
|
|
|
|
|
|
|
|
|
|
|
input = " Hallelujah ";
|
2024-03-22 17:50:26 +01:00
|
|
|
|
CHECK (not regex_search (input, mat, ACCEPT_MARKUP)); // walk away ... nothing to see here...
|
2024-03-22 01:35:31 +01:00
|
|
|
|
|
2024-03-22 17:50:26 +01:00
|
|
|
|
input = " stale${beer}forever";
|
2024-03-22 01:35:31 +01:00
|
|
|
|
CHECK (regex_search (input, mat, ACCEPT_MARKUP));
|
|
|
|
|
|
CHECK (mat.position() == 6);
|
|
|
|
|
|
CHECK (mat.length() == 7);
|
|
|
|
|
|
CHECK (mat.prefix() == " stale"_expect);
|
2024-03-22 17:50:26 +01:00
|
|
|
|
CHECK (mat.suffix() == "forever"_expect);
|
2024-03-24 23:05:16 +01:00
|
|
|
|
CHECK (mat[0] == "${beer}"_expect); // so this first example demonstrates placeholder recognition
|
2024-03-22 17:50:26 +01:00
|
|
|
|
CHECK (not mat[1].matched); // Sub-1 : this is not an escaped pattern
|
|
|
|
|
|
CHECK (not mat[2].matched); // Sub-2 : this pattern does not start with "else"
|
|
|
|
|
|
CHECK (not mat[3].matched); // Sub-3 : no "end" keyword
|
|
|
|
|
|
CHECK (not mat[4].matched); // Sub-4 : no further logic syntax
|
|
|
|
|
|
CHECK (mat[5] == "beer"_expect); // Sub-5 : extracts the Key ID
|
2024-03-22 01:35:31 +01:00
|
|
|
|
|
|
|
|
|
|
input = " watch ${for stale}${beer} whatever ";
|
|
|
|
|
|
CHECK (regex_search (input, mat, ACCEPT_MARKUP));
|
|
|
|
|
|
CHECK (mat.position() == 7);
|
|
|
|
|
|
CHECK (mat.length() == 12);
|
|
|
|
|
|
CHECK (mat.prefix() == " watch "_expect);
|
2024-03-22 17:50:26 +01:00
|
|
|
|
CHECK (mat.suffix() == "${beer} whatever "_expect); // (performing only one search here...)
|
|
|
|
|
|
CHECK (mat[0] == "${for stale}"_expect); // Matched a regular opening iteration tag
|
|
|
|
|
|
CHECK (not mat[2].matched); // Sub-2 does not trigger, since there is no "else" mark
|
|
|
|
|
|
CHECK (not mat[3].matched); // Sub-3 does not trigger, no end mark either
|
|
|
|
|
|
CHECK (mat[4] == "for"_expect); // Sub-4 picks the "for" keyword
|
|
|
|
|
|
CHECK (mat[5] == "stale"_expect); // Sub-5 extracts a simple Key ≡ "stale"
|
2024-03-22 01:35:31 +01:00
|
|
|
|
|
2024-03-22 17:50:26 +01:00
|
|
|
|
input = " work ${ end if beer \t } however ";
|
2024-03-22 01:35:31 +01:00
|
|
|
|
CHECK (regex_search (input, mat, ACCEPT_MARKUP));
|
|
|
|
|
|
CHECK (mat.position() == 6);
|
2024-03-22 17:50:26 +01:00
|
|
|
|
CHECK (mat.length() == 19);
|
2024-03-22 01:35:31 +01:00
|
|
|
|
CHECK (mat.prefix() == " work "_expect);
|
|
|
|
|
|
CHECK (mat.suffix() == " however "_expect);
|
2024-03-22 17:50:26 +01:00
|
|
|
|
CHECK (mat[0] == "${ end if beer \t }"_expect); // A regular end marker of an conditional
|
|
|
|
|
|
CHECK (mat[3] == "end "_expect); // Sub-3 triggers on the "end" token
|
|
|
|
|
|
CHECK (mat[4] == "if"_expect); // Sub-4 picks the "if" keyword
|
|
|
|
|
|
CHECK (mat[5] == "beer"_expect); // Sub-5 extracts a simple Key ≡ "beer"
|
2024-03-22 01:35:31 +01:00
|
|
|
|
|
2024-03-22 17:50:26 +01:00
|
|
|
|
input = " catch ${endgame stale}${endfor brown.beer} ever ";
|
2024-03-22 01:35:31 +01:00
|
|
|
|
CHECK (regex_search (input, mat, ACCEPT_MARKUP));
|
2024-03-22 17:50:26 +01:00
|
|
|
|
CHECK (mat.position() == 23);
|
2024-03-22 01:35:31 +01:00
|
|
|
|
CHECK (mat.length() == 20);
|
2024-03-22 17:50:26 +01:00
|
|
|
|
CHECK (mat.prefix() == " catch ${endgame stale}"_expect);// "while" is no valid keyword at the second position of the syntax
|
2024-03-22 01:35:31 +01:00
|
|
|
|
CHECK (mat.suffix() == " ever "_expect);
|
2024-03-22 17:50:26 +01:00
|
|
|
|
CHECK (mat[0] == "${endfor brown.beer}"_expect); // ...thus search proceeds to match on the second pattern installment
|
|
|
|
|
|
CHECK (mat[3] == "end"_expect); // Sub-3 triggers on the "end" token
|
|
|
|
|
|
CHECK (mat[4] == "for"_expect); // Sub-4 picks the "for" keyword
|
|
|
|
|
|
CHECK (mat[5] == "brown.beer"_expect); // Sub-5 extracts a hierarchical key ID
|
2024-03-22 01:35:31 +01:00
|
|
|
|
|
|
|
|
|
|
input = " catch ${else} ever ";
|
|
|
|
|
|
CHECK (regex_search (input, mat, ACCEPT_MARKUP));
|
|
|
|
|
|
CHECK (mat.position() == 7);
|
|
|
|
|
|
CHECK (mat.length() == 7);
|
|
|
|
|
|
CHECK (mat.prefix() == " catch "_expect);
|
|
|
|
|
|
CHECK (mat.suffix() == " ever "_expect);
|
2024-03-22 17:50:26 +01:00
|
|
|
|
CHECK (mat[0] == "${else}"_expect); // Standard match on an "else"-tag
|
|
|
|
|
|
CHECK (mat[2] == "else"_expect); // Sub-2 confirmed a solitary "else" keyword
|
|
|
|
|
|
CHECK (not mat[1].matched);
|
|
|
|
|
|
CHECK (not mat[3].matched);
|
|
|
|
|
|
CHECK (not mat[4].matched);
|
|
|
|
|
|
CHECK (not mat[5].matched);
|
2024-03-22 01:35:31 +01:00
|
|
|
|
|
|
|
|
|
|
input = " catch ${else if} fever \\${can.beer} ";
|
|
|
|
|
|
CHECK (regex_search (input, mat, ACCEPT_MARKUP));
|
|
|
|
|
|
CHECK (mat.position() == 24);
|
|
|
|
|
|
CHECK (mat.length() == 2);
|
2024-03-22 17:50:26 +01:00
|
|
|
|
CHECK (mat.prefix() == " catch ${else if} fever "_expect); // Note: first pattern does not match as "else" must be solitary
|
2024-03-24 23:05:16 +01:00
|
|
|
|
CHECK (mat.suffix() == "{can.beer} "_expect); // Note: the following braced expression is tossed aside
|
2024-03-22 17:50:26 +01:00
|
|
|
|
CHECK (mat[0] == "\\$"_expect); // Only the escaped pattern mark opening is picked up
|
2024-03-22 01:35:31 +01:00
|
|
|
|
CHECK (not mat[2].matched);
|
|
|
|
|
|
CHECK (not mat[3].matched);
|
|
|
|
|
|
CHECK (not mat[4].matched);
|
2024-03-22 17:50:26 +01:00
|
|
|
|
CHECK (not mat[5].matched);
|
|
|
|
|
|
CHECK (mat[1] == "\\$"_expect); // Sub-1 picks the escaped mark (and the remainder is no complete tag)
|
2024-03-23 02:54:55 +01:00
|
|
|
|
|
|
|
|
|
|
|
2024-03-24 23:05:16 +01:00
|
|
|
|
|
2024-03-23 02:54:55 +01:00
|
|
|
|
// Demonstration: can use this regular expression in a matching pipeline....
|
|
|
|
|
|
input = "one ${two} three \\${four} ${if high} five";
|
|
|
|
|
|
CHECK (util::join(
|
|
|
|
|
|
explore (util::RegexSearchIter{input, ACCEPT_MARKUP})
|
|
|
|
|
|
.transform ([](smatch mat){ return mat.str(); }))
|
|
|
|
|
|
==
|
|
|
|
|
|
"${two}, \\$, ${if high}"_expect);
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-03-23 23:47:30 +01:00
|
|
|
|
// Parse matches of this regexp into well defined syntax elements
|
2024-03-28 01:12:55 +01:00
|
|
|
|
auto parser = text_template::parse (input);
|
2024-03-23 23:47:30 +01:00
|
|
|
|
CHECK (not isnil(parser));
|
|
|
|
|
|
CHECK (parser->syntax == TagSyntax::KEYID);
|
|
|
|
|
|
CHECK (parser->lead == "one "_expect);
|
2024-03-25 15:44:48 +01:00
|
|
|
|
CHECK (parser->key == "two"_expect); // extract "two" as key for data lookup
|
2024-03-23 23:47:30 +01:00
|
|
|
|
++parser;
|
|
|
|
|
|
CHECK (parser);
|
|
|
|
|
|
CHECK (parser->syntax == TagSyntax::ESCAPE);
|
|
|
|
|
|
CHECK (parser->lead == " three "_expect);
|
2024-03-25 15:44:48 +01:00
|
|
|
|
CHECK (parser->key == ""_expect); // empty since this tag has been escaped
|
2024-03-23 23:47:30 +01:00
|
|
|
|
++parser;
|
|
|
|
|
|
CHECK (parser);
|
|
|
|
|
|
CHECK (parser->syntax == TagSyntax::IF);
|
2024-03-25 15:44:48 +01:00
|
|
|
|
CHECK (parser->lead == "${four} "_expect); // note: leading escape sign removed
|
|
|
|
|
|
CHECK (parser->key == "high"_expect); // key ≡ "high" used to evaluate conditional
|
2024-03-23 23:47:30 +01:00
|
|
|
|
++parser;
|
2024-03-25 15:44:48 +01:00
|
|
|
|
CHECK (isnil (parser)); // note: the /parser/ stops right behind last token
|
2024-03-23 23:47:30 +01:00
|
|
|
|
VERIFY_ERROR (ITER_EXHAUST, *parser);
|
|
|
|
|
|
VERIFY_ERROR (ITER_EXHAUST, ++parser);
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-03-24 23:05:16 +01:00
|
|
|
|
|
2024-03-23 23:47:30 +01:00
|
|
|
|
// Generate sequence of Action tokens from parsing results
|
|
|
|
|
|
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...
|
|
|
|
|
|
)~";
|
2024-03-24 23:05:16 +01:00
|
|
|
|
auto actions = TextTemplate::compile (input);
|
2024-03-24 21:42:38 +01:00
|
|
|
|
CHECK (25 == actions.size());
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (actions[ 0].code == TextTemplate::Code::TEXT);
|
|
|
|
|
|
CHECK (actions[ 0].val == "\n Prefix-1 "_expect); // static text prefix
|
|
|
|
|
|
CHECK (actions[ 0].refIDX == 0);
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (actions[ 1].code == TextTemplate::Code::KEY); // a placeholder to be substituted
|
|
|
|
|
|
CHECK (actions[ 1].val == "some.key"_expect); // use "some.key" for data retrieval
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (actions[ 2].code == TextTemplate::Code::TEXT); // static text between active fields
|
|
|
|
|
|
CHECK (actions[ 2].val == " next one is "_expect);
|
|
|
|
|
|
|
2024-03-25 15:44:48 +01:00
|
|
|
|
CHECK (actions[ 3].code == TextTemplate::Code::TEXT); // since next tag was escaped, it appears in static segment,
|
|
|
|
|
|
CHECK (actions[ 3].val == "${escaped}\n Prefix-2 "_expect); // yet without the leading escape, which has been absorbed.
|
2024-03-24 21:42:38 +01:00
|
|
|
|
|
|
|
|
|
|
CHECK (actions[ 4].code == TextTemplate::Code::COND); // start of an if-bracket construct
|
|
|
|
|
|
CHECK (actions[ 4].val == "cond1"_expect); // data marked with "cond1" will be used to determine true/false
|
|
|
|
|
|
CHECK (actions[ 4].refIDX == 7 ); // IDX ≡ 7 marks start of the else-branch
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (actions[ 5].code == TextTemplate::Code::TEXT); // this static block will only be included if "cond1" evaluates to true
|
|
|
|
|
|
CHECK (actions[ 5].val == " active "_expect);
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (actions[ 6].code == TextTemplate::Code::JUMP); // unconditional jump at the end of the if-true-block
|
|
|
|
|
|
CHECK (actions[ 6].val == ""_expect);
|
|
|
|
|
|
CHECK (actions[ 6].refIDX == 8 ); // IDX ≡ 8 points to the next element after the conditional construct
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (actions[ 7].code == TextTemplate::Code::TEXT); // this static (else)-block will be included if "cond1" does not hold
|
|
|
|
|
|
CHECK (actions[ 7].val == " inactive "_expect);
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (actions[ 8].code == TextTemplate::Code::TEXT); // again a static segment, displayed unconditionally
|
|
|
|
|
|
CHECK (actions[ 8].val == "Prefix-3 "_expect); // Note: no newline, since the closing bracket was placed at line start
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (actions[ 9].code == TextTemplate::Code::COND); // again a conditional (but this time without else-branch)
|
|
|
|
|
|
CHECK (actions[ 9].val == "cond2"_expect); // data marked with "cond2" will be evaluated as condition
|
|
|
|
|
|
CHECK (actions[ 9].refIDX == 11 ); // IDX ≡ 11 is the alternative route, this time pointing behind the conditional
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (actions[10].code == TextTemplate::Code::TEXT); // static text block to be displayed as content of the conditional
|
|
|
|
|
|
CHECK (actions[10].val == " active2"_expect);
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (actions[11].code == TextTemplate::Code::TEXT); // again an unconditional static segment (behind end of preceding conditional)
|
|
|
|
|
|
CHECK (actions[11].val == " more\n Prefix-4 "_expect);
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (actions[12].code == TextTemplate::Code::ITER); // Start of a for-construct (iteration)
|
|
|
|
|
|
CHECK (actions[12].val == "data"_expect); // data marked with "data" will be used to find and iterate nested elements
|
|
|
|
|
|
CHECK (actions[12].refIDX == 23 ); // IDX ≡ 23 points to the alternative "else" block, in case no iteration takes place
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (actions[13].code == TextTemplate::Code::TEXT); // static block to appear for each nested "data" element
|
|
|
|
|
|
CHECK (actions[13].val == " fixed "_expect);
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (actions[14].code == TextTemplate::Code::KEY); // placeholder to be substituted
|
|
|
|
|
|
CHECK (actions[14].val == "embedded"_expect); // _typically_ the data "embedded" will live in the iterated, nested elements
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (actions[15].code == TextTemplate::Code::TEXT); // again a static block, which however lives within the iterated segment
|
|
|
|
|
|
CHECK (actions[15].val == "\n Pre-5 "_expect);
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (actions[16].code == TextTemplate::Code::COND); // a nested conditional, thus nested on second level within the iteration construct
|
|
|
|
|
|
CHECK (actions[16].val == "nested"_expect); // data marked with "nested" will control the conditional (typically from iterated data elements)
|
|
|
|
|
|
CHECK (actions[16].refIDX == 19 ); // IDX ≡ 19 points to the else-block of this nested conditional
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (actions[17].code == TextTemplate::Code::TEXT); // static content to appear as nested if-true-section
|
|
|
|
|
|
CHECK (actions[17].val == "nested-active"_expect);
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (actions[18].code == TextTemplate::Code::JUMP); // jump code at end of the true-section
|
|
|
|
|
|
CHECK (actions[18].val == ""_expect);
|
|
|
|
|
|
CHECK (actions[18].refIDX == 20 ); // IDX ≡ 20 points behind the end of this nested conditional construct
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (actions[19].code == TextTemplate::Code::TEXT); // static content comprising the else-section
|
|
|
|
|
|
CHECK (actions[19].val == "nested-inactive"_expect); // Note: no whitespace due to placement of the tag brackets of "else" / "end if"
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (actions[20].code == TextTemplate::Code::TEXT); // again an unconditional static segment, yet still within the looping construct
|
|
|
|
|
|
CHECK (actions[20].val == "loop-suffix"_expect);
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (actions[21].code == TextTemplate::Code::LOOP); // the loop-end code, where evaluation will consider the next iteration
|
|
|
|
|
|
CHECK (actions[21].val == ""_expect);
|
|
|
|
|
|
CHECK (actions[21].refIDX == 12 ); // IDX ≡ 12 points back to the opening ITER code
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (actions[22].code == TextTemplate::Code::JUMP); // if however the iteration is complete, evaluation will jump over the "else" section
|
|
|
|
|
|
CHECK (actions[22].val == ""_expect);
|
|
|
|
|
|
CHECK (actions[22].refIDX == 24 );
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (actions[23].code == TextTemplate::Code::TEXT); // this static else-segment will appear whenever no iteration takes place
|
|
|
|
|
|
CHECK (actions[23].val == ""_expect); // Note: in this example there is an ${else}-tag, yet the content is empty
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (actions[24].code == TextTemplate::Code::TEXT); // a final static segment after the last active tag
|
|
|
|
|
|
CHECK (actions[24].val == " tail...\n"_expect);
|
|
|
|
|
|
CHECK (actions[24].refIDX == 0);
|
2024-03-25 00:38:35 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
VERIFY_FAIL ("TextTemplate spec without active placeholders"
|
Library: prepare for a ETD binding
Document existing data binding logic and investigate in detail
what must be done to enable a similar binding backed by Lumiera's ETD structures.
This analysis highlights some tricky aspects, which can be accommodated by
slight adjustments and generalisations in the `TextTemplate` implementation
* `GenNode` is not structured string data, rather binary data
* thus exposing a std::string_view is not adequate, requiring to
pick up the result type from the actual data binding
* moreover, to allow for arbitrary nested scopes, a back-pointer
to the parent scope must be maintained, which requires stable memory locations.
This can best be solved within the InstanceCore itself, which manages
the actual hierarchy of data source references.
* the existing code happens already to fulfil this requirement, but
for sake of clarity, handling of such a nested scope is now extracted
into a dedicated operation, to highlight the guaranteed memory layout.
2024-03-26 19:25:35 +01:00
|
|
|
|
, TextTemplate::compile("O tempora O mores"));
|
2024-03-25 00:38:35 +01:00
|
|
|
|
|
|
|
|
|
|
VERIFY_FAIL ("Tag without key: ...horror ${<placeholder> |↯|}"
|
|
|
|
|
|
, TextTemplate::compile("horror ${ } vacui"));
|
|
|
|
|
|
|
|
|
|
|
|
VERIFY_FAIL (" ...horror ${if <conditional> |↯|}"
|
|
|
|
|
|
, TextTemplate::compile("horror ${if} late"));
|
|
|
|
|
|
|
|
|
|
|
|
VERIFY_FAIL (" ...horror ${for <data-id> |↯|}"
|
|
|
|
|
|
, TextTemplate::compile("horror ${for} all"));
|
|
|
|
|
|
|
|
|
|
|
|
VERIFY_FAIL ("Misplaced ...horror |↯|${else}"
|
|
|
|
|
|
, TextTemplate::compile("horror ${else} deaf"));
|
|
|
|
|
|
|
|
|
|
|
|
VERIFY_FAIL ("unqualified \"end\" without logic-keyword"
|
|
|
|
|
|
, TextTemplate::compile("horror without ${end}"));
|
|
|
|
|
|
|
|
|
|
|
|
VERIFY_FAIL ("Unbalanced Logic: expect ${end ?? } -- found ...horror ${end |↯|for }"
|
|
|
|
|
|
, TextTemplate::compile("horror ${end for} ever"));
|
|
|
|
|
|
|
2024-03-26 00:21:19 +01:00
|
|
|
|
VERIFY_FAIL ("Unbalanced Logic: expect ${end for free} -- found ... horror ${end |↯|if }"
|
|
|
|
|
|
, TextTemplate::compile("${for free} horror ${end if}"));
|
2024-03-25 00:38:35 +01:00
|
|
|
|
|
|
|
|
|
|
VERIFY_FAIL ("Unbalanced Logic: expect ${end for free} -- found ... yet ${end |↯|for me}"
|
|
|
|
|
|
, TextTemplate::compile("${if wee} horror ${for free} yet ${end for me}"));
|
|
|
|
|
|
|
|
|
|
|
|
VERIFY_FAIL ("Conflicting ... precipitous ${else} ⟷ ... callous |↯|${else}"
|
|
|
|
|
|
, TextTemplate::compile("${if smarmy} precipitous ${else} callous ${else} horror"));
|
2024-03-26 02:29:24 +01:00
|
|
|
|
|
|
|
|
|
|
VERIFY_FAIL ("Unclosed Logic tags: |↯|${end if sleazy} missing"
|
|
|
|
|
|
, TextTemplate::compile("${if sleazy} precipitous ${else} horror"));
|
|
|
|
|
|
|
|
|
|
|
|
VERIFY_FAIL ("Unclosed Logic tags: |↯|${end for horror} missing"
|
|
|
|
|
|
, TextTemplate::compile("${for horror}${if flimsy} atrocious ${end if} precipitous"));
|
2024-03-22 01:35:31 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-03-25 17:25:54 +01:00
|
|
|
|
|
2024-03-28 01:12:55 +01:00
|
|
|
|
/** @test Compile a template and instantiate with various data bindings. */
|
2024-03-18 22:32:49 +01:00
|
|
|
|
void
|
|
|
|
|
|
verify_instantiation()
|
|
|
|
|
|
{
|
2024-03-25 17:25:54 +01:00
|
|
|
|
string wonder = "${a} / ${b} = (${a} + ${b})/${a} ≕ ${phi}";
|
|
|
|
|
|
TextTemplate temple{wonder};
|
|
|
|
|
|
CHECK (join(temple.keys()) == "a, b, a, b, a, phi"_expect);
|
|
|
|
|
|
|
|
|
|
|
|
auto insta = temple.submit (string{"phi=Φ, b=b, a=a"});
|
|
|
|
|
|
CHECK (not isnil(insta));
|
|
|
|
|
|
CHECK (join(insta,"⁐") == "⁐a⁐ / ⁐b⁐ = (⁐a⁐ + ⁐b⁐)/⁐a⁐ ≕ ⁐Φ⁐"_expect);
|
2024-03-26 02:29:24 +01:00
|
|
|
|
|
|
|
|
|
|
CHECK (temple.render("phi=Φ,a=μ,b=ν") == "μ / ν = (μ + ν)/μ ≕ Φ"_expect );
|
|
|
|
|
|
CHECK (temple.render("phi=schmuh,a=8,b=5") == "8 / 5 = (8 + 5)/8 ≕ schmuh"_expect);
|
|
|
|
|
|
CHECK (temple.render("phi=1.6180,a=55,b=34") == "55 / 34 = (55 + 34)/55 ≕ 1.6180"_expect);
|
2024-03-18 22:32:49 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-03-26 02:29:24 +01:00
|
|
|
|
/** @test Segments of the text-template can be included
|
2024-03-28 01:12:55 +01:00
|
|
|
|
* conditionally, based on interpretation of a controlling key.
|
2024-03-18 22:32:49 +01:00
|
|
|
|
*/
|
|
|
|
|
|
void
|
|
|
|
|
|
verify_conditional()
|
|
|
|
|
|
{
|
2024-03-26 02:29:24 +01:00
|
|
|
|
TextTemplate t1{"Value ${if val}= ${val} ${else}missing${endif}..."};
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (t1.render("val=55") == "Value = 55 ..."_expect );
|
|
|
|
|
|
CHECK (t1.render("val=\"\"") == "Value missing..."_expect); // empty value counts as false
|
|
|
|
|
|
CHECK (t1.render("val=\" \"") == "Value = ..."_expect ); // one space counts as content (=true)
|
|
|
|
|
|
CHECK (t1.render("val=false") == "Value missing..."_expect); // various bool-false tokens recognised
|
|
|
|
|
|
CHECK (t1.render("val=NO" ) == "Value missing..."_expect);
|
|
|
|
|
|
CHECK (t1.render("val= 0 " ) == "Value missing..."_expect);
|
|
|
|
|
|
CHECK (t1.render("val=true") == "Value = true ..."_expect); // bool true token treated as content
|
|
|
|
|
|
CHECK (t1.render("vol=high") == "Value missing..."_expect); // missing key treated as false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TextTemplate t2{"Solution${if val} is ${val} ${endif val}..."};
|
|
|
|
|
|
CHECK (t2.render("val=42") == "Solution is 42 ..."_expect );
|
|
|
|
|
|
CHECK (t2.render("nil=42") == "Solution..."_expect );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TextTemplate t3{" 1 ${if a} 2 ${if b} 3 ${else} ${b} ${endif b} 4 ${else}${if a} 5 ${else} ${a} ${endif a}${endif a} 6 "};
|
|
|
|
|
|
CHECK (t3.render("a=2,b=3") == " 1 2 3 4 6 "_expect ); // ^^^^^ Note can never be true here
|
|
|
|
|
|
CHECK (t3.render("a=2,b=0") == " 1 2 0 4 6 "_expect );
|
Library: investigate how a »zip iterator« can be built
Basically I am sick of writing for-loops in those cases
where the actual iteration is based on one or several data sources,
and I just need some damn index counter. Nothing against for-loops
in general — they have their valid uses — sometimes a for-loop is KISS
But in these typical cases, an iterator-based solution would be a
one-liner, when also exploiting the structured bindings of C++17
''I must admit that I want this for a loooooong time —''
...but always got intimidated again when thinking through the fine points.
Basically it „should be dead simple“ — as they say
Well — — it ''is'' simple, after getting the nasty aspects of tuple binding
and reference data types out of the way. Yesterday, while writing those
`TestFrame` test cases (which are again an example where you want to iterate
over two word sequences simultaneously and just compare them), I noticed that
last year I learned about the `std::apply`-to-fold-expression trick, and
that this solution pattern could be adapted to construct a tuple directly,
thereby circumventing most of the problems related to ''perfect forwarding''
So now we have a new util function `mapEach` (defined in `tuple-helper.hpp`)
and I have learned how to make this application completely generic.
As a second step, I implemented a proof-of-concept in `IterZip_test`,
which indeed was not really challenging, because the `IterExplorer`
is so very sophisticated by now and handles most cases with transparent
type-driven adaptors. A lot of work went into `IterExplorer` over the years,
and this pays off now.
The solution works as follows:
* apply the `lib::explore()` constructor function to the varargs
* package the resulting `IterExplorer` instantiations into a tuple
* build a »state core« implementation which just lifts out the three
iterator primitives onto this ''product type'' (i.e. the tuple)
* wrap it in yet another `IterExplorer`
* add a transformer function on top to extract a value-tuple for each ''yield'
As expected, works out-of-the-box, with all conceivable variants and wild
mixes of iterators, const, pointers, references, you name it....
PS: I changed the rendering of unsigned types in diagnostic output
to use the short notation, e.g. `uint` instead of `unsigned int`.
This dramatically improves the legibility of verification strings.
2024-11-21 23:30:07 +01:00
|
|
|
|
CHECK (t3.render("a=0,b=3") == " 1 0 6 "_expect ); // thus if a ≙ false we see only 1 ${a} 6
|
2024-03-26 02:29:24 +01:00
|
|
|
|
CHECK (t3.render("a=0,b=0") == " 1 0 6 "_expect );
|
2024-03-18 22:32:49 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-03-26 02:29:24 +01:00
|
|
|
|
|
|
|
|
|
|
/** @test Segments of the text-template can be iterated...
|
|
|
|
|
|
* - there is a control-key to guide the iteration
|
|
|
|
|
|
* - how this key translates into nested data scopes
|
|
|
|
|
|
* is defined by the implementation of the data binding
|
|
|
|
|
|
* - 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,
|
2024-03-28 01:12:55 +01:00
|
|
|
|
* as is demonstrated here with the "x" key at top level
|
2024-03-26 02:29:24 +01:00
|
|
|
|
* - loops and conditionals can be nested
|
2024-03-18 22:32:49 +01:00
|
|
|
|
*/
|
|
|
|
|
|
void
|
|
|
|
|
|
verify_iteration()
|
|
|
|
|
|
{
|
2024-03-26 02:29:24 +01:00
|
|
|
|
TextTemplate t1{"▶${for i} ${x} ▷${else} ∅${end for} ◇ ${i} ▶"};
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (t1.render("i=\"1,2,3\", i.1.x=3, i.2.x=5, i.3.x=8 ") == "▶ 3 ▷ 5 ▷ 8 ▷ ◇ 1,2,3 ▶"_expect ); // fully defined
|
|
|
|
|
|
CHECK (t1.render("i=\"3,1,2\", i.1.x=3, i.2.x=5, i.3.x=8 ") == "▶ 8 ▷ 3 ▷ 5 ▷ ◇ 3,1,2 ▶"_expect ); // order changed
|
|
|
|
|
|
CHECK (t1.render("i=\"3,2,3\", i.1.x=3, i.2.x=5, i.3.x=8 ") == "▶ 8 ▷ 5 ▷ 8 ▷ ◇ 3,2,3 ▶"_expect ); // duplicate entities
|
|
|
|
|
|
CHECK (t1.render("i=\"3,2,1\", i.2.x=5, i.3.x=8 ") == "▶ 8 ▷ 5 ▷ ▷ ◇ 3,2,1 ▶"_expect ); // missing key for entity-1
|
|
|
|
|
|
CHECK (t1.render("i=\"3,2,1\", x=↯, i.2.x=5, i.3.x=8 ") == "▶ 8 ▷ 5 ▷ ↯ ▷ ◇ 3,2,1 ▶"_expect ); // top-level key "x" partially shadowed
|
|
|
|
|
|
CHECK (t1.render("i=\"p,q,r\", x=↯, i.q.x=5, i.3.x=8 ") == "▶ ↯ ▷ 5 ▷ ↯ ▷ ◇ p,q,r ▶"_expect ); // arbitrary names for the entities
|
|
|
|
|
|
CHECK (t1.render("i= 0 , x=↯, i.q.x=5, i.3.x=8 ") == "▶ ∅ ◇ 0 ▶"_expect ); // "0" is false, thus no iteration
|
|
|
|
|
|
CHECK (t1.render(" x=↯, i.q.x=5, i.3.x=8 ") == "▶ ∅ ◇ ▶"_expect ); // no binding for iteration-control key i
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TextTemplate t2{"▶${for i}${if x}${for j}${x}▷${else}${x}●${end for j}${end if x} 🔁 ${end for i} ▶"};
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (t2.render("i=\"1,2\",j=\"1,2\", x=1 , i.1.j.1.x=11, i.1.j.2.x=12, i.2.j.1.x=21, i.2.j.2.x=22") == "▶11▷12▷ 🔁 21▷22▷ 🔁 ▶"_expect );
|
|
|
|
|
|
CHECK (t2.render("i=\"1,2\",j=\"1,2\", i.1.x=1, i.1.j.1.x=11, i.1.j.2.x=12, i.2.j.1.x=21, i.2.j.2.x=22") == "▶11▷12▷ 🔁 🔁 ▶"_expect );
|
|
|
|
|
|
CHECK (t2.render("i=\"1,2\" , x=00 , i.1.j.1.x=11, i.1.j.2.x=12, i.2.j.1.x=21, i.2.j.2.x=22") == "▶00● 🔁 00● 🔁 ▶"_expect );
|
|
|
|
|
|
CHECK (t2.render("i=\"1,2\" , x=00 , i.1.x =10, i.2.x =20, ") == "▶10● 🔁 20● 🔁 ▶"_expect );
|
|
|
|
|
|
CHECK (t2.render(" j=\"1,2\" ") == "▶ ▶"_expect );
|
|
|
|
|
|
CHECK (t2.render(" ") == "▶ ▶"_expect );
|
2024-03-18 22:32:49 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-03-28 01:12:55 +01:00
|
|
|
|
|
|
|
|
|
|
/** @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.
|
2024-03-18 22:32:49 +01:00
|
|
|
|
*/
|
|
|
|
|
|
void
|
|
|
|
|
|
verify_Map_binding()
|
|
|
|
|
|
{
|
2024-03-27 18:26:55 +01:00
|
|
|
|
MapS data{{"a","5"}
|
|
|
|
|
|
,{"i","p,q,r"}
|
|
|
|
|
|
,{"i.p.a","11"}
|
|
|
|
|
|
,{"i.q.a","22"}
|
|
|
|
|
|
,{"i.q.aa","222"}};
|
|
|
|
|
|
|
2024-03-28 01:12:55 +01:00
|
|
|
|
auto binding = text_template::DataSource{data};
|
|
|
|
|
|
CHECK (meta::typeStr(binding) == "text_template::DataSource<map<string, string>, void>"_expect );
|
2024-03-27 18:26:55 +01:00
|
|
|
|
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 );
|
2024-03-28 01:12:55 +01:00
|
|
|
|
CHECK (not binding.isSubScope());
|
2024-03-27 18:26:55 +01:00
|
|
|
|
|
|
|
|
|
|
auto it = binding.getSequence("i");
|
|
|
|
|
|
CHECK (it);
|
|
|
|
|
|
CHECK (*it == "i.p."_expect );
|
2024-11-26 22:15:33 +01:00
|
|
|
|
CHECK (meta::typeStr(it) == "IterExplorer<IterableDecorator<CheckedCore<iter_explorer::Transformer<iter_explorer::BaseAdapter<RegexSearchIter>, string> > > >"_expect );
|
2024-03-27 18:26:55 +01:00
|
|
|
|
|
|
|
|
|
|
auto subBind = binding.openContext(it);
|
2024-03-28 01:12:55 +01:00
|
|
|
|
CHECK (subBind.isSubScope());
|
2024-03-27 18:26:55 +01:00
|
|
|
|
CHECK ((meta::is_same<decltype(binding),decltype(subBind)>()));
|
|
|
|
|
|
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 );
|
|
|
|
|
|
|
|
|
|
|
|
++it;
|
|
|
|
|
|
CHECK (it);
|
|
|
|
|
|
CHECK (*it == "i.q."_expect );
|
|
|
|
|
|
|
|
|
|
|
|
// Note: existing sub-ctx is not automatically linked to the Iterator
|
|
|
|
|
|
CHECK (subBind.retrieveContent("a") == "11"_expect );
|
|
|
|
|
|
// ...rather need to open a new sub-ctx explicitly
|
|
|
|
|
|
subBind = binding.openContext(it);
|
2024-03-28 01:12:55 +01:00
|
|
|
|
CHECK (subBind.isSubScope());
|
2024-03-27 18:26:55 +01:00
|
|
|
|
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 );
|
|
|
|
|
|
|
|
|
|
|
|
++it;
|
|
|
|
|
|
CHECK (it);
|
|
|
|
|
|
CHECK (*it == "i.r."_expect );
|
|
|
|
|
|
|
|
|
|
|
|
subBind = binding.openContext(it);
|
|
|
|
|
|
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 );
|
|
|
|
|
|
|
|
|
|
|
|
++it;
|
|
|
|
|
|
CHECK (isnil (it));
|
|
|
|
|
|
VERIFY_ERROR (ITER_EXHAUST, *it);
|
2024-03-18 22:32:49 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-03-28 01:12:55 +01:00
|
|
|
|
/** @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.
|
2024-03-18 22:32:49 +01:00
|
|
|
|
*/
|
|
|
|
|
|
void
|
|
|
|
|
|
verify_ETD_binding()
|
|
|
|
|
|
{
|
2024-03-27 18:26:55 +01:00
|
|
|
|
auto root = MakeRec()
|
|
|
|
|
|
.set("a", 5)
|
|
|
|
|
|
.set("i", MakeRec()
|
|
|
|
|
|
.scope( MakeRec()
|
|
|
|
|
|
.set("a", 11)
|
|
|
|
|
|
.genNode()
|
|
|
|
|
|
, MakeRec()
|
|
|
|
|
|
.set("a", 22)
|
|
|
|
|
|
.set("aa", 222)
|
|
|
|
|
|
.genNode()
|
2024-03-28 01:12:55 +01:00
|
|
|
|
, MakeRec()
|
|
|
|
|
|
/*——empty——*/
|
|
|
|
|
|
.genNode()
|
2024-03-27 18:26:55 +01:00
|
|
|
|
))
|
|
|
|
|
|
.genNode();
|
2024-03-28 01:12:55 +01:00
|
|
|
|
|
|
|
|
|
|
auto binding = text_template::DataSource{root};
|
|
|
|
|
|
CHECK (meta::typeStr(binding) == "text_template::DataSource<GenNode, void>"_expect );
|
2024-03-27 18:26:55 +01:00
|
|
|
|
CHECK ( binding.contains("a"));
|
|
|
|
|
|
CHECK (not binding.contains("b"));
|
2024-03-28 01:12:55 +01:00
|
|
|
|
CHECK (binding.retrieveContent("a") == "5"_expect );
|
|
|
|
|
|
CHECK (binding.retrieveContent("i") == "{|{a=11}, {a=22, aa=222}, {}}"_expect );
|
|
|
|
|
|
CHECK (not binding.isSubScope());
|
2024-03-27 18:26:55 +01:00
|
|
|
|
|
|
|
|
|
|
auto it = binding.getSequence("i");
|
|
|
|
|
|
CHECK (it);
|
2024-03-28 01:12:55 +01:00
|
|
|
|
CHECK (renderCompact(*it) == "{a=11}");
|
|
|
|
|
|
CHECK (*it == root.data.get<Rec>().get("i").data.get<Rec>().child(0));
|
2024-03-27 18:26:55 +01:00
|
|
|
|
|
|
|
|
|
|
auto subBind = binding.openContext(it);
|
2024-03-28 01:12:55 +01:00
|
|
|
|
CHECK (subBind.isSubScope());
|
2024-03-27 18:26:55 +01:00
|
|
|
|
CHECK ((meta::is_same<decltype(binding),decltype(subBind)>()));
|
|
|
|
|
|
CHECK ( subBind.contains("a"));
|
|
|
|
|
|
CHECK (not subBind.contains("b"));
|
|
|
|
|
|
CHECK (not subBind.contains("aa"));
|
|
|
|
|
|
CHECK ( subBind.contains("i"));
|
2024-03-28 01:12:55 +01:00
|
|
|
|
CHECK (subBind.retrieveContent("i") == "{|{a=11}, {a=22, aa=222}, {}}"_expect );
|
|
|
|
|
|
CHECK (subBind.retrieveContent("a") == "11"_expect );
|
|
|
|
|
|
|
2024-03-27 18:26:55 +01:00
|
|
|
|
++it;
|
|
|
|
|
|
CHECK (it);
|
2024-03-28 01:12:55 +01:00
|
|
|
|
CHECK (renderCompact(*it) == "{a=22, aa=222}");
|
|
|
|
|
|
CHECK (subBind.retrieveContent("a") == "11"_expect );
|
|
|
|
|
|
|
2024-03-27 18:26:55 +01:00
|
|
|
|
subBind = binding.openContext(it);
|
2024-03-28 01:12:55 +01:00
|
|
|
|
CHECK (subBind.isSubScope());
|
2024-03-27 18:26:55 +01:00
|
|
|
|
CHECK (subBind.contains("a"));
|
|
|
|
|
|
CHECK (subBind.contains("aa"));
|
2024-03-28 01:12:55 +01:00
|
|
|
|
CHECK (subBind.retrieveContent("a") == "22"_expect );
|
|
|
|
|
|
CHECK (subBind.retrieveContent("aa") == "222"_expect);
|
|
|
|
|
|
|
2024-03-27 18:26:55 +01:00
|
|
|
|
++it;
|
|
|
|
|
|
CHECK (it);
|
2024-03-28 01:12:55 +01:00
|
|
|
|
CHECK (renderCompact(*it) == "{}");
|
|
|
|
|
|
|
2024-03-27 18:26:55 +01:00
|
|
|
|
subBind = binding.openContext(it);
|
|
|
|
|
|
CHECK ( subBind.contains("a"));
|
|
|
|
|
|
CHECK (not subBind.contains("aa"));
|
2024-03-28 01:12:55 +01:00
|
|
|
|
CHECK (subBind.retrieveContent("a") == "5"_expect );
|
|
|
|
|
|
|
2024-03-27 18:26:55 +01:00
|
|
|
|
++it;
|
|
|
|
|
|
CHECK (isnil (it));
|
|
|
|
|
|
VERIFY_ERROR (ITER_EXHAUST, *it);
|
2024-03-28 01:12:55 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
2024-03-18 22:32:49 +01:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
LAUNCHER (TextTemplate_test, "unit common");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}} // namespace lib::test
|