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