// Copyright (c) 2020 Pantor. All rights reserved. #ifndef INCLUDE_INJA_RENDERER_HPP_ #define INCLUDE_INJA_RENDERER_HPP_ #include #include #include #include #include #include #include "config.hpp" #include "exceptions.hpp" #include "node.hpp" #include "template.hpp" #include "utils.hpp" namespace inja { /*! * \brief Class for rendering a Template with data. */ class Renderer : public NodeVisitor { using Op = FunctionStorage::Operation; const RenderConfig config; const Template *current_template; const TemplateStorage &template_storage; const FunctionStorage &function_storage; const json *json_input; std::ostream *output_stream; json json_additional_data; json* current_loop_data = &json_additional_data["loop"]; std::vector> json_tmp_stack; std::stack json_eval_stack; std::stack not_found_stack; bool truthy(const json* data) const { if (data->is_boolean()) { return data->get(); } else if (data->is_number()) { return (*data != 0); } else if (data->is_null()) { return false; } return !data->empty(); } void print_json(const std::shared_ptr value) { if (value->is_string()) { *output_stream << value->get_ref(); } else if (value->is_number_integer()) { *output_stream << value->get(); } else if (value->is_null()) { } else { *output_stream << value->dump(); } } const std::shared_ptr eval_expression_list(const ExpressionListNode& expression_list) { for (auto& expression : expression_list.rpn_output) { expression->accept(*this); } if (json_eval_stack.empty()) { throw_renderer_error("empty expression", expression_list); } else if (json_eval_stack.size() != 1) { throw_renderer_error("malformed expression", expression_list); } auto result = json_eval_stack.top(); json_eval_stack.pop(); if (!result) { if (not_found_stack.empty()) { throw_renderer_error("expression could not be evaluated", expression_list); } auto node = not_found_stack.top(); not_found_stack.pop(); throw_renderer_error("variable '" + static_cast(node->name) + "' not found", *node); } return std::make_shared(*result); } void throw_renderer_error(const std::string &message, const AstNode& node) { SourceLocation loc = get_source_location(current_template->content, node.pos); INJA_THROW(RenderError(message, loc)); } template std::array get_arguments(const AstNode& node) { if (json_eval_stack.size() < N) { throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(json_eval_stack.size()), node); } std::array result; for (size_t i = 0; i < N; i += 1) { result[N - i - 1] = json_eval_stack.top(); json_eval_stack.pop(); if (!result[N - i - 1]) { auto json_node = not_found_stack.top(); not_found_stack.pop(); if (throw_not_found) { throw_renderer_error("variable '" + static_cast(json_node->name) + "' not found", *json_node); } } } return result; } template Arguments get_argument_vector(size_t N, const AstNode& node) { if (json_eval_stack.size() < N) { throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(json_eval_stack.size()), node); } Arguments result {N}; for (size_t i = 0; i < N; i += 1) { result[N - i - 1] = json_eval_stack.top(); json_eval_stack.pop(); if (!result[N - i - 1]) { auto json_node = not_found_stack.top(); not_found_stack.pop(); if (throw_not_found) { throw_renderer_error("variable '" + static_cast(json_node->name) + "' not found", *json_node); } } } return result; } void visit(const BlockNode& node) { for (auto& n : node.nodes) { n->accept(*this); } } void visit(const TextNode& node) { output_stream->write(current_template->content.c_str() + node.pos, node.length); } void visit(const ExpressionNode&) { } void visit(const LiteralNode& node) { json_eval_stack.push(&node.value); } void visit(const JsonNode& node) { if (json_additional_data.contains(node.ptr)) { json_eval_stack.push(&(json_additional_data[node.ptr])); } else if (json_input->contains(node.ptr)) { json_eval_stack.push(&(*json_input)[node.ptr]); } else { // Try to evaluate as a no-argument callback auto function_data = function_storage.find_function(node.name, 0); if (function_data.operation == FunctionStorage::Operation::Callback) { Arguments empty_args {}; auto value = std::make_shared(function_data.callback(empty_args)); json_tmp_stack.push_back(value); json_eval_stack.push(value.get()); } else { json_eval_stack.push(nullptr); not_found_stack.emplace(&node); } } } void visit(const FunctionNode& node) { std::shared_ptr result_ptr; switch (node.operation) { case Op::Not: { auto args = get_arguments<1>(node); result_ptr = std::make_shared(!truthy(args[0])); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::And: { auto args = get_arguments<2>(node); result_ptr = std::make_shared(truthy(args[0]) && truthy(args[1])); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::Or: { auto args = get_arguments<2>(node); result_ptr = std::make_shared(truthy(args[0]) || truthy(args[1])); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::In: { auto args = get_arguments<2>(node); result_ptr = std::make_shared(std::find(args[1]->begin(), args[1]->end(), *args[0]) != args[1]->end()); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::Equal: { auto args = get_arguments<2>(node); result_ptr = std::make_shared(*args[0] == *args[1]); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::NotEqual: { auto args = get_arguments<2>(node); result_ptr = std::make_shared(*args[0] != *args[1]); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::Greater: { auto args = get_arguments<2>(node); result_ptr = std::make_shared(*args[0] > *args[1]); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::GreaterEqual: { auto args = get_arguments<2>(node); result_ptr = std::make_shared(*args[0] >= *args[1]); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::Less: { auto args = get_arguments<2>(node); result_ptr = std::make_shared(*args[0] < *args[1]); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::LessEqual: { auto args = get_arguments<2>(node); result_ptr = std::make_shared(*args[0] <= *args[1]); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::Add: { auto args = get_arguments<2>(node); if (args[0]->is_string() && args[1]->is_string()) { result_ptr = std::make_shared(args[0]->get_ref() + args[1]->get_ref()); json_tmp_stack.push_back(result_ptr); } else if (args[0]->is_number_integer() && args[1]->is_number_integer()) { result_ptr = std::make_shared(args[0]->get() + args[1]->get()); json_tmp_stack.push_back(result_ptr); } else { result_ptr = std::make_shared(args[0]->get() + args[1]->get()); json_tmp_stack.push_back(result_ptr); } json_eval_stack.push(result_ptr.get()); } break; case Op::Subtract: { auto args = get_arguments<2>(node); if (args[0]->is_number_integer() && args[1]->is_number_integer()) { result_ptr = std::make_shared(args[0]->get() - args[1]->get()); json_tmp_stack.push_back(result_ptr); } else { result_ptr = std::make_shared(args[0]->get() - args[1]->get()); json_tmp_stack.push_back(result_ptr); } json_eval_stack.push(result_ptr.get()); } break; case Op::Multiplication: { auto args = get_arguments<2>(node); if (args[0]->is_number_integer() && args[1]->is_number_integer()) { result_ptr = std::make_shared(args[0]->get() * args[1]->get()); json_tmp_stack.push_back(result_ptr); } else { result_ptr = std::make_shared(args[0]->get() * args[1]->get()); json_tmp_stack.push_back(result_ptr); } json_eval_stack.push(result_ptr.get()); } break; case Op::Division: { auto args = get_arguments<2>(node); if (args[1]->get() == 0) { throw_renderer_error("division by zero", node); } result_ptr = std::make_shared(args[0]->get() / args[1]->get()); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::Power: { auto args = get_arguments<2>(node); if (args[0]->is_number_integer() && args[1]->get() >= 0) { int result = std::pow(args[0]->get(), args[1]->get()); result_ptr = std::make_shared(std::move(result)); json_tmp_stack.push_back(result_ptr); } else { double result = std::pow(args[0]->get(), args[1]->get()); result_ptr = std::make_shared(std::move(result)); json_tmp_stack.push_back(result_ptr); } json_eval_stack.push(result_ptr.get()); } break; case Op::Modulo: { auto args = get_arguments<2>(node); result_ptr = std::make_shared(args[0]->get() % args[1]->get()); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::AtId: { json_eval_stack.pop(); // Pop id nullptr auto container = get_arguments<1, false>(node)[0]; if (not_found_stack.empty()) { throw_renderer_error("could not find element with given name", node); } auto id_node = not_found_stack.top(); not_found_stack.pop(); json_eval_stack.push(&container->at(id_node->name)); } break; case Op::At: { auto args = get_arguments<2>(node); json_eval_stack.push(&args[0]->at(args[1]->get())); } break; case Op::Default: { auto default_arg = get_arguments<1>(node)[0]; auto test_arg = get_arguments<1, false>(node)[0]; json_eval_stack.push(test_arg ? test_arg : default_arg); } break; case Op::DivisibleBy: { auto args = get_arguments<2>(node); int divisor = args[1]->get(); result_ptr = std::make_shared((divisor != 0) && (args[0]->get() % divisor == 0)); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::Even: { result_ptr = std::make_shared(get_arguments<1>(node)[0]->get() % 2 == 0); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::Exists: { auto &&name = get_arguments<1>(node)[0]->get_ref(); result_ptr = std::make_shared(json_input->contains(json::json_pointer(JsonNode::convert_dot_to_json_ptr(name)))); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::ExistsInObject: { auto args = get_arguments<2>(node); auto &&name = args[1]->get_ref(); result_ptr = std::make_shared(args[0]->find(name) != args[0]->end()); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::First: { auto result = &get_arguments<1>(node)[0]->front(); json_eval_stack.push(result); } break; case Op::Float: { result_ptr = std::make_shared(std::stod(get_arguments<1>(node)[0]->get_ref())); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::Int: { result_ptr = std::make_shared(std::stoi(get_arguments<1>(node)[0]->get_ref())); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::Last: { auto result = &get_arguments<1>(node)[0]->back(); json_eval_stack.push(result); } break; case Op::Length: { auto val = get_arguments<1>(node)[0]; if (val->is_string()) { result_ptr = std::make_shared(val->get_ref().length()); } else { result_ptr = std::make_shared(val->size()); } json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::Lower: { std::string result = get_arguments<1>(node)[0]->get(); std::transform(result.begin(), result.end(), result.begin(), ::tolower); result_ptr = std::make_shared(std::move(result)); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::Max: { auto args = get_arguments<1>(node); auto result = std::max_element(args[0]->begin(), args[0]->end()); json_eval_stack.push(&(*result)); } break; case Op::Min: { auto args = get_arguments<1>(node); auto result = std::min_element(args[0]->begin(), args[0]->end()); json_eval_stack.push(&(*result)); } break; case Op::Odd: { result_ptr = std::make_shared(get_arguments<1>(node)[0]->get() % 2 != 0); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::Range: { std::vector result(get_arguments<1>(node)[0]->get()); std::iota(result.begin(), result.end(), 0); result_ptr = std::make_shared(std::move(result)); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::Round: { auto args = get_arguments<2>(node); int precision = args[1]->get(); double result = std::round(args[0]->get() * std::pow(10.0, precision)) / std::pow(10.0, precision); result_ptr = std::make_shared(std::move(result)); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::Sort: { result_ptr = std::make_shared(get_arguments<1>(node)[0]->get>()); std::sort(result_ptr->begin(), result_ptr->end()); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::Upper: { std::string result = get_arguments<1>(node)[0]->get(); std::transform(result.begin(), result.end(), result.begin(), ::toupper); result_ptr = std::make_shared(std::move(result)); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::IsBoolean: { result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_boolean()); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::IsNumber: { result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_number()); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::IsInteger: { result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_number_integer()); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::IsFloat: { result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_number_float()); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::IsObject: { result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_object()); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::IsArray: { result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_array()); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::IsString: { result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_string()); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::Callback: { auto args = get_argument_vector(node.number_args, node); result_ptr = std::make_shared(node.callback(args)); json_tmp_stack.push_back(result_ptr); json_eval_stack.push(result_ptr.get()); } break; case Op::ParenLeft: case Op::ParenRight: case Op::None: break; } } void visit(const ExpressionListNode& node) { print_json(eval_expression_list(node)); } void visit(const StatementNode&) { } void visit(const ForStatementNode&) { } void visit(const ForArrayStatementNode& node) { auto result = eval_expression_list(node.condition); if (!result->is_array()) { throw_renderer_error("object must be an array", node); } if (!current_loop_data->empty()) { auto tmp = *current_loop_data; // Because of clang-3 (*current_loop_data)["parent"] = std::move(tmp); } size_t index = 0; (*current_loop_data)["is_first"] = true; (*current_loop_data)["is_last"] = (result->size() <= 1); for (auto it = result->begin(); it != result->end(); ++it) { json_additional_data[static_cast(node.value)] = *it; (*current_loop_data)["index"] = index; (*current_loop_data)["index1"] = index + 1; if (index == 1) { (*current_loop_data)["is_first"] = false; } if (index == result->size() - 1) { (*current_loop_data)["is_last"] = true; } node.body.accept(*this); ++index; } json_additional_data[static_cast(node.value)].clear(); if (!(*current_loop_data)["parent"].empty()) { auto tmp = (*current_loop_data)["parent"]; *current_loop_data = std::move(tmp); } else { current_loop_data = &json_additional_data["loop"]; } } void visit(const ForObjectStatementNode& node) { auto result = eval_expression_list(node.condition); if (!result->is_object()) { throw_renderer_error("object must be an object", node); } if (!current_loop_data->empty()) { (*current_loop_data)["parent"] = std::move(*current_loop_data); } size_t index = 0; (*current_loop_data)["is_first"] = true; (*current_loop_data)["is_last"] = (result->size() <= 1); for (auto it = result->begin(); it != result->end(); ++it) { json_additional_data[static_cast(node.key)] = it.key(); json_additional_data[static_cast(node.value)] = it.value(); (*current_loop_data)["index"] = index; (*current_loop_data)["index1"] = index + 1; if (index == 1) { (*current_loop_data)["is_first"] = false; } if (index == result->size() - 1) { (*current_loop_data)["is_last"] = true; } node.body.accept(*this); ++index; } json_additional_data[static_cast(node.key)].clear(); json_additional_data[static_cast(node.value)].clear(); if (!(*current_loop_data)["parent"].empty()) { *current_loop_data = std::move((*current_loop_data)["parent"]); } else { current_loop_data = &json_additional_data["loop"]; } } void visit(const IfStatementNode& node) { auto result = eval_expression_list(node.condition); if (truthy(result.get())) { node.true_statement.accept(*this); } else if (node.has_false_statement) { node.false_statement.accept(*this); } } void visit(const IncludeStatementNode& node) { auto sub_renderer = Renderer(config, template_storage, function_storage); auto included_template_it = template_storage.find(node.file); if (included_template_it != template_storage.end()) { sub_renderer.render_to(*output_stream, included_template_it->second, *json_input, &json_additional_data); } else if (config.throw_at_missing_includes) { throw_renderer_error("include '" + node.file + "' not found", node); } } void visit(const SetStatementNode& node) { json_additional_data[node.key] = *eval_expression_list(node.expression); } public: Renderer(const RenderConfig& config, const TemplateStorage &template_storage, const FunctionStorage &function_storage) : config(config), template_storage(template_storage), function_storage(function_storage) { } void render_to(std::ostream &os, const Template &tmpl, const json &data, json *loop_data = nullptr) { output_stream = &os; current_template = &tmpl; json_input = &data; if (loop_data) { json_additional_data = *loop_data; current_loop_data = &json_additional_data["loop"]; } current_template->root.accept(*this); json_tmp_stack.clear(); } }; } // namespace inja #endif // INCLUDE_INJA_RENDERER_HPP_