Skip to content

Commit fc24a0b

Browse files
authored
Make the most of the keywords context-dependent #73 (#88)
1 parent 853f64a commit fc24a0b

File tree

9 files changed

+257
-122
lines changed

9 files changed

+257
-122
lines changed

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ C++ implementation of big subset of Jinja2 template engine features. This librar
2828
- ['set' statement](#set-statement)
2929
- ['extends' statement](#extends-statement)
3030
- [Macros](#macros)
31+
- [User-defined callables](#user-defined-callables)
3132
- [Error reporting](#error-reporting)
3233
- [Other features](#other-features)
3334
- [Current Jinja2 support](#current-jinja2-support)
@@ -437,6 +438,40 @@ inline {{ resultType }} {{ methodName }}({{ methodParams | join(', ') }} )
437438

438439
Here is an `InlineMacro` which just describe the inline method definition skeleton. This macro doesn't contain the actual method body. Instead of this it calls `caller` special function. This function invokes the special **callback** macro which is a body of `call` statement. And this macro can have parameters as well. More detailed this mechanics described in the [Jinja2 documentation](http://jinja.pocoo.org/docs/2.10/templates/#macros).
439440

441+
### 'applymacro' filter
442+
443+
With help of `applymacro` filter macro can be called in filtering context. `applymacro` works similar to `map` (or `test`) filter with one exception: instead of name of other filter it takes name of macro via `macro` param and pass the rest of arguments to it. The object which is been filtered is passed as the first positional argument. For example:
444+
445+
```
446+
{% macro toUpper(str) %}{{ str | upper }}{% endmacro %}
447+
{{ 'Hello World!' | applymacro(macro='toUpper') }}
448+
```
449+
450+
produces the result `HELLO WORLD`. `applymacro` can be applied to the sequences via `map` filter. Also, macro name can be `caller`. In this case outer `call` statement will be invoked during macro application.
451+
452+
## User-defined callables
453+
454+
Not only C++ types can be reflected into Jinja2 template context, but the functions (and lambdas, and any other callable objects) as well. These refelected callable objects are called 'user-defined callables' and can be accessed from Jinja2 templates in the same manner as any other callables (like macros or global functions). In order to reflect callable object into Jinja2 context the `jinja2::MakeCallable` method should be used:
455+
456+
```c++
457+
jinja2::ValuesMap params;
458+
params["concat"] = MakeCallable(
459+
[](const std::string& str1, const std::string& str2) {
460+
return str1 + " " + str2;
461+
},
462+
ArgInfo{"str1"}, ArgInfo{"str2", false, "default"}
463+
);
464+
```
465+
466+
As a first parameter this method takes the callable itself. It can be lambda, the std::function<> instance or pointer to function. The rest of params are callable arguments descriptors, which are provided via `ArgInfo` structure. In the sample above user-defined callable `concat` is introduced, which take two argument: `str1` and `str2`. This callable can be accessed from the template in the following ways:
467+
468+
```
469+
{{ concat('Hello', 'World!') }}
470+
{{ concat(str2='World!', str1='Hello') }}
471+
{{ concat(str2='World!') }}
472+
{{ concat('Hello') }}
473+
```
474+
440475
## Error reporting
441476
It's difficult to write complex template completely without errors. Missed braces, wrong characters, incorrect names... Everything is possible. So, it's crucial to be able to get informative error report from the template engine. Jinja2Cpp provides such kind of report. ```Template::Load``` method (and TemplateEnv::LoadTemplate respectively) return instance of ```ErrorInfo``` class which contains details about the error. These details include:
442477
- Error code

src/expression_parser.cpp

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "expression_parser.h"
22

33
#include <sstream>
4+
#include <unordered_set>
45

56
namespace jinja2
67
{
@@ -59,7 +60,7 @@ ExpressionParser::ParseResult<ExpressionEvaluatorPtr<FullExpressionEvaluator>> E
5960
evaluator->SetFilter(*filter);
6061
}
6162

62-
if (includeIfPart && lexer.EatIfEqual(Token::If))
63+
if (includeIfPart && lexer.EatIfEqual(Keyword::If))
6364
{
6465
if (includeIfPart)
6566
{
@@ -79,7 +80,7 @@ ExpressionParser::ParseResult<ExpressionEvaluatorPtr<Expression>> ExpressionPars
7980
{
8081
auto left = ParseLogicalAnd(lexer);
8182

82-
if (left && lexer.EatIfEqual(Token::LogicalOr))
83+
if (left && lexer.EatIfEqual(Keyword::LogicalOr))
8384
{
8485
auto right = ParseLogicalOr(lexer);
8586
if (!right)
@@ -95,7 +96,7 @@ ExpressionParser::ParseResult<ExpressionEvaluatorPtr<Expression>> ExpressionPars
9596
{
9697
auto left = ParseLogicalCompare(lexer);
9798

98-
if (left && lexer.EatIfEqual(Token::LogicalAnd))
99+
if (left && lexer.EatIfEqual(Keyword::LogicalAnd))
99100
{
100101
auto right = ParseLogicalAnd(lexer);
101102
if (!right)
@@ -135,29 +136,33 @@ ExpressionParser::ParseResult<ExpressionEvaluatorPtr<Expression>> ExpressionPars
135136
case Token::LessEqual:
136137
operation = BinaryExpression::LogicalLe;
137138
break;
138-
case Token::In:
139-
operation = BinaryExpression::In;
140-
break;
141-
case Token::Is:
142-
{
143-
Token nextTok = lexer.NextToken();
144-
if (nextTok != Token::Identifier)
145-
return MakeParseError(ErrorCode::ExpectedIdentifier, nextTok);
146-
147-
std::string name = AsString(nextTok.value);
148-
ParseResult<CallParams> params;
149-
150-
if (lexer.EatIfEqual('('))
151-
params = ParseCallParams(lexer);
152-
153-
if (!params)
154-
return params.get_unexpected();
155-
156-
return std::make_shared<IsExpression>(*left, std::move(name), std::move(*params));
157-
}
158139
default:
159-
lexer.ReturnToken();
160-
return left;
140+
switch (lexer.GetAsKeyword(tok))
141+
{
142+
case Keyword::In:
143+
operation = BinaryExpression::In;
144+
break;
145+
case Keyword::Is:
146+
{
147+
Token nextTok = lexer.NextToken();
148+
if (nextTok != Token::Identifier)
149+
return MakeParseError(ErrorCode::ExpectedIdentifier, nextTok);
150+
151+
std::string name = AsString(nextTok.value);
152+
ParseResult<CallParams> params;
153+
154+
if (lexer.EatIfEqual('('))
155+
params = ParseCallParams(lexer);
156+
157+
if (!params)
158+
return params.get_unexpected();
159+
160+
return std::make_shared<IsExpression>(*left, std::move(name), std::move(*params));
161+
}
162+
default:
163+
lexer.ReturnToken();
164+
return left;
165+
}
161166
}
162167

163168
auto right = ParseStringConcat(lexer);
@@ -263,7 +268,7 @@ ExpressionParser::ParseResult<ExpressionEvaluatorPtr<Expression>> ExpressionPars
263268
ExpressionParser::ParseResult<ExpressionEvaluatorPtr<Expression>> ExpressionParser::ParseUnaryPlusMinus(LexScanner& lexer)
264269
{
265270
Token tok = lexer.NextToken();
266-
if (tok != '+' && tok != '-' && tok != Token::LogicalNot)
271+
if (tok != '+' && tok != '-' && lexer.GetAsKeyword(tok) != Keyword::LogicalNot)
267272
{
268273
lexer.ReturnToken();
269274
return ParseValueExpression(lexer);
@@ -279,14 +284,21 @@ ExpressionParser::ParseResult<ExpressionEvaluatorPtr<Expression>> ExpressionPars
279284
ExpressionParser::ParseResult<ExpressionEvaluatorPtr<Expression>> ExpressionParser::ParseValueExpression(LexScanner& lexer)
280285
{
281286
Token tok = lexer.NextToken();
287+
static const std::unordered_set<Keyword> forbiddenKw = {Keyword::Is, Keyword::In, Keyword::If, Keyword::Else};
282288

283289
ParseResult<ExpressionEvaluatorPtr<Expression>> valueRef;
284290

285291
switch (tok.type)
286292
{
287293
case Token::Identifier:
294+
{
295+
auto kwType = lexer.GetAsKeyword(tok);
296+
if (forbiddenKw.count(kwType) != 0)
297+
return MakeParseError(ErrorCode::UnexpectedToken, tok);
298+
288299
valueRef = std::make_shared<ValueRefExpression>(AsString(tok.value));
289300
break;
301+
}
290302
case Token::IntegerNum:
291303
case Token::FloatNum:
292304
case Token::String:
@@ -565,7 +577,7 @@ ExpressionParser::ParseResult<ExpressionEvaluatorPtr<IfExpression>> ExpressionPa
565577
return testExpr.get_unexpected();
566578

567579
ParseResult<ExpressionEvaluatorPtr<>> altValue;
568-
if (lexer.PeekNextToken() == Token::Else)
580+
if (lexer.GetAsKeyword(lexer.PeekNextToken()) == Keyword::Else)
569581
{
570582
lexer.EatToken();
571583
auto value = ParseFullExpression(lexer);

src/filters.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,14 +319,14 @@ InternalValue GroupBy::Filter(const InternalValue& baseVal, RenderContext& conte
319319

320320
ApplyMacro::ApplyMacro(FilterParams params)
321321
{
322-
ParseParams({{"name", true}}, params);
322+
ParseParams({{"macro", true}}, params);
323323
m_mappingParams.kwParams = m_args.extraKwArgs;
324324
m_mappingParams.posParams = m_args.extraPosArgs;
325325
}
326326

327327
InternalValue ApplyMacro::Filter(const InternalValue& baseVal, RenderContext& context)
328328
{
329-
InternalValue macroName = GetArgumentValue("name", context);
329+
InternalValue macroName = GetArgumentValue("macro", context);
330330
if (IsEmpty(macroName))
331331
return InternalValue();
332332

src/lexer.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,25 @@ bool Lexer::ProcessNumber(const lexertk::token&, Token& newToken)
8080

8181
bool Lexer::ProcessSymbolOrKeyword(const lexertk::token&, Token& newToken)
8282
{
83-
Token::Type tokType = m_helper->GetKeyword(newToken.range);
83+
Keyword kwType = m_helper->GetKeyword(newToken.range);
84+
Token::Type tokType = Token::Unknown;
85+
86+
switch (kwType)
87+
{
88+
case Keyword::None:
89+
tokType = Token::None;
90+
break;
91+
case Keyword::True:
92+
tokType = Token::True;
93+
break;
94+
case Keyword::False:
95+
tokType = Token::False;
96+
break;
97+
default:
98+
tokType = Token::Unknown;
99+
break;
100+
}
101+
84102
if (tokType == Token::Unknown)
85103
{
86104
newToken.type = Token::Identifier;

src/lexer.h

Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,18 @@ struct Token
5252
GreaterEqual,
5353
StarStar,
5454
DashDash,
55-
LogicalOr,
56-
LogicalAnd,
57-
LogicalNot,
5855
MulMul,
5956
DivDiv,
6057
True,
6158
False,
6259
None,
63-
In,
64-
Is,
6560

6661
// Keywords
62+
LogicalOr,
63+
LogicalAnd,
64+
LogicalNot,
65+
In,
66+
Is,
6767
For,
6868
Endfor,
6969
If,
@@ -83,6 +83,8 @@ struct Token
8383
EndSet,
8484
Include,
8585
Import,
86+
Recursive,
87+
Scoped,
8688

8789
// Template control
8890
CommentBegin,
@@ -92,7 +94,7 @@ struct Token
9294
ExprBegin,
9395
ExprEnd,
9496
};
95-
97+
9698
Type type = Unknown;
9799
CharRange range = {0, 0};
98100
InternalValue value;
@@ -119,11 +121,47 @@ struct Token
119121
}
120122
};
121123

124+
enum class Keyword
125+
{
126+
Unknown,
127+
128+
// Keywords
129+
LogicalOr,
130+
LogicalAnd,
131+
LogicalNot,
132+
True,
133+
False,
134+
None,
135+
In,
136+
Is,
137+
For,
138+
Endfor,
139+
If,
140+
Else,
141+
ElIf,
142+
EndIf,
143+
Block,
144+
EndBlock,
145+
Extends,
146+
Macro,
147+
EndMacro,
148+
Call,
149+
EndCall,
150+
Filter,
151+
EndFilter,
152+
Set,
153+
EndSet,
154+
Include,
155+
Import,
156+
Recursive,
157+
Scoped
158+
};
159+
122160
struct LexerHelper
123161
{
124162
virtual std::string GetAsString(const CharRange& range) = 0;
125163
virtual InternalValue GetAsValue(const CharRange& range, Token::Type type) = 0;
126-
virtual Token::Type GetKeyword(const CharRange& range) = 0;
164+
virtual Keyword GetKeyword(const CharRange& range) = 0;
127165
virtual char GetCharAt(size_t pos) = 0;
128166
};
129167

@@ -142,6 +180,8 @@ class Lexer
142180
{
143181
return m_tokens;
144182
}
183+
184+
auto GetHelper() const {return m_helper;}
145185

146186
private:
147187
bool ProcessNumber(const lexertk::token& token, Token& newToken);
@@ -188,6 +228,7 @@ class LexScanner
188228
};
189229

190230
LexScanner(const Lexer& lexer)
231+
: m_helper(lexer.GetHelper())
191232
{
192233
m_state.m_begin = lexer.GetTokens().begin();
193234
m_state.m_end = lexer.GetTokens().end();
@@ -252,7 +293,27 @@ class LexScanner
252293
return type == Token::Type::Eof;
253294
}
254295

255-
if (m_state.m_cur->type == type)
296+
return EatIfEqualImpl(tok, [type](const Token& t) {return t.type == type;});
297+
}
298+
299+
auto GetAsKeyword(const Token& tok) const
300+
{
301+
return m_helper->GetKeyword(tok.range);
302+
}
303+
304+
bool EatIfEqual(Keyword kwType, Token* tok = nullptr)
305+
{
306+
if (m_state.m_cur == m_state.m_end)
307+
return false;
308+
309+
return EatIfEqualImpl(tok, [this, kwType](const Token& t) {return GetAsKeyword(t) == kwType;});
310+
}
311+
312+
private:
313+
template<typename Fn>
314+
bool EatIfEqualImpl(Token* tok, Fn&& predicate)
315+
{
316+
if (predicate(*m_state.m_cur))
256317
{
257318
if (tok)
258319
*tok = *m_state.m_cur;
@@ -265,6 +326,8 @@ class LexScanner
265326

266327
private:
267328
State m_state;
329+
LexerHelper* m_helper;
330+
268331
static const Token& EofToken()
269332
{
270333
static Token eof;
@@ -275,4 +338,16 @@ class LexScanner
275338

276339
} // jinja2
277340

341+
namespace std
342+
{
343+
template<>
344+
struct hash<jinja2::Keyword>
345+
{
346+
size_t operator()(jinja2::Keyword kw) const
347+
{
348+
return std::hash<int>{}(static_cast<int>(kw));
349+
}
350+
};
351+
}
352+
278353
#endif // LEXER_H

0 commit comments

Comments
 (0)