Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/expression_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class ExpressionParser
ParseResult<ExpressionEvaluatorPtr<IfExpression>> ParseIfExpression(LexScanner& lexer);

private:
ComposedRenderer* m_topLevelRenderer;
ComposedRenderer* m_topLevelRenderer = nullptr;
};

} // jinja2
Expand Down
2 changes: 2 additions & 0 deletions src/lexer.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ struct Token
Recursive,
Scoped,
With,
EndWith,
Without,
Ignore,
Missing,
Expand Down Expand Up @@ -164,6 +165,7 @@ enum class Keyword
Recursive,
Scoped,
With,
EndWith,
Without,
Ignore,
Missing,
Expand Down
13 changes: 13 additions & 0 deletions src/statements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -639,4 +639,17 @@ void DoStatement::Render(OutStream& os, RenderContext& values)
{
m_expr->Evaluate(values);
}

void WithStatement::Render(OutStream& os, RenderContext& values)
{
auto innerValues = values.Clone(true);
auto& scope = innerValues.EnterScope();

for (auto& var : m_scopeVars)
scope[var.first] = var.second->Evaluate(values);

m_mainBody->Render(os, innerValues);

innerValues.ExitScope();
}
} // jinja2
23 changes: 22 additions & 1 deletion src/statements.h
Original file line number Diff line number Diff line change
Expand Up @@ -328,13 +328,34 @@ class DoStatement : public Statement
public:
VISITABLE_STATEMENT();

DoStatement(ExpressionEvaluatorPtr <> expr) : m_expr(expr) {}
DoStatement(ExpressionEvaluatorPtr<> expr) : m_expr(expr) {}

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

private:
ExpressionEvaluatorPtr<> m_expr;
};

class WithStatement : public Statement
{
public:
VISITABLE_STATEMENT();

void SetScopeVars(std::vector<std::pair<std::string, ExpressionEvaluatorPtr<>>> vars)
{
m_scopeVars = std::move(vars);
}
void SetMainBody(RendererPtr renderer)
{
m_mainBody = renderer;
}

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

private:
std::vector<std::pair<std::string, ExpressionEvaluatorPtr<>>> m_scopeVars;
RendererPtr m_mainBody;
};
} // jinja2


Expand Down
65 changes: 65 additions & 0 deletions src/template_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ StatementsParser::ParseResult StatementsParser::Parse(LexScanner& lexer, Stateme
return MakeParseError(ErrorCode::ExtensionDisabled, tok);
result = ParseDo(lexer, statementsInfo, tok);
break;
case Keyword::With:
result = ParseWith(lexer, statementsInfo, tok);
break;
case Keyword::EndWith:
result = ParseEndWith(lexer, statementsInfo, tok);
break;
case Keyword::Filter:
case Keyword::EndFilter:
case Keyword::EndSet:
Expand Down Expand Up @@ -815,4 +821,63 @@ StatementsParser::ParseResult StatementsParser::ParseDo(LexScanner& lexer, State
return jinja2::StatementsParser::ParseResult();
}

StatementsParser::ParseResult StatementsParser::ParseWith(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok)
{
std::vector<std::pair<std::string, ExpressionEvaluatorPtr<>>> vars;

ExpressionParser exprParser(m_settings);
while (lexer.PeekNextToken() == Token::Identifier)
{
auto nameTok = lexer.NextToken();
if (!lexer.EatIfEqual('='))
return MakeParseErrorTL(ErrorCode::ExpectedToken, lexer.PeekNextToken(), '=');

auto expr = exprParser.ParseFullExpression(lexer);
if (!expr)
return expr.get_unexpected();
auto valueExpr = *expr;

vars.emplace_back(AsString(nameTok.value), valueExpr);

if (!lexer.EatIfEqual(','))
break;
}

auto nextTok = lexer.PeekNextToken();
if (vars.empty())
return MakeParseError(ErrorCode::ExpectedIdentifier, nextTok);

if (nextTok != Token::Eof)
return MakeParseErrorTL(ErrorCode::ExpectedToken, nextTok, Token::Eof, ',');

auto renderer = std::make_shared<WithStatement>();
renderer->SetScopeVars(std::move(vars));
StatementInfo statementInfo = StatementInfo::Create(StatementInfo::WithStatement, stmtTok);
statementInfo.renderer = renderer;
statementsInfo.push_back(statementInfo);

return ParseResult();
}

StatementsParser::ParseResult StatementsParser::ParseEndWith(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok)
{
if (statementsInfo.size() <= 1)
return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);

StatementInfo info = statementsInfo.back();

if (info.type != StatementInfo::WithStatement)
{
return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);
}

