Skip to content

Commit ea845ee

Browse files
authored
Add pipe syntax support for function calls like in Jinja2, resolves #294 (#296)
1 parent b3d0e06 commit ea845ee

File tree

6 files changed

+104
-0
lines changed

6 files changed

+104
-0
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,16 @@ render("{{ isArray(guests) }}", data); // "true"
270270
// Implemented type checks: isArray, isBoolean, isFloat, isInteger, isNumber, isObject, isString,
271271
```
272272

273+
The Jinja2 pipe call syntax of functions is also supported:
274+
275+
```.cpp
276+
// Upper neighbour value
277+
render("Hello {{ neighbour | upper }}!", data); // "Hello PETER!"
278+
279+
// Sort array and join with comma
280+
render("{{ [\"B\", \"A\", \"C\"] | sort | join(\",\") }}", data); // "A,B,C"
281+
```
282+
273283
### Callbacks
274284
275285
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 41 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,7 @@ struct Token {
10021002
GreaterEqual, // >=
10031003
LessThan, // <
10041004
LessEqual, // <=
1005+
Pipe, // |
10051006
Unknown,
10061007
Eof,
10071008
};
@@ -1138,6 +1139,8 @@ class Lexer {
11381139
return make_token(Token::Kind::Comma);
11391140
case ':':
11401141
return make_token(Token::Kind::Colon);
1142+
case '|':
1143+
return make_token(Token::Kind::Pipe);
11411144
case '(':
11421145
return make_token(Token::Kind::LeftParen);
11431146
case ')':
@@ -1797,6 +1800,47 @@ class Parser {
17971800
}
17981801
arguments.emplace_back(expr);
17991802
} break;
1803+
1804+
// parse function call pipe syntax
1805+
case Token::Kind::Pipe: {
1806+
// get function name
1807+
get_next_token();
1808+
if (tok.kind != Token::Kind::Id) {
1809+
throw_parser_error("expected function name, got '" + tok.describe() + "'");
1810+
}
1811+
auto func = std::make_shared<FunctionNode>(tok.text, tok.text.data() - tmpl.content.c_str());
1812+
// add first parameter as last value from arguments
1813+
func->number_args += 1;
1814+
func->arguments.emplace_back(arguments.back());
1815+
arguments.pop_back();
1816+
get_peek_token();
1817+
if (peek_tok.kind == Token::Kind::LeftParen) {
1818+
get_next_token();
1819+
// parse additional parameters
1820+
do {
1821+
get_next_token();
1822+
auto expr = parse_expression(tmpl);
1823+
if (!expr) {
1824+
break;
1825+
}
1826+
func->number_args += 1;
1827+
func->arguments.emplace_back(expr);
1828+
} while (tok.kind == Token::Kind::Comma);
1829+
if (tok.kind != Token::Kind::RightParen) {
1830+
throw_parser_error("expected right parenthesis, got '" + tok.describe() + "'");
1831+
}
1832+
}
1833+
// search store for defined function with such name and number of args
1834+
auto function_data = function_storage.find_function(func->name, func->number_args);
1835+
if (function_data.operation == FunctionStorage::Operation::None) {
1836+
throw_parser_error("unknown function " + func->name);
1837+
}
1838+
func->operation = function_data.operation;
1839+
if (function_data.operation == FunctionStorage::Operation::Callback) {
1840+
func->callback = function_data.callback;
1841+
}
1842+
arguments.emplace_back(func);
1843+
} break;
18001844
default:
18011845
goto break_loop;
18021846
}

test/test-renderer.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,12 @@ Yeah!
154154
data) == R""""(Yeah!
155155
)"""");
156156
}
157+
158+
SUBCASE("pipe syntax") {
159+
CHECK(env.render("{{ brother.name | upper }}", data) == "CHRIS");
160+
CHECK(env.render("{{ brother.name | upper | lower }}", data) == "chris");
161+
CHECK(env.render("{{ [\"C\", \"A\", \"B\"] | sort | join(\",\") }}", data) == "A,B,C");
162+
}
157163
}
158164

159165
TEST_CASE("templates") {

0 commit comments

Comments
 (0)