Skip to content

Commit

Permalink
Add pipe syntax support for function calls like in Jinja2, resolves #294
Browse files Browse the repository at this point in the history
  • Loading branch information
navrocky committed Feb 13, 2025
1 parent 17ba53c commit 18c7a49
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 0 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,16 @@ render("{{ isArray(guests) }}", data); // "true"
// Implemented type checks: isArray, isBoolean, isFloat, isInteger, isNumber, isObject, isString,
```

The Jinja2 pipe call syntax of functions is also supported:

```.cpp
// Upper neightbour value
render("Hello {{ neightbour | upper }}!", data); // "Hello PETER!"

// Sort array and join with comma
render("{{ [\"B\", \"A\", \"C\"] | sort | join(\",\") }}", data); // "A,B,C"
```
### Callbacks
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.
Expand Down
2 changes: 2 additions & 0 deletions include/inja/lexer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ class Lexer {
return make_token(Token::Kind::Comma);
case ':':
return make_token(Token::Kind::Colon);
case '|':
return make_token(Token::Kind::Pipe);
case '(':
return make_token(Token::Kind::LeftParen);
case ')':
Expand Down
41 changes: 41 additions & 0 deletions include/inja/parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,47 @@ class Parser {
}
arguments.emplace_back(expr);
} break;

// parse function call pipe syntax
case Token::Kind::Pipe: {
// get function name
get_next_token();
if (tok.kind != Token::Kind::Id) {
throw_parser_error("expected function name, got '" + tok.describe() + "'");
}
auto func = std::make_shared<FunctionNode>(tok.text, tok.text.data() - tmpl.content.c_str());
// add first parameter as last value from arguments
func->number_args += 1;
func->arguments.emplace_back(arguments.back());
arguments.pop_back();
get_peek_token();
if (peek_tok.kind == Token::Kind::LeftParen) {
get_next_token();
// parse additional parameters
do {
get_next_token();
auto expr = parse_expression(tmpl);
if (!expr) {
break;
}
func->number_args += 1;
func->arguments.emplace_back(expr);
} while (tok.kind == Token::Kind::Comma);
if (tok.kind != Token::Kind::RightParen) {
throw_parser_error("expected right parenthesis, got '" + tok.describe() + "'");
}
}
// search store for defined function with such name and number of args
auto function_data = function_storage.find_function(func->name, func->number_args);
if (function_data.operation == FunctionStorage::Operation::None) {
throw_parser_error("unknown function " + func->name);
}
func->operation = function_data.operation;
if (function_data.operation == FunctionStorage::Operation::Callback) {
func->callback = function_data.callback;
}
arguments.emplace_back(func);
} break;
default:
goto break_loop;
}
Expand Down
1 change: 1 addition & 0 deletions include/inja/token.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ struct Token {
GreaterEqual, // >=
LessThan, // <
LessEqual, // <=
Pipe, // |
Unknown,
Eof,
};
Expand Down
44 changes: 44 additions & 0 deletions single_include/inja/inja.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,7 @@ struct Token {
GreaterEqual, // >=
LessThan, // <
LessEqual, // <=
Pipe, // |
Unknown,
Eof,
};
Expand Down Expand Up @@ -1123,6 +1124,8 @@ class Lexer {
return make_token(Token::Kind::Comma);
case ':':
return make_token(Token::Kind::Colon);
case '|':
return make_token(Token::Kind::Pipe);
case '(':
return make_token(Token::Kind::LeftParen);
case ')':
Expand Down Expand Up @@ -1781,6 +1784,47 @@ class Parser {
}
arguments.emplace_back(expr);
} break;

// parse function call pipe syntax
case Token::Kind::Pipe: {
// get function name
get_next_token();
if (tok.kind != Token::Kind::Id) {
throw_parser_error("expected function name, got '" + tok.describe() + "'");
}
auto func = std::make_shared<FunctionNode>(tok.text, tok.text.data() - tmpl.content.c_str());
// add first parameter as last value from arguments
func->number_args += 1;
func->arguments.emplace_back(arguments.back());
arguments.pop_back();
get_peek_token();
if (peek_tok.kind == Token::Kind::LeftParen) {
get_next_token();
// parse additional parameters
do {
get_next_token();
auto expr = parse_expression(tmpl);
if (!expr) {
break;
}
func->number_args += 1;
func->arguments.emplace_back(expr);
} while (tok.kind == Token::Kind::Comma);
if (tok.kind != Token::Kind::RightParen) {
throw_parser_error("expected right parenthesis, got '" + tok.describe() + "'");
}
}
// search store for defined function with such name and number of args
auto function_data = function_storage.find_function(func->name, func->number_args);
if (function_data.operation == FunctionStorage::Operation::None) {
throw_parser_error("unknown function " + func->name);
}
func->operation = function_data.operation;
if (function_data.operation == FunctionStorage::Operation::Callback) {
func->callback = function_data.callback;
}
arguments.emplace_back(func);
} break;
default:
goto break_loop;
}
Expand Down
6 changes: 6 additions & 0 deletions test/test-renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ Yeah!
data) == R""""(Yeah!
)"""");
}

SUBCASE("pipe syntax") {
CHECK(env.render("{{ brother.name | upper }}", data) == "CHRIS");
CHECK(env.render("{{ brother.name | upper | lower }}", data) == "chris");
CHECK(env.render("{{ [\"C\", \"A\", \"B\"] | sort | join(\",\") }}", data) == "A,B,C");
}
}

TEST_CASE("templates") {
Expand Down

0 comments on commit 18c7a49

Please sign in to comment.