Skip to content

Commit 760dd51

Browse files
committed
Add pipe syntax support for function calls like in Jinja2, resolves #294
1 parent 17ba53c commit 760dd51

File tree

6 files changed

+104
-0
lines changed

6 files changed

+104
-0
lines changed

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,16 @@ render("{{ isArray(guests) }}", data); // "true"
267267
// Implemented type checks: isArray, isBoolean, isFloat, isInteger, isNumber, isObject, isString,
268268
```
269269

270+
The Jinja2 pipe call syntax of functions is also supported:
271+
272+
```.cpp
273+
// Upper neightbour value
274+
render("Hello {{ neightbour | upper }}!", data); // "Hello PETER!"
275+
276+
// Sort array and join with comma
277+
render("{{ [\"B\", \"A\", \"C\"] | sort | join(\",\") }}", data); // "A,B,C"
278+
```
279+
270280
### Callbacks
271281
272282
You can create your own and more complex functions with callbacks. These are implemented with `std::function`, so you can for example use C++ lambdas. Inja `Arguments` are a vector of json pointers.

include/inja/lexer.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ class Lexer {
115115
return make_token(Token::Kind::Comma);
116116
case ':':
117117
return make_token(Token::Kind::Colon);
118+
case '|':
119+
return make_token(Token::Kind::Pipe);
118120
case '(':
119121
return make_token(Token::Kind::LeftParen);
120122
case ')':

include/inja/parser.hpp

+41
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,47 @@ class Parser {
350350
}
351351
arguments.emplace_back(expr);
352352
} break;
353+
354+
// parse function call pipe syntax
355+
case Token::Kind::Pipe: {
356+
// get function name
357+
get_next_token();
358+
if (tok.kind != Token::Kind::Id) {
359+
throw_parser_error("expected function name, got '" + tok.describe() + "'");
360+
}
361+
auto func = std::make_shared<FunctionNode>(tok.text, tok.text.data() - tmpl.content.c_str());
362+
// add first parameter as last value from arguments
363+
func->number_args += 1;
364+
func->arguments.emplace_back(arguments.back());
365+
arguments.pop_back();
366+
get_peek_token();
367+
if (peek_tok.kind == Token::Kind::LeftParen) {
368+
get_next_token();
369+
// parse additional parameters
370+
do {
371+
get_next_token();
372+
auto expr = parse_expression(tmpl);
373+
if (!expr) {
374+
break;
375+
}
376+
func->number_args += 1;
377+
func->arguments.emplace_back(expr);
378+
} while (tok.kind == Token::Kind::Comma);
379+
if (tok.kind != Token::Kind::RightParen) {
380+
throw_parser_error("expected right parenthesis, got '" + tok.describe() + "'");
381+
}
382+
}
383+
// search store for defined function with such name and number of args
384+
auto function_data = function_storage.find_function(func->name, func->number_args);
385+
if (function_data.operation == FunctionStorage::Operation::None) {
386+
throw_parser_error("unknown function " + func->name);
387+
}
388+
func->operation = function_data.operation;
389+
if (function_data.operation == FunctionStorage::Operation::Callback) {
390+
func->callback = function_data.callback;
391+
}
392+
arguments.emplace_back(func);
393+
} break;
353394
default:
354395
goto break_loop;
355396
}

include/inja/token.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ struct Token {
4444
GreaterEqual, // >=
4545
LessThan, // <
4646
LessEqual, // <=
47+
Pipe, // |
4748
Unknown,
4849
Eof,
4950
};

single_include/inja/inja.hpp

+44
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,7 @@ struct Token {
987987
GreaterEqual, // >=
988988
LessThan, // <
989989
LessEqual, // <=
990+
Pipe, // |
990991
Unknown,
991992
Eof,
992993
};
@@ -1123,6 +1124,8 @@ class Lexer {
11231124
return make_token(Token::Kind::Comma);
11241125
case ':':
11251126
return make_token(Token::Kind::Colon);
1127+
case '|':
1128+
return make_token(Token::Kind::Pipe);
11261129
case '(':
11271130
return make_token(Token::Kind::LeftParen);
11281131
case ')':
@@ -1781,6 +1784,47 @@ class Parser {
17811784
}
17821785
arguments.emplace_back(expr);
17831786
} break;
1787+
1788+
// parse function call pipe syntax
1789+
case Token::Kind::Pipe: {
1790+
// get function name
1791+
get_next_token();
1792+
if (tok.kind != Token::Kind::Id) {
1793+
throw_parser_error("expected function name, got '" + tok.describe() + "'");
1794+
}
1795+
auto func = std::make_shared<FunctionNode>(tok.text, tok.text.data() - tmpl.content.c_str());
1796+
// add first parameter as last value from arguments
1797+
func->number_args += 1;
1798+
func->arguments.emplace_back(arguments.back());
1799+
arguments.pop_back();
1800+
get_peek_token();
1801+
if (peek_tok.kind == Token::Kind::LeftParen) {
1802+
get_next_token();
1803+
// parse additional parameters
1804+
do {
1805+
get_next_token();
1806+
auto expr = parse_expression(tmpl);
1807+
if (!expr) {
1808+
break;
1809+
}
1810+
func->number_args += 1;
1811+
func->arguments.emplace_back(expr);
1812+
} while (tok.kind == Token::Kind::Comma);
1813+
if (tok.kind != Token::Kind::RightParen) {
1814+
throw_parser_error("expected right parenthesis, got '" + tok.describe() + "'");
1815+
}
1816+
}
1817+
// search store for defined function with such name and number of args
1818+
auto function_data = function_storage.find_function(func->name, func->number_args);
1819+
if (function_data.operation == FunctionStorage::Operation::None) {
1820+
throw_parser_error("unknown function " + func->name);
1821+
}
1822+
func->operation = function_data.operation;
1823+
if (function_data.operation == FunctionStorage::Operation::Callback) {
1824+
func->callback = function_data.callback;
1825+
}
1826+
arguments.emplace_back(func);
1827+
} break;
17841828
default:
17851829
goto break_loop;
17861830
}

test/test-renderer.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,12 @@ Yeah!
157157
data) == R""""(Yeah!
158158
)"""");
159159
}
160+
161+
SUBCASE("pipe syntax") {
162+
CHECK(env.render("{{ brother.name | upper }}", data) == "CHRIS");
163+
CHECK(env.render("{{ brother.name | upper | lower }}", data) == "chris");
164+
CHECK(env.render("{{ [\"C\", \"A\", \"B\"] | sort | join(\",\") }}", data) == "A,B,C");
165+
}
160166
}
161167

162168
TEST_CASE("templates") {

0 commit comments

Comments
 (0)