Skip to content

Commit 0836553

Browse files
authored
Implemented 'with' statement (#110)
1 parent 5d0a6c2 commit 0836553

File tree

8 files changed

+213
-7
lines changed

8 files changed

+213
-7
lines changed

src/expression_parser.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class ExpressionParser
4141
ParseResult<ExpressionEvaluatorPtr<IfExpression>> ParseIfExpression(LexScanner& lexer);
4242

4343
private:
44-
ComposedRenderer* m_topLevelRenderer;
44+
ComposedRenderer* m_topLevelRenderer = nullptr;
4545
};
4646

4747
} // jinja2

src/lexer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ struct Token
8686
Recursive,
8787
Scoped,
8888
With,
89+
EndWith,
8990
Without,
9091
Ignore,
9192
Missing,
@@ -164,6 +165,7 @@ enum class Keyword
164165
Recursive,
165166
Scoped,
166167
With,
168+
EndWith,
167169
Without,
168170
Ignore,
169171
Missing,

src/statements.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,4 +639,17 @@ void DoStatement::Render(OutStream& os, RenderContext& values)
639639
{
640640
m_expr->Evaluate(values);
641641
}
642+
643+
void WithStatement::Render(OutStream& os, RenderContext& values)
644+
{
645+
auto innerValues = values.Clone(true);
646+
auto& scope = innerValues.EnterScope();
647+
648+
for (auto& var : m_scopeVars)
649+
scope[var.first] = var.second->Evaluate(values);
650+
651+
m_mainBody->Render(os, innerValues);
652+
653+
innerValues.ExitScope();
654+
}
642655
} // jinja2

src/statements.h

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,13 +328,34 @@ class DoStatement : public Statement
328328
public:
329329
VISITABLE_STATEMENT();
330330

331-
DoStatement(ExpressionEvaluatorPtr <> expr) : m_expr(expr) {}
331+
DoStatement(ExpressionEvaluatorPtr<> expr) : m_expr(expr) {}
332332

333333
void Render(OutStream &os, RenderContext &values) override;
334334

335335
private:
336336
ExpressionEvaluatorPtr<> m_expr;
337337
};
338+
339+
class WithStatement : public Statement
340+
{
341+
public:
342+
VISITABLE_STATEMENT();
343+
344+
void SetScopeVars(std::vector<std::pair<std::string, ExpressionEvaluatorPtr<>>> vars)
345+
{
346+
m_scopeVars = std::move(vars);
347+
}
348+
void SetMainBody(RendererPtr renderer)
349+
{
350+
m_mainBody = renderer;
351+
}
352+
353+
void Render(OutStream &os, RenderContext &values) override;
354+
355+
private:
356+
std::vector<std::pair<std::string, ExpressionEvaluatorPtr<>>> m_scopeVars;
357+
RendererPtr m_mainBody;
358+
};
338359
} // jinja2
339360

340361