statementsInfo.pop_back();
auto renderer = static_cast<WithStatement*>(info.renderer.get());
renderer->SetMainBody(info.compositions[0]);

statementsInfo.back().currentComposition->AddRenderer(info.renderer);

return ParseResult();
}

}
13 changes: 10 additions & 3 deletions src/template_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ template<typename T = void>
struct ParserTraitsBase
{
static Token::Type s_keywords[];
static KeywordsInfo s_keywordsInfo[40];
static KeywordsInfo s_keywordsInfo[41];
static std::unordered_map<int, MultiStringLiteral> s_tokens;
};

Expand Down Expand Up @@ -166,7 +166,8 @@ struct StatementInfo
BlockStatement,
ParentBlockStatement,
MacroStatement,
MacroCallStatement
MacroCallStatement,
WithStatement
};

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

private:
Settings m_settings;

ParseResult ParseWith(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& token);

ParseResult ParseEndWith(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok);
};

template<typename CharT>
Expand Down Expand Up @@ -781,7 +786,7 @@ class TemplateParser : public LexerHelper
};

template<typename T>
KeywordsInfo ParserTraitsBase<T>::s_keywordsInfo[40] = {
KeywordsInfo ParserTraitsBase<T>::s_keywordsInfo[41] = {
{UNIVERSAL_STR("for"), Keyword::For},
{UNIVERSAL_STR("endfor"), Keyword::Endfor},
{UNIVERSAL_STR("in"), Keyword::In},
Expand Down Expand Up @@ -815,6 +820,7 @@ KeywordsInfo ParserTraitsBase<T>::s_keywordsInfo[40] = {
{UNIVERSAL_STR("recursive"), Keyword::Recursive},
{UNIVERSAL_STR("scoped"), Keyword::Scoped},
{UNIVERSAL_STR("with"), Keyword::With},
{UNIVERSAL_STR("endwith"), Keyword::EndWith},
{UNIVERSAL_STR("without"), Keyword::Without},
{UNIVERSAL_STR("ignore"), Keyword::Ignore},
{UNIVERSAL_STR("missing"), Keyword::Missing},
Expand Down Expand Up @@ -881,6 +887,7 @@ std::unordered_map<int, MultiStringLiteral> ParserTraitsBase<T>::s_tokens = {
{Token::Recursive, UNIVERSAL_STR("recursive")},
{Token::Scoped, UNIVERSAL_STR("scoped")},
{Token::With, UNIVERSAL_STR("with")},
{Token::EndWith, UNIVERSAL_STR("endwith")},
{Token::Without, UNIVERSAL_STR("without")},
{Token::Ignore, UNIVERSAL_STR("ignore")},
{Token::Missing, UNIVERSAL_STR("missing")},
Expand Down
23 changes: 21 additions & 2 deletions test/errors_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ TEST_P(ErrorsGenericTest, Test)

Template tpl;
auto parseResult = tpl.Load(source);
EXPECT_FALSE(parseResult.has_value());
ASSERT_FALSE(parseResult.has_value());

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

Template tpl(&env);
auto parseResult = tpl.Load(source);
EXPECT_FALSE(parseResult.has_value());
ASSERT_FALSE(parseResult.has_value());

std::ostringstream errorDescr;
errorDescr << parseResult.error();
Expand Down Expand Up @@ -213,6 +213,7 @@ INSTANTIATE_TEST_CASE_P(StatementsTest_1, ErrorsGenericTest, ::testing::Values(
InputOutputPair{"{% from 'foo' import bar with context, %}",
"noname.j2tpl:1:38: error: Expected end of statement, got: ','\n{% from 'foo' import bar with context, %}\n ---^-------"}
));

INSTANTIATE_TEST_CASE_P(StatementsTest_2, ErrorsGenericTest, ::testing::Values(
InputOutputPair{"{% block %}",
"noname.j2tpl:1:10: error: Identifier expected\n{% block %}\n ---^-------"},
Expand Down Expand Up @@ -260,6 +261,24 @@ INSTANTIATE_TEST_CASE_P(StatementsTest_2, ErrorsGenericTest, ::testing::Values(
"noname.j2tpl:1:17: error: Unexpected statement: 'endcall'\n{% block b %}{% endcall %}\n ---^-------"},
InputOutputPair{"{% do 'Hello World' %}",
"noname.j2tpl:1:4: error: Extension disabled\n{% do 'Hello World' %}\n---^-------"},
InputOutputPair{"{% with %}{% endif }",
"noname.j2tpl:1:9: error: Identifier expected\n{% with %}{% endif }\n ---^-------"},
InputOutputPair{"{% with a %}{% endif }",
"noname.j2tpl:1:11: error: Unexpected token '<<End of block>>'. Expected: '='\n{% with a %}{% endif }\n ---^-------"},
InputOutputPair{"{% with a 42 %}{% endif }",
"noname.j2tpl:1:11: error: Unexpected token '42'. Expected: '='\n{% with a 42 %}{% endif }\n ---^-------"},
InputOutputPair{"{% with a = %}{% endif }",
"noname.j2tpl:1:13: error: Unexpected token: '<<End of block>>'\n{% with a = %}{% endif }\n ---^-------"},
InputOutputPair{"{% with a = 42 b = 30 %}{% endif }",
"noname.j2tpl:1:16: error: Unexpected token 'b'. Expected: '<<End of block>>', ','\n{% with a = 42 b = 30 %}{% endif }\n ---^-------"},
InputOutputPair{"{% with a = 42, %}{% endif }",
"noname.j2tpl:1:22: error: Unexpected statement: 'endif'\n{% with a = 42, %}{% endif }\n ---^-------"},
// FIXME: InputOutputPair{"{% with a = 42 %}",
// "noname.j2tpl:1:4: error: Extension disabled\n{% do 'Hello World' %}\n---^-------"},
InputOutputPair{"{% with a = 42 %}{% endfor %}",
"noname.j2tpl:1:21: error: Unexpected statement: 'endfor'\n{% with a = 42 %}{% endfor %}\n ---^-------"},
InputOutputPair{"{% set a = 42 %}{% endwith %}",
"noname.j2tpl:1:20: error: Unexpected statement: 'endwith'\n{% set a = 42 %}{% endwith %}\n ---^-------"},
InputOutputPair{"{{}}",
"noname.j2tpl:1:3: error: Unexpected token: '<<End of block>>'\n{{}}\n--^-------"}
));
Expand Down
79 changes: 79 additions & 0 deletions test/set_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

#include "jinja2cpp/template.h"

#include "test_tools.h"

using namespace jinja2;

TEST(SetTest, SimpleSetTest)
Expand Down Expand Up @@ -131,3 +133,80 @@ world: World
)";
EXPECT_EQ(expectedResult, result);
}

using WithTest = TemplateEnvFixture;

TEST_F(WithTest, SimpleTest)
{
auto result = Render(R"(
{% with inner = 42 %}
{{ inner }}
{%- endwith %}
)", {});
EXPECT_EQ("\n42", result);
}

TEST_F(WithTest, MultiVarsTest)
{
auto result = Render(R"(
{% with inner1 = 42, inner2 = 'Hello World' %}
{{ inner1 }}
{{ inner2 }}
{%- endwith %}
)", {});
EXPECT_EQ("\n42\nHello World", result);
}

TEST_F(WithTest, ScopeTest1)
{
auto result = Render(R"(
{{ outer }}
{% with inner = 42, outer = 'Hello World' %}
{{ inner }}
{{ outer }}
{%- endwith %}
{{ outer }}
)", {{"outer", "World Hello"}});
EXPECT_EQ("\nWorld Hello\n42\nHello WorldWorld Hello\n", result);
}

TEST_F(WithTest, ScopeTest2)
{
auto result = Render(R"(
{{ outer }}
{% with outer = 'Hello World', inner = outer %}
{{ inner }}
{{ outer }}
{%- endwith %}
{{ outer }}
)", {{"outer", "World Hello"}});
EXPECT_EQ("\nWorld Hello\nWorld Hello\nHello WorldWorld Hello\n", result);
}

TEST_F(WithTest, ScopeTest3)
{
auto result = Render(R"(
{{ outer }}
{% with outer = 'Hello World' %}
{% set inner = outer %}
{{ inner }}
{{ outer }}
{%- endwith %}
{{ outer }}
)", {{"outer", "World Hello"}});
EXPECT_EQ("\nWorld Hello\nHello World\nHello WorldWorld Hello\n", result);
}

TEST_F(WithTest, ScopeTest4)
{
auto result = Render(R"(
{% with inner1 = 42 %}
{% set inner2 = outer %}
{{ inner1 }}
{{ inner2 }}
{%- endwith %}
>> {{ inner1 }} <<
>> {{ inner2 }} <<
)", {{"outer", "World Hello"}});
EXPECT_EQ("\n42\nWorld Hello>> <<\n>> <<\n", result);
}