Inja 3.4.0
A Template Engine for Modern C++
Loading...
Searching...
No Matches
renderer.hpp
1#ifndef INCLUDE_INJA_RENDERER_HPP_
2#define INCLUDE_INJA_RENDERER_HPP_
3
4#include <algorithm>
5#include <numeric>
6#include <string>
7#include <utility>
8#include <vector>
9
10#include "config.hpp"
11#include "exceptions.hpp"
12#include "node.hpp"
13#include "template.hpp"
14#include "utils.hpp"
15
16namespace inja {
17
21class Renderer : public NodeVisitor {
22 using Op = FunctionStorage::Operation;
23
24 const RenderConfig config;
25 const TemplateStorage& template_storage;
26 const FunctionStorage& function_storage;
27
28 const Template* current_template;
29 size_t current_level {0};
30 std::vector<const Template*> template_stack;
31 std::vector<const BlockStatementNode*> block_statement_stack;
32
33 const json* data_input;
34 std::ostream* output_stream;
35
36 json additional_data;
37 json* current_loop_data = &additional_data["loop"];
38
39 std::vector<std::shared_ptr<json>> data_tmp_stack;
40 std::stack<const json*> data_eval_stack;
41 std::stack<const DataNode*> not_found_stack;
42
43 bool break_rendering {false};
44
45 static bool truthy(const json* data) {
46 if (data->is_boolean()) {
47 return data->get<bool>();
48 } else if (data->is_number()) {
49 return (*data != 0);
50 } else if (data->is_null()) {
51 return false;
52 }
53 return !data->empty();
54 }
55
56 void print_data(const std::shared_ptr<json> value) {
57 if (value->is_string()) {
58 *output_stream << value->get_ref<const json::string_t&>();
59 } else if (value->is_number_integer()) {
60 *output_stream << value->get<const json::number_integer_t>();
61 } else if (value->is_null()) {
62 } else {
63 *output_stream << value->dump();
64 }
65 }
66
67 const std::shared_ptr<json> eval_expression_list(const ExpressionListNode& expression_list) {
68 if (!expression_list.root) {
69 throw_renderer_error("empty expression", expression_list);
70 }
71
72 expression_list.root->accept(*this);
73
74 if (data_eval_stack.empty()) {
75 throw_renderer_error("empty expression", expression_list);
76 } else if (data_eval_stack.size() != 1) {
77 throw_renderer_error("malformed expression", expression_list);
78 }
79
80 const auto result = data_eval_stack.top();
81 data_eval_stack.pop();
82
83 if (!result) {
84 if (not_found_stack.empty()) {
85 throw_renderer_error("expression could not be evaluated", expression_list);
86 }
87
88 auto node = not_found_stack.top();
89 not_found_stack.pop();
90
91 throw_renderer_error("variable '" + static_cast<std::string>(node->name) + "' not found", *node);
92 }
93 return std::make_shared<json>(*result);
94 }
95
96 void throw_renderer_error(const std::string& message, const AstNode& node) {
97 SourceLocation loc = get_source_location(current_template->content, node.pos);
98 INJA_THROW(RenderError(message, loc));
99 }
100
101 void make_result(const json&& result) {
102 auto result_ptr = std::make_shared<json>(result);
103 data_tmp_stack.push_back(result_ptr);
104 data_eval_stack.push(result_ptr.get());
105 }
106
107 template <size_t N, size_t N_start = 0, bool throw_not_found = true> std::array<const json*, N> get_arguments(const FunctionNode& node) {
108 if (node.arguments.size() < N_start + N) {
109 throw_renderer_error("function needs " + std::to_string(N_start + N) + " variables, but has only found " + std::to_string(node.arguments.size()), node);
110 }
111
112 for (size_t i = N_start; i < N_start + N; i += 1) {
113 node.arguments[i]->accept(*this);
114 }
115
116 if (data_eval_stack.size() < N) {
117 throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(data_eval_stack.size()), node);
118 }
119
120 std::array<const json*, N> result;
121 for (size_t i = 0; i < N; i += 1) {
122 result[N - i - 1] = data_eval_stack.top();
123 data_eval_stack.pop();
124
125 if (!result[N - i - 1]) {
126 const auto data_node = not_found_stack.top();
127 not_found_stack.pop();
128
129 if (throw_not_found) {
130 throw_renderer_error("variable '" + static_cast<std::string>(data_node->name) + "' not found", *data_node);
131 }
132 }
133 }
134 return result;
135 }
136
137 template <bool throw_not_found = true> Arguments get_argument_vector(const FunctionNode& node) {
138 const size_t N = node.arguments.size();
139 for (auto a : node.arguments) {
140 a->accept(*this);
141 }
142
143 if (data_eval_stack.size() < N) {
144 throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(data_eval_stack.size()), node);
145 }
146
147 Arguments result {N};
148 for (size_t i = 0; i < N; i += 1) {
149 result[N - i - 1] = data_eval_stack.top();
150 data_eval_stack.pop();
151
152 if (!result[N - i - 1]) {
153 const auto data_node = not_found_stack.top();
154 not_found_stack.pop();
155
156 if (throw_not_found) {
157 throw_renderer_error("variable '" + static_cast<std::string>(data_node->name) + "' not found", *data_node);
158 }
159 }
160 }
161 return result;
162 }
163
164 void visit(const BlockNode& node) {
165 for (auto& n : node.nodes) {
166 n->accept(*this);
167
168 if (break_rendering) {
169 break;
170 }
171 }
172 }
173
174 void visit(const TextNode& node) {
175 output_stream->write(current_template->content.c_str() + node.pos, node.length);
176 }
177
178 void visit(const ExpressionNode&) {}
179
180 void visit(const LiteralNode& node) {
181 data_eval_stack.push(&node.value);
182 }
183
184 void visit(const DataNode& node) {
185 if (additional_data.contains(node.ptr)) {
186 data_eval_stack.push(&(additional_data[node.ptr]));
187 } else if (data_input->contains(node.ptr)) {
188 data_eval_stack.push(&(*data_input)[node.ptr]);
189 } else {
190 // Try to evaluate as a no-argument callback
191 const auto function_data = function_storage.find_function(node.name, 0);
192 if (function_data.operation == FunctionStorage::Operation::Callback) {
193 Arguments empty_args {};
194 const auto value = std::make_shared<json>(function_data.callback(empty_args));
195 data_tmp_stack.push_back(value);
196 data_eval_stack.push(value.get());
197 } else {
198 data_eval_stack.push(nullptr);
199 not_found_stack.emplace(&node);
200 }
201 }
202 }
203
204 void visit(const FunctionNode& node) {
205 switch (node.operation) {
206 case Op::Not: {
207 const auto args = get_arguments<1>(node);
208 make_result(!truthy(args[0]));
209 } break;
210 case Op::And: {
211 make_result(truthy(get_arguments<1, 0>(node)[0]) && truthy(get_arguments<1, 1>(node)[0]));
212 } break;
213 case Op::Or: {
214 make_result(truthy(get_arguments<1, 0>(node)[0]) || truthy(get_arguments<1, 1>(node)[0]));
215 } break;
216 case Op::In: {
217 const auto args = get_arguments<2>(node);
218 make_result(std::find(args[1]->begin(), args[1]->end(), *args[0]) != args[1]->end());
219 } break;
220 case Op::Equal: {
221 const auto args = get_arguments<2>(node);
222 make_result(*args[0] == *args[1]);
223 } break;
224 case Op::NotEqual: {
225 const auto args = get_arguments<2>(node);
226 make_result(*args[0] != *args[1]);
227 } break;
228 case Op::Greater: {
229 const auto args = get_arguments<2>(node);
230 make_result(*args[0] > *args[1]);
231 } break;
232 case Op::GreaterEqual: {
233 const auto args = get_arguments<2>(node);
234 make_result(*args[0] >= *args[1]);
235 } break;
236 case Op::Less: {
237 const auto args = get_arguments<2>(node);
238 make_result(*args[0] < *args[1]);
239 } break;
240 case Op::LessEqual: {
241 const auto args = get_arguments<2>(node);
242 make_result(*args[0] <= *args[1]);
243 } break;
244 case Op::Add: {
245 const auto args = get_arguments<2>(node);
246 if (args[0]->is_string() && args[1]->is_string()) {
247 make_result(args[0]->get_ref<const json::string_t&>() + args[1]->get_ref<const json::string_t&>());
248 } else if (args[0]->is_number_integer() && args[1]->is_number_integer()) {
249 make_result(args[0]->get<const json::number_integer_t>() + args[1]->get<const json::number_integer_t>());
250 } else {
251 make_result(args[0]->get<const json::number_float_t>() + args[1]->get<const json::number_float_t>());
252 }
253 } break;
254 case Op::Subtract: {
255 const auto args = get_arguments<2>(node);
256 if (args[0]->is_number_integer() && args[1]->is_number_integer()) {
257 make_result(args[0]->get<const json::number_integer_t>() - args[1]->get<const json::number_integer_t>());
258 } else {
259 make_result(args[0]->get<const json::number_float_t>() - args[1]->get<const json::number_float_t>());
260 }
261 } break;
262 case Op::Multiplication: {
263 const auto args = get_arguments<2>(node);
264 if (args[0]->is_number_integer() && args[1]->is_number_integer()) {
265 make_result(args[0]->get<const json::number_integer_t>() * args[1]->get<const json::number_integer_t>());
266 } else {
267 make_result(args[0]->get<const json::number_float_t>() * args[1]->get<const json::number_float_t>());
268 }
269 } break;
270 case Op::Division: {
271 const auto args = get_arguments<2>(node);
272 if (args[1]->get<const json::number_float_t>() == 0) {
273 throw_renderer_error("division by zero", node);
274 }
275 make_result(args[0]->get<const json::number_float_t>() / args[1]->get<const json::number_float_t>());
276 } break;
277 case Op::Power: {
278 const auto args = get_arguments<2>(node);
279 if (args[0]->is_number_integer() && args[1]->get<const json::number_integer_t>() >= 0) {
280 const auto result = static_cast<json::number_integer_t>(std::pow(args[0]->get<const json::number_integer_t>(), args[1]->get<const json::number_integer_t>()));
281 make_result(result);
282 } else {
283 const auto result = std::pow(args[0]->get<const json::number_float_t>(), args[1]->get<const json::number_integer_t>());
284 make_result(result);
285 }
286 } break;
287 case Op::Modulo: {
288 const auto args = get_arguments<2>(node);
289 make_result(args[0]->get<const json::number_integer_t>() % args[1]->get<const json::number_integer_t>());
290 } break;
291 case Op::AtId: {
292 const auto container = get_arguments<1, 0, false>(node)[0];
293 node.arguments[1]->accept(*this);
294 if (not_found_stack.empty()) {
295 throw_renderer_error("could not find element with given name", node);
296 }
297 const auto id_node = not_found_stack.top();
298 not_found_stack.pop();
299 data_eval_stack.pop();
300 data_eval_stack.push(&container->at(id_node->name));
301 } break;
302 case Op::At: {
303 const auto args = get_arguments<2>(node);
304 if (args[0]->is_object()) {
305 data_eval_stack.push(&args[0]->at(args[1]->get<std::string>()));
306 } else {
307 data_eval_stack.push(&args[0]->at(args[1]->get<int>()));
308 }
309 } break;
310 case Op::Default: {
311 const auto test_arg = get_arguments<1, 0, false>(node)[0];
312 data_eval_stack.push(test_arg ? test_arg : get_arguments<1, 1>(node)[0]);
313 } break;
314 case Op::DivisibleBy: {
315 const auto args = get_arguments<2>(node);
316 const auto divisor = args[1]->get<const json::number_integer_t>();
317 make_result((divisor != 0) && (args[0]->get<const json::number_integer_t>() % divisor == 0));
318 } break;
319 case Op::Even: {
320 make_result(get_arguments<1>(node)[0]->get<const json::number_integer_t>() % 2 == 0);
321 } break;
322 case Op::Exists: {
323 auto&& name = get_arguments<1>(node)[0]->get_ref<const json::string_t&>();
324 make_result(data_input->contains(json::json_pointer(DataNode::convert_dot_to_ptr(name))));
325 } break;
326 case Op::ExistsInObject: {
327 const auto args = get_arguments<2>(node);
328 auto&& name = args[1]->get_ref<const json::string_t&>();
329 make_result(args[0]->find(name) != args[0]->end());
330 } break;
331 case Op::First: {
332 const auto result = &get_arguments<1>(node)[0]->front();
333 data_eval_stack.push(result);
334 } break;
335 case Op::Float: {
336 make_result(std::stod(get_arguments<1>(node)[0]->get_ref<const json::string_t&>()));
337 } break;
338 case Op::Int: {
339 make_result(std::stoi(get_arguments<1>(node)[0]->get_ref<const json::string_t&>()));
340 } break;
341 case Op::Last: {
342 const auto result = &get_arguments<1>(node)[0]->back();
343 data_eval_stack.push(result);
344 } break;
345 case Op::Length: {
346 const auto val = get_arguments<1>(node)[0];
347 if (val->is_string()) {
348 make_result(val->get_ref<const json::string_t&>().length());
349 } else {
350 make_result(val->size());
351 }
352 } break;
353 case Op::Lower: {
354 auto result = get_arguments<1>(node)[0]->get<json::string_t>();
355 std::transform(result.begin(), result.end(), result.begin(), [](char c) { return static_cast<char>(::tolower(c)); });
356 make_result(std::move(result));
357 } break;
358 case Op::Max: {
359 const auto args = get_arguments<1>(node);
360 const auto result = std::max_element(args[0]->begin(), args[0]->end());
361 data_eval_stack.push(&(*result));
362 } break;
363 case Op::Min: {
364 const auto args = get_arguments<1>(node);
365 const auto result = std::min_element(args[0]->begin(), args[0]->end());
366 data_eval_stack.push(&(*result));
367 } break;
368 case Op::Odd: {
369 make_result(get_arguments<1>(node)[0]->get<const json::number_integer_t>() % 2 != 0);
370 } break;
371 case Op::Range: {
372 std::vector<int> result(get_arguments<1>(node)[0]->get<const json::number_integer_t>());
373 std::iota(result.begin(), result.end(), 0);
374 make_result(std::move(result));
375 } break;
376 case Op::Round: {
377 const auto args = get_arguments<2>(node);
378 const int precision = args[1]->get<const json::number_integer_t>();
379 const double result = std::round(args[0]->get<const json::number_float_t>() * std::pow(10.0, precision)) / std::pow(10.0, precision);
380 if (precision == 0) {
381 make_result(int(result));
382 } else {
383 make_result(result);
384 }
385 } break;
386 case Op::Sort: {
387 auto result_ptr = std::make_shared<json>(get_arguments<1>(node)[0]->get<std::vector<json>>());
388 std::sort(result_ptr->begin(), result_ptr->end());
389 data_tmp_stack.push_back(result_ptr);
390 data_eval_stack.push(result_ptr.get());
391 } break;
392 case Op::Upper: {
393 auto result = get_arguments<1>(node)[0]->get<json::string_t>();
394 std::transform(result.begin(), result.end(), result.begin(), [](char c) { return static_cast<char>(::toupper(c)); });
395 make_result(std::move(result));
396 } break;
397 case Op::IsBoolean: {
398 make_result(get_arguments<1>(node)[0]->is_boolean());
399 } break;
400 case Op::IsNumber: {
401 make_result(get_arguments<1>(node)[0]->is_number());
402 } break;
403 case Op::IsInteger: {
404 make_result(get_arguments<1>(node)[0]->is_number_integer());
405 } break;
406 case Op::IsFloat: {
407 make_result(get_arguments<1>(node)[0]->is_number_float());
408 } break;
409 case Op::IsObject: {
410 make_result(get_arguments<1>(node)[0]->is_object());
411 } break;
412 case Op::IsArray: {
413 make_result(get_arguments<1>(node)[0]->is_array());
414 } break;
415 case Op::IsString: {
416 make_result(get_arguments<1>(node)[0]->is_string());
417 } break;
418 case Op::Callback: {
419 auto args = get_argument_vector(node);
420 make_result(node.callback(args));
421 } break;
422 case Op::Super: {
423 const auto args = get_argument_vector(node);
424 const size_t old_level = current_level;
425 const size_t level_diff = (args.size() == 1) ? args[0]->get<int>() : 1;
426 const size_t level = current_level + level_diff;
427
428 if (block_statement_stack.empty()) {
429 throw_renderer_error("super() call is not within a block", node);
430 }
431
432 if (level < 1 || level > template_stack.size() - 1) {
433 throw_renderer_error("level of super() call does not match parent templates (between 1 and " + std::to_string(template_stack.size() - 1) + ")", node);
434 }
435
436 const auto current_block_statement = block_statement_stack.back();
437 const Template* new_template = template_stack.at(level);
438 const Template* old_template = current_template;
439 const auto block_it = new_template->block_storage.find(current_block_statement->name);
440 if (block_it != new_template->block_storage.end()) {
441 current_template = new_template;
442 current_level = level;
443 block_it->second->block.accept(*this);
444 current_level = old_level;
445 current_template = old_template;
446 } else {
447 throw_renderer_error("could not find block with name '" + current_block_statement->name + "'", node);
448 }
449 make_result(nullptr);
450 } break;
451 case Op::Join: {
452 const auto args = get_arguments<2>(node);
453 const auto separator = args[1]->get<json::string_t>();
454 std::ostringstream os;
455 std::string sep;
456 for (const auto& value : *args[0]) {
457 os << sep;
458 if (value.is_string()) {
459 os << value.get<std::string>(); // otherwise the value is surrounded with ""
460 } else {
461 os << value.dump();
462 }
463 sep = separator;
464 }
465 make_result(os.str());
466 } break;
467 case Op::None:
468 break;
469 }
470 }
471
472 void visit(const ExpressionListNode& node) {
473 print_data(eval_expression_list(node));
474 }
475
476 void visit(const StatementNode&) {}
477
478 void visit(const ForStatementNode&) {}
479
480 void visit(const ForArrayStatementNode& node) {
481 const auto result = eval_expression_list(node.condition);
482 if (!result->is_array()) {
483 throw_renderer_error("object must be an array", node);
484 }
485
486 if (!current_loop_data->empty()) {
487 auto tmp = *current_loop_data; // Because of clang-3
488 (*current_loop_data)["parent"] = std::move(tmp);
489 }
490
491 size_t index = 0;
492 (*current_loop_data)["is_first"] = true;
493 (*current_loop_data)["is_last"] = (result->size() <= 1);
494 for (auto it = result->begin(); it != result->end(); ++it) {
495 additional_data[static_cast<std::string>(node.value)] = *it;
496
497 (*current_loop_data)["index"] = index;
498 (*current_loop_data)["index1"] = index + 1;
499 if (index == 1) {
500 (*current_loop_data)["is_first"] = false;
501 }
502 if (index == result->size() - 1) {
503 (*current_loop_data)["is_last"] = true;
504 }
505
506 node.body.accept(*this);
507 ++index;
508 }
509
510 additional_data[static_cast<std::string>(node.value)].clear();
511 if (!(*current_loop_data)["parent"].empty()) {
512 const auto tmp = (*current_loop_data)["parent"];
513 *current_loop_data = std::move(tmp);
514 } else {
515 current_loop_data = &additional_data["loop"];
516 }
517 }
518
519 void visit(const ForObjectStatementNode& node) {
520 const auto result = eval_expression_list(node.condition);
521 if (!result->is_object()) {
522 throw_renderer_error("object must be an object", node);
523 }
524
525 if (!current_loop_data->empty()) {
526 (*current_loop_data)["parent"] = std::move(*current_loop_data);
527 }
528
529 size_t index = 0;
530 (*current_loop_data)["is_first"] = true;
531 (*current_loop_data)["is_last"] = (result->size() <= 1);
532 for (auto it = result->begin(); it != result->end(); ++it) {
533 additional_data[static_cast<std::string>(node.key)] = it.key();
534 additional_data[static_cast<std::string>(node.value)] = it.value();
535
536 (*current_loop_data)["index"] = index;
537 (*current_loop_data)["index1"] = index + 1;
538 if (index == 1) {
539 (*current_loop_data)["is_first"] = false;
540 }
541 if (index == result->size() - 1) {
542 (*current_loop_data)["is_last"] = true;
543 }
544
545 node.body.accept(*this);
546 ++index;
547 }
548
549 additional_data[static_cast<std::string>(node.key)].clear();
550 additional_data[static_cast<std::string>(node.value)].clear();
551 if (!(*current_loop_data)["parent"].empty()) {
552 *current_loop_data = std::move((*current_loop_data)["parent"]);
553 } else {
554 current_loop_data = &additional_data["loop"];
555 }
556 }
557
558 void visit(const IfStatementNode& node) {
559 const auto result = eval_expression_list(node.condition);
560 if (truthy(result.get())) {
561 node.true_statement.accept(*this);
562 } else if (node.has_false_statement) {
563 node.false_statement.accept(*this);
564 }
565 }
566
567 void visit(const IncludeStatementNode& node) {
568 auto sub_renderer = Renderer(config, template_storage, function_storage);
569 const auto included_template_it = template_storage.find(node.file);
570 if (included_template_it != template_storage.end()) {
571 sub_renderer.render_to(*output_stream, included_template_it->second, *data_input, &additional_data);
572 } else if (config.throw_at_missing_includes) {
573 throw_renderer_error("include '" + node.file + "' not found", node);
574 }
575 }
576
577 void visit(const ExtendsStatementNode& node) {
578 const auto included_template_it = template_storage.find(node.file);
579 if (included_template_it != template_storage.end()) {
580 const Template* parent_template = &included_template_it->second;
581 render_to(*output_stream, *parent_template, *data_input, &additional_data);
582 break_rendering = true;
583 } else if (config.throw_at_missing_includes) {
584 throw_renderer_error("extends '" + node.file + "' not found", node);
585 }
586 }
587
588 void visit(const BlockStatementNode& node) {
589 const size_t old_level = current_level;
590 current_level = 0;
591 current_template = template_stack.front();
592 const auto block_it = current_template->block_storage.find(node.name);
593 if (block_it != current_template->block_storage.end()) {
594 block_statement_stack.emplace_back(&node);
595 block_it->second->block.accept(*this);
596 block_statement_stack.pop_back();
597 }
598 current_level = old_level;
599 current_template = template_stack.back();
600 }
601
602 void visit(const SetStatementNode& node) {
603 std::string ptr = node.key;
604 replace_substring(ptr, ".", "/");
605 ptr = "/" + ptr;
606 additional_data[json::json_pointer(ptr)] = *eval_expression_list(node.expression);
607 }
608
609public:
610 Renderer(const RenderConfig& config, const TemplateStorage& template_storage, const FunctionStorage& function_storage)
611 : config(config), template_storage(template_storage), function_storage(function_storage) {}
612
613 void render_to(std::ostream& os, const Template& tmpl, const json& data, json* loop_data = nullptr) {
614 output_stream = &os;
615 current_template = &tmpl;
616 data_input = &data;
617 if (loop_data) {
618 additional_data = *loop_data;
619 current_loop_data = &additional_data["loop"];
620 }
621
622 template_stack.emplace_back(current_template);
623 current_template->root.accept(*this);
624
625 data_tmp_stack.clear();
626 }
627};
628
629} // namespace inja
630
631#endif // INCLUDE_INJA_RENDERER_HPP_
Base node class for the abstract syntax tree (AST).
Definition: node.hpp:56
Definition: node.hpp:66
Definition: node.hpp:345
Definition: node.hpp:108
Definition: node.hpp:251
Definition: node.hpp:88
Definition: node.hpp:334
Definition: node.hpp:281
Definition: node.hpp:292
Definition: node.hpp:270
Definition: node.hpp:131
Class for builtin functions and user-defined callbacks.
Definition: function_storage.hpp:16
Definition: node.hpp:305
Definition: node.hpp:323
Definition: node.hpp:97
Definition: node.hpp:31
Class for rendering a Template with data.
Definition: renderer.hpp:21
Definition: node.hpp:358
Definition: node.hpp:263
Definition: node.hpp:77
Class for render configuration.
Definition: config.hpp:75
Definition: exceptions.hpp:32
Definition: exceptions.hpp:9
The main inja Template.
Definition: template.hpp:17