src/template_parser.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ StatementsParser::ParseResult StatementsParser::Parse(LexScanner& lexer, Stateme
6767
return MakeParseError(ErrorCode::ExtensionDisabled, tok);
6868
result = ParseDo(lexer, statementsInfo, tok);
6969
break;
70+
case Keyword::With:
71+
result = ParseWith(lexer, statementsInfo, tok);
72+
break;
73+
case Keyword::EndWith:
74+
result = ParseEndWith(lexer, statementsInfo, tok);
75+
break;
7076
case Keyword::Filter:
7177
case Keyword::EndFilter:
7278
case Keyword::EndSet:
@@ -815,4 +821,63 @@ StatementsParser::ParseResult StatementsParser::ParseDo(LexScanner& lexer, State
815821
return jinja2::StatementsParser::ParseResult();
816822
}
817823

824+
StatementsParser::ParseResult StatementsParser::ParseWith(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok)
825+
{
826+
std::vector<std::pair<std::string, ExpressionEvaluatorPtr<>>> vars;
827+
828+
ExpressionParser exprParser(m_settings);
829+
while (lexer.PeekNextToken() == Token::Identifier)
830+
{
831+
auto nameTok = lexer.NextToken();
832+
if (!lexer.EatIfEqual('='))
833+
return MakeParseErrorTL(ErrorCode::ExpectedToken, lexer.PeekNextToken(), '=');
834+
835+
auto expr = exprParser.ParseFullExpression(lexer);
836+
if (!expr)
837+
return expr.get_unexpected();
838+
auto valueExpr = *expr;
839+
840+
vars.emplace_back(AsString(nameTok.value), valueExpr);
841+
842+
if (!lexer.EatIfEqual(','))
843+
break;
844+
}
845+
846+
auto nextTok = lexer.PeekNextToken();
847+
if (vars.empty())
848+
return MakeParseError(ErrorCode::ExpectedIdentifier, nextTok);
849+
850+
if (nextTok != Token::Eof)
851+
return MakeParseErrorTL(ErrorCode::ExpectedToken, nextTok, Token::Eof, ',');
852+
853+
auto renderer = std::make_shared<WithStatement>();
854+
renderer->SetScopeVars(std::move(vars));
855+
StatementInfo statementInfo = StatementInfo::Create(StatementInfo::WithStatement, stmtTok);
856+
statementInfo.renderer = renderer;
857+
statementsInfo.push_back(statementInfo);
858+
859+
return ParseResult();
860+
}
861+
862+
StatementsParser::ParseResult StatementsParser::ParseEndWith(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok)
863+
{
864+
if (statementsInfo.size() <= 1)
865+
return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);
866+
867+
StatementInfo info = statementsInfo.back();
868+
869+
if (info.type != StatementInfo::WithStatement)
870+
{
871+
return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);
872+
}
873+
874+
statementsInfo.pop_back();
875+
auto renderer = static_cast<WithStatement*>(info.renderer.get());
876+
renderer->SetMainBody(info.compositions[0]);
877+
878+
statementsInfo.back().currentComposition->AddRenderer(info.renderer);
879+
880+
return ParseResult();
881+
}
882+
818883
}

src/template_parser.h

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ template<typename T = void>
4949
struct ParserTraitsBase
5050
{
5151
static Token::Type s_keywords[];
52-
static KeywordsInfo s_keywordsInfo[40];
52+
static KeywordsInfo s_keywordsInfo[41];
5353
static std::unordered_map<int, MultiStringLiteral> s_tokens;
5454
};
5555

@@ -166,7 +166,8 @@ struct StatementInfo
166166
BlockStatement,
167167
ParentBlockStatement,
168168
MacroStatement,
169-
MacroCallStatement
169+
MacroCallStatement,
170+
WithStatement
170171
};
171172

172173
using ComposedPtr = std::shared_ptr<ComposedRenderer>;
@@ -224,6 +225,10 @@ class StatementsParser
224225

225226
private:
226227
Settings m_settings;
228+
229+
ParseResult ParseWith(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& token);
230+
231+
ParseResult ParseEndWith(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok);
227232
};
228233

229234
template<typename CharT>
@@ -781,7 +786,7 @@ class TemplateParser : public LexerHelper
781786
};
782787

