From 3a593f5f712d94915c6b324869c79d414dccf074 Mon Sep 17 00:00:00 2001 From: Flex Ferrum Date: Thu, 5 Jul 2018 15:06:55 +0300 Subject: [PATCH] Implement 'for' loops with 'if' condition --- src/expression_parser.cpp | 15 +++++++----- src/expression_parser.h | 2 +- src/internal_value.h | 5 ++++ src/statements.cpp | 22 +++++++++++++++++ src/statements.h | 4 ++- src/template_parser.cpp | 8 ++++-- test/forloop_test.cpp | 51 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 97 insertions(+), 10 deletions(-) diff --git a/src/expression_parser.cpp b/src/expression_parser.cpp index 02da556..0a1b521 100644 --- a/src/expression_parser.cpp +++ b/src/expression_parser.cpp @@ -21,7 +21,7 @@ RendererPtr ExpressionParser::Parse(LexScanner& lexer) return result; } -ExpressionEvaluatorPtr ExpressionParser::ParseFullExpression(LexScanner &lexer) +ExpressionEvaluatorPtr ExpressionParser::ParseFullExpression(LexScanner &lexer, bool includeIfPart) { ExpressionEvaluatorPtr result; LexScanner::StateSaver saver(lexer); @@ -45,11 +45,14 @@ ExpressionEvaluatorPtr ExpressionParser::ParseFullExpre ExpressionEvaluatorPtr ifExpr; if (lexer.PeekNextToken() == Token::If) { - lexer.EatToken(); - ifExpr = ParseIfExpression(lexer); - if (!ifExpr) - return result; - evaluator->SetTester(ifExpr); + if (includeIfPart) + { + lexer.EatToken(); + ifExpr = ParseIfExpression(lexer); + if (!ifExpr) + return result; + evaluator->SetTester(ifExpr); + } } saver.Commit(); diff --git a/src/expression_parser.h b/src/expression_parser.h index 6e371ac..35ec432 100644 --- a/src/expression_parser.h +++ b/src/expression_parser.h @@ -12,7 +12,7 @@ class ExpressionParser public: ExpressionParser(); RendererPtr Parse(LexScanner& lexer); - ExpressionEvaluatorPtr ParseFullExpression(LexScanner& lexer); + ExpressionEvaluatorPtr ParseFullExpression(LexScanner& lexer, bool includeIfPart = true); private: ExpressionEvaluatorPtr ParseLogicalNot(LexScanner& lexer); ExpressionEvaluatorPtr ParseLogicalOr(LexScanner& lexer); diff --git a/src/internal_value.h b/src/internal_value.h index a252b39..e260fee 100644 --- a/src/internal_value.h +++ b/src/internal_value.h @@ -71,6 +71,8 @@ class ListAdapter public: ListAdapter() {} explicit ListAdapter(ListAccessorProvider prov) : m_accessorProvider(std::move(prov)) {} + ListAdapter(const ListAdapter&) = default; + ListAdapter(ListAdapter&&) = default; static ListAdapter CreateAdapter(InternalValueList&& values); static ListAdapter CreateAdapter(const GenericList& values); @@ -78,6 +80,9 @@ class ListAdapter static ListAdapter CreateAdapter(GenericList&& values); static ListAdapter CreateAdapter(ValuesList&& values); + ListAdapter& operator = (const ListAdapter&) = default; + ListAdapter& operator = (ListAdapter&&) = default; + size_t GetSize() const { if (m_accessorProvider && m_accessorProvider()) diff --git a/src/statements.cpp b/src/statements.cpp index b8bf68d..17c1e31 100644 --- a/src/statements.cpp +++ b/src/statements.cpp @@ -18,6 +18,28 @@ void ForStatement::Render(OutStream& os, RenderContext& values) if (!isConverted) return; + if (m_ifExpr) + { + auto& tempContext = values.EnterScope(); + InternalValueList newLoopItems; + for (auto& curValue : loopItems) + { + if (m_vars.size() > 1) + { + for (auto& varName : m_vars) + tempContext[varName] = Subscript(curValue, varName); + } + else + tempContext[m_vars[0]] = curValue; + + if (ConvertToBool(m_ifExpr->Evaluate(values))) + newLoopItems.push_back(curValue); + } + values.ExitScope(); + + loopItems = ListAdapter::CreateAdapter(std::move(newLoopItems)); + } + int64_t itemsNum = static_cast(loopItems.GetSize()); loopVar["length"] = InternalValue(itemsNum); bool loopRendered = false; diff --git a/src/statements.h b/src/statements.h index 278c0bd..f2fd300 100644 --- a/src/statements.h +++ b/src/statements.h @@ -19,9 +19,10 @@ using StatementPtr = std::shared_ptr; class ForStatement : public Statement { public: - ForStatement(std::vector vars, ExpressionEvaluatorPtr<> expr) + ForStatement(std::vector vars, ExpressionEvaluatorPtr<> expr, ExpressionEvaluatorPtr<> ifExpr) : m_vars(std::move(vars)) , m_value(expr) + , m_ifExpr(ifExpr) { } @@ -40,6 +41,7 @@ class ForStatement : public Statement private: std::vector m_vars; ExpressionEvaluatorPtr<> m_value; + ExpressionEvaluatorPtr<> m_ifExpr; RendererPtr m_mainBody; RendererPtr m_elseBody; }; diff --git a/src/template_parser.cpp b/src/template_parser.cpp index ab84d71..efc5b81 100644 --- a/src/template_parser.cpp +++ b/src/template_parser.cpp @@ -76,11 +76,15 @@ bool StatementsParser::ParseFor(LexScanner& lexer, StatementInfoList& statements return false; ExpressionParser exprPraser; - auto valueExpr = exprPraser.ParseFullExpression(lexer); + auto valueExpr = exprPraser.ParseFullExpression(lexer, false); if (!valueExpr) return false; - auto renderer = std::make_shared(vars, valueExpr); + ExpressionEvaluatorPtr<> ifExpr; + if (lexer.EatIfEqual(Token::If)) + ifExpr = exprPraser.ParseFullExpression(lexer, false); + + auto renderer = std::make_shared(vars, valueExpr, ifExpr); StatementInfo statementInfo = StatementInfo::Create(StatementInfo::ForStatement, pos); statementInfo.renderer = renderer; statementsInfo.push_back(statementInfo); diff --git a/test/forloop_test.cpp b/test/forloop_test.cpp index 3307881..e48bbf5 100644 --- a/test/forloop_test.cpp +++ b/test/forloop_test.cpp @@ -194,6 +194,57 @@ a[4] = image[b]; EXPECT_EQ(expectedResult, result); } +TEST(ForLoopTest, LoopWithIf) +{ + std::string source = R"( +{% for i in range(10) if i is even %} +a[{{i}}] = image[{{i}}]; +{% endfor %} +)"; + + Template tpl; + ASSERT_TRUE(tpl.Load(source)); + + ValuesMap params = { + }; + + std::string result = tpl.RenderAsString(params); + std::cout << result << std::endl; + std::string expectedResult = R"( +a[0] = image[0]; +a[2] = image[2]; +a[4] = image[4]; +a[6] = image[6]; +a[8] = image[8]; +)"; + EXPECT_EQ(expectedResult, result); +} + +TEST(ForLoopTest, LoopVariableWithIf) +{ + std::string source = R"( +{% for i in its if i is even%} +{{i}} length={{loop.length}}, index={{loop.index}}, index0={{loop.index0}}, first={{loop.first}}, last={{loop.last}}, previtem={{loop.previtem}}, nextitem={{loop.nextitem}}; +{% endfor %} +)"; + + Template mytemplate; + ASSERT_TRUE(mytemplate.Load(source)); + + ValuesMap params = { + {"its", ValuesList{0, 1, 2, 3, 4} } + }; + + std::string result = mytemplate.RenderAsString(params); + std::cout << result << std::endl; + std::string expectedResult = R"( +0 length=3, index=1, index0=0, first=true, last=false, previtem=, nextitem=2; +2 length=3, index=2, index0=1, first=false, last=false, previtem=0, nextitem=4; +4 length=3, index=3, index0=2, first=false, last=true, previtem=2, nextitem=; +)"; + EXPECT_EQ(expectedResult, result); +} + TEST(ForLoopTest, LoopVariable) { std::string source = R"(