Skip to content

Commit 38bbaca

Browse files
committed
Add handling of global functions and 'loop.cycle' function
1 parent a2d6177 commit 38bbaca

File tree

5 files changed

+278
-0
lines changed

5 files changed

+278
-0
lines changed

src/expression_evaluator.cpp

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#include "testers.h"
44
#include "value_visitors.h"
55

6+
#include <boost/algorithm/string/join.hpp>
7+
68
#include <cmath>
79

810
namespace jinja2
@@ -162,4 +164,150 @@ Value DictionaryCreator::Evaluate(RenderContext& context)
162164
return result;
163165
}
164166

167+
Value CallExpression::Evaluate(RenderContext& values)
168+
{
169+
std::string valueRef = boost::algorithm::join(m_valueRef, ".");
170+
171+
if (valueRef == "range")
172+
return CallGlobalRange(values);
173+
else if (valueRef == "loop.cycle")
174+
return CallLoopCycle(values);
175+
176+
return Value();
177+
}
178+
179+
Value CallExpression::CallGlobalRange(RenderContext& values)
180+
{
181+
auto startExpr = ExpressionEvaluatorPtr<>();
182+
auto stopExpr = ExpressionEvaluatorPtr<>();
183+
auto stepExpr = ExpressionEvaluatorPtr<>();
184+
185+
helpers::FindParam(m_params, helpers::NoPosParam, "start", startExpr);
186+
helpers::FindParam(m_params, helpers::NoPosParam, "stop", stopExpr);
187+
helpers::FindParam(m_params, helpers::NoPosParam, "step", startExpr);
188+
189+
switch (m_params.posParams.size())
190+
{
191+
case 1:
192+
if (startExpr && stopExpr && stepExpr)
193+
break;
194+
else if (startExpr && stopExpr)
195+
stepExpr = m_params.posParams[0];
196+
else if (startExpr && stepExpr)
197+
stopExpr = m_params.posParams[0];
198+
else if (stopExpr && stepExpr)
199+
startExpr = m_params.posParams[0];
200+
else if (startExpr)
201+
stopExpr = m_params.posParams[0];
202+
else if (stopExpr)
203+
startExpr = m_params.posParams[0];
204+
else if (stepExpr)
205+
stopExpr = m_params.posParams[0];
206+
else
207+
stopExpr = m_params.posParams[0];
208+
break;
209+
case 2:
210+
if (startExpr && stopExpr && stepExpr)
211+
break;
212+
else if (startExpr && stopExpr)
213+
break;
214+
else if (startExpr && stepExpr)
215+
break;
216+
else if (stopExpr && stepExpr)
217+
break;
218+
else if (startExpr)
219+
{
220+
stopExpr = m_params.posParams[0];
221+
stepExpr = m_params.posParams[1];
222+
}
223+
else if (stopExpr)
224+
{
225+
startExpr = m_params.posParams[0];
226+
stepExpr = m_params.posParams[1];
227+
}
228+
else if (stepExpr)
229+
{
230+
startExpr = m_params.posParams[0];
231+
stopExpr = m_params.posParams[1];
232+
}
233+
else
234+
{
235+
startExpr = m_params.posParams[0];
236+
stopExpr = m_params.posParams[1];
237+
}
238+
break;
239+
case 3:
240+
if (startExpr || stopExpr || stepExpr)
241+
break;
242+
else
243+
{
244+
startExpr = m_params.posParams[0];
245+
stopExpr = m_params.posParams[1];
246+
stepExpr = m_params.posParams[2];
247+
}
248+
break;
249+
}
250+
251+
Value startVal = startExpr ? startExpr->Evaluate(values) : Value();
252+
Value stopVal = stopExpr ? stopExpr->Evaluate(values) : Value();
253+
Value stepVal = stepExpr ? stepExpr->Evaluate(values) : Value();
254+
255+
int64_t start = boost::apply_visitor(visitors::IntegerEvaluator(), startVal.data());
256+
int64_t stop = boost::apply_visitor(visitors::IntegerEvaluator(), stopVal.data());
257+
int64_t step = boost::apply_visitor(visitors::IntegerEvaluator(), stepVal.data());
258+
259+
if (!stepExpr)
260+
{
261+
step = 1;
262+
}
263+
else
264+
{
265+
if (step == 0)
266+
return Value();
267+
}
268+
269+
class RangeGenerator : public ListItemAccessor
270+
{
271+
public:
272+
RangeGenerator(int64_t start, int64_t stop, int64_t step)
273+
: m_start(start)
274+
, m_stop(stop)
275+
, m_step(step)
276+
{
277+
}
278+
279+
size_t GetSize() const override
280+
{
281+
size_t count = (m_stop - m_start);
282+
return count / m_step;
283+
}
284+
Value GetValueByIndex(int64_t idx) const
285+
{
286+
return m_start + m_step * idx;
287+
}
288+
289+
private:
290+
int64_t m_start;
291+
int64_t m_stop;
292+
int64_t m_step;
293+
};
294+
295+
return GenericList([accessor = RangeGenerator(start, stop, step)]() -> const ListItemAccessor* {return &accessor;});
296+
}
297+
298+
Value CallExpression::CallLoopCycle(RenderContext& values)
299+
{
300+
bool loopFound = false;
301+
auto loopValP = values.FindValue("loop", loopFound);
302+
if (!loopFound)
303+
return Value();
304+
305+
const ValuesMap* loop = boost::get<ValuesMap>(&loopValP->second.data());
306+
int64_t baseIdx = boost::apply_visitor(visitors::IntegerEvaluator(), (*loop).at("index0").data());
307+
auto idx = baseIdx % m_params.posParams.size();
308+
return m_params.posParams[idx]->Evaluate(values);
309+
}
310+
311+
312+
165313
}

src/expression_evaluator.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,31 @@ class IsExpression : public Expression
212212
static std::unordered_map<std::string, TesterFactoryFn> s_testers;
213213
};
214214

215+
class CallExpression : public Expression
216+
{
217+
public:
218+
virtual ~CallExpression() {}
219+
220+
CallExpression(std::vector<std::string> valueRef, CallParams params)
221+
: m_valueRef(std::move(valueRef))
222+
, m_params(std::move(params))
223+
{
224+
}
225+
226+
Value Evaluate(RenderContext &values);
227+
228+
auto& GetValueRef() const {return m_valueRef;}
229+
auto& GetParams() const {return m_params;}
230+
231+
private:
232+
Value CallGlobalRange(RenderContext &values);
233+
Value CallLoopCycle(RenderContext &values);
234+
235+
private:
236+
std::vector<std::string> m_valueRef;
237+
CallParams m_params;
238+
};
239+
215240
class ExpressionFilter
216241
{
217242
public:
@@ -239,6 +264,7 @@ class ExpressionFilter
239264
static std::unordered_map<std::string, FilterFactoryFn> s_filters;
240265
};
241266

267+
242268
class IfExpression
243269
{
244270
public:

src/expression_parser.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,13 @@ ExpressionEvaluatorPtr<Expression> ExpressionParser::ParseCall(LexScanner& lexer
434434
{
435435
ExpressionEvaluatorPtr<Expression> result;
436436

437+
bool isValid = false;
438+
CallParams params = ParseCallParams(lexer, isValid);
439+
if (!isValid)
440+
return result;
441+
442+
result = std::make_shared<CallExpression>(valueRef, std::move(params));
443+
437444
return result;
438445
}
439446

src/value_visitors.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,27 @@ struct BooleanEvaluator : boost::static_visitor<bool>
515515
}
516516
};
517517

518+
struct IntegerEvaluator : public boost::static_visitor<int64_t>
519+
{
520+
int64_t operator ()(int64_t val) const
521+
{
522+
return val;
523+
}
524+
int64_t operator ()(double val) const
525+
{
526+
return static_cast<int64_t>(val);
527+
}
528+
int64_t operator ()(bool val) const
529+
{
530+
return static_cast<int64_t>(val);
531+
}
532+
template<typename U>
533+
int64_t operator()(U&&) const
534+
{
535+
return 0;
536+
}
537+
};
538+
518539
struct StringJoiner : BaseVisitor<>
519540
{
520541
using BaseVisitor::operator ();

test/forloop_test.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,82 @@ a[2] = image[2];
104104
EXPECT_EQ(expectedResult, result);
105105
}
106106

107+
TEST(ForLoopTest, IntegersRangeLoop)
108+
{
109+
std::string source = R"(
110+
{% for i in range(3) %}
111+
a[{{i}}] = image[{{i}}];
112+
{% endfor %}
113+
)";
114+
115+
Template tpl;
116+
ASSERT_TRUE(tpl.Load(source));
117+
118+
ValuesMap params = {
119+
};
120+
121+
std::string result = tpl.RenderAsString(params);
122+
std::cout << result << std::endl;
123+
std::string expectedResult = R"(
124+
a[0] = image[0];
125+
a[1] = image[1];
126+
a[2] = image[2];
127+
)";
128+
EXPECT_EQ(expectedResult, result);
129+
}
130+
131+
TEST(ForLoopTest, LoopCycleLoop)
132+
{
133+
std::string source = R"(
134+
{% for i in range(5) %}
135+
a[{{i}}] = image[{{loop.cycle(2, 4, 6)}}];
136+
{% endfor %}
137+
)";
138+
139+
Template tpl;
140+
ASSERT_TRUE(tpl.Load(source));
141+
142+
ValuesMap params = {
143+
};
144+
145+
std::string result = tpl.RenderAsString(params);
146+
std::cout << result << std::endl;
147+
std::string expectedResult = R"(
148+
a[0] = image[2];
149+
a[1] = image[4];
150+
a[2] = image[6];
151+
a[3] = image[2];
152+
a[4] = image[4];
153+
)";
154+
EXPECT_EQ(expectedResult, result);
155+
}
156+
157+
TEST(ForLoopTest, LoopCycle2Loop)
158+
{
159+
std::string source = R"(
160+
{% for i in range(5) %}
161+
a[{{i}}] = image[{{loop.cycle("a", "b", "c")}}];
162+
{% endfor %}
163+
)";
164+
165+
Template tpl;
166+
ASSERT_TRUE(tpl.Load(source));
167+
168+
ValuesMap params = {
169+
};
170+
171+
std::string result = tpl.RenderAsString(params);
172+
std::cout << result << std::endl;
173+
std::string expectedResult = R"(
174+
a[0] = image[a];
175+
a[1] = image[b];
176+
a[2] = image[c];
177+
a[3] = image[a];
178+
a[4] = image[b];
179+
)";
180+
EXPECT_EQ(expectedResult, result);
181+
}
182+
107183
TEST(ForLoopTest, LoopVariable)
108184
{
109185
std::string source = R"(

0 commit comments

Comments
 (0)