783788
template<typename T>
784-
KeywordsInfo ParserTraitsBase<T>::s_keywordsInfo[40] = {
789+
KeywordsInfo ParserTraitsBase<T>::s_keywordsInfo[41] = {
785790
{UNIVERSAL_STR("for"), Keyword::For},
786791
{UNIVERSAL_STR("endfor"), Keyword::Endfor},
787792
{UNIVERSAL_STR("in"), Keyword::In},
@@ -815,6 +820,7 @@ KeywordsInfo ParserTraitsBase<T>::s_keywordsInfo[40] = {
815820
{UNIVERSAL_STR("recursive"), Keyword::Recursive},
816821
{UNIVERSAL_STR("scoped"), Keyword::Scoped},
817822
{UNIVERSAL_STR("with"), Keyword::With},
823+
{UNIVERSAL_STR("endwith"), Keyword::EndWith},
818824
{UNIVERSAL_STR("without"), Keyword::Without},
819825
{UNIVERSAL_STR("ignore"), Keyword::Ignore},
820826
{UNIVERSAL_STR("missing"), Keyword::Missing},
@@ -881,6 +887,7 @@ std::unordered_map<int, MultiStringLiteral> ParserTraitsBase<T>::s_tokens = {
881887
{Token::Recursive, UNIVERSAL_STR("recursive")},
882888
{Token::Scoped, UNIVERSAL_STR("scoped")},
883889
{Token::With, UNIVERSAL_STR("with")},
890+
{Token::EndWith, UNIVERSAL_STR("endwith")},
884891
{Token::Without, UNIVERSAL_STR("without")},
885892
{Token::Ignore, UNIVERSAL_STR("ignore")},
886893
{Token::Missing, UNIVERSAL_STR("missing")},

test/errors_test.cpp

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ TEST_P(ErrorsGenericTest, Test)
1919

2020
Template tpl;
2121
auto parseResult = tpl.Load(source);
22-
EXPECT_FALSE(parseResult.has_value());
22+
ASSERT_FALSE(parseResult.has_value());
2323

2424
std::ostringstream errorDescr;
2525
errorDescr << parseResult.error();
@@ -39,7 +39,7 @@ TEST_P(ErrorsGenericExtensionsTest, Test)
3939

4040
Template tpl(&env);
4141
auto parseResult = tpl.Load(source);
42-
EXPECT_FALSE(parseResult.has_value());
42+
ASSERT_FALSE(parseResult.has_value());
4343

4444
std::ostringstream errorDescr;
4545
errorDescr << parseResult.error();
@@ -213,6 +213,7 @@ INSTANTIATE_TEST_CASE_P(StatementsTest_1, ErrorsGenericTest, ::testing::Values(
213213
InputOutputPair{"{% from 'foo' import bar with context, %}",
214214
"noname.j2tpl:1:38: error: Expected end of statement, got: ','\n{% from 'foo' import bar with context, %}\n ---^-------"}
215215
));
216+
216217
INSTANTIATE_TEST_CASE_P(StatementsTest_2, ErrorsGenericTest, ::testing::Values(
217218
InputOutputPair{"{% block %}",
218219
"noname.j2tpl:1:10: error: Identifier expected\n{% block %}\n ---^-------"},
@@ -260,6 +261,24 @@ INSTANTIATE_TEST_CASE_P(StatementsTest_2, ErrorsGenericTest, ::testing::Values(
260261
"noname.j2tpl:1:17: error: Unexpected statement: 'endcall'\n{% block b %}{% endcall %}\n ---^-------"},
261262
InputOutputPair{"{% do 'Hello World' %}",
262263
"noname.j2tpl:1:4: error: Extension disabled\n{% do 'Hello World' %}\n---^-------"},
264+
InputOutputPair{"{% with %}{% endif }",
265+
"noname.j2tpl:1:9: error: Identifier expected\n{% with %}{% endif }\n ---^-------"},
266+
InputOutputPair{"{% with a %}{% endif }",
267+
"noname.j2tpl:1:11: error: Unexpected token '<<End of block>>'. Expected: '='\n{% with a %}{% endif }\n ---^-------"},
268+
InputOutputPair{"{% with a 42 %}{% endif }",
269+
"noname.j2tpl:1:11: error: Unexpected token '42'. Expected: '='\n{% with a 42 %}{% endif }\n ---^-------"},
270+
InputOutputPair{"{% with a = %}{% endif }",
271+
"noname.j2tpl:1:13: error: Unexpected token: '<<End of block>>'\n{% with a = %}{% endif }\n ---^-------"},
272+
InputOutputPair{"{% with a = 42 b = 30 %}{% endif }",
273+
"noname.j2tpl:1:16: error: Unexpected token 'b'. Expected: '<<End of block>>', ','\n{% with a = 42 b = 30 %}{% endif }\n ---^-------"},
274+
InputOutputPair{"{% with a = 42, %}{% endif }",
275+
"noname.j2tpl:1:22: error: Unexpected statement: 'endif'\n{% with a = 42, %}{% endif }\n ---^-------"},
276+
// FIXME: InputOutputPair{"{% with a = 42 %}",
277+
// "noname.j2tpl:1:4: error: Extension disabled\n{% do 'Hello World' %}\n---^-------"},
278+
InputOutputPair{"{% with a = 42 %}{% endfor %}",
279+
"noname.j2tpl:1:21: error: Unexpected statement: 'endfor'\n{% with a = 42 %}{% endfor %}\n ---^-------"},
280+
InputOutputPair{"{% set a = 42 %}{% endwith %}",
281+
"noname.j2tpl:1:20: error: Unexpected statement: 'endwith'\n{% set a = 42 %}{% endwith %}\n ---^-------"},
263282
InputOutputPair{"{{}}",
264283
"noname.j2tpl:1:3: error: Unexpected token: '<<End of block>>'\n{{}}\n--^-------"}
265284
));

test/set_test.cpp

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
#include "jinja2cpp/template.h"
77

8+
#include "test_tools.h"
9+
810
using namespace jinja2;
911

1012
TEST(SetTest, SimpleSetTest)
@@ -131,3 +133,80 @@ world: World
131133
)";
132134
EXPECT_EQ(expectedResult, result);
133135
}
136+
137+
using WithTest = TemplateEnvFixture;
138+
139+
TEST_F(WithTest, SimpleTest)
140+
{
141+
auto result = Render(R"(
142+
{% with inner = 42 %}
143+
{{ inner }}
144+
{%- endwith %}
145+
)", {});
146+
EXPECT_EQ("\n42", result);
147+
}
148+
149+
TEST_F(WithTest, MultiVarsTest)
150+
{
151+
auto result = Render(R"(
152+
{% with inner1 = 42, inner2 = 'Hello World' %}
153+
{{ inner1 }}
154+
{{ inner2 }}
155+
{%- endwith %}
156+
)", {});
157+
EXPECT_EQ("\n42\nHello World", result);
158+
}
159+
160+
TEST_F(WithTest, ScopeTest1)
161+
{
162+
auto result = Render(R"(
163+
{{ outer }}
164+
{% with inner = 42, outer = 'Hello World' %}
165+
{{ inner }}
166+
{{ outer }}
167+
{%- endwith %}
168+
{{ outer }}
169+
)", {{"outer", "World Hello"}});
170+
EXPECT_EQ("\nWorld Hello\n42\nHello WorldWorld Hello\n", result);
171+
}
172+
173+
TEST_F(WithTest, ScopeTest2)
174+
{
175+
auto result = Render(R"(
176+
{{ outer }}
177+
{% with outer = 'Hello World', inner = outer %}
178+
{{ inner }}
179+
{{ outer }}
180+
{%- endwith %}
181+
{{ outer }}
182+
)", {{"outer", "World Hello"}});
183+
EXPECT_EQ("\nWorld Hello\nWorld Hello\nHello WorldWorld Hello\n", result);
184+
}
185+
186+
TEST_F(WithTest, ScopeTest3)
187+
{
188+
auto result = Render(R"(
189+
{{ outer }}
190+
{% with outer = 'Hello World' %}
191+
{% set inner = outer %}
192+
{{ inner }}
193+
{{ outer }}
194+
{%- endwith %}
195+
{{ outer }}
196+
)", {{"outer", "World Hello"}});
197+
EXPECT_EQ("\nWorld Hello\nHello World\nHello WorldWorld Hello\n", result);
198+
}
199+
200+
TEST_F(WithTest, ScopeTest4)
201+
{
202+
auto result = Render(R"(
203+
{% with inner1 = 42 %}
204+
{% set inner2 = outer %}
205+
{{ inner1 }}
206+
{{ inner2 }}
207+
{%- endwith %}
208+
>> {{ inner1 }} <<
209+
>> {{ inner2 }} <<
210+
)", {{"outer", "World Hello"}});
211+
EXPECT_EQ("\n42\nWorld Hello>> <<\n>> <<\n", result);
212+
}

0 commit comments

Comments
 (0)