|
|
|
@@ -1,10 +1,8 @@
|
|
|
|
|
// Copyright (c) 2019 Pantor. All rights reserved.
|
|
|
|
|
// Copyright (c) 2020 Pantor. All rights reserved.
|
|
|
|
|
|
|
|
|
|
#ifndef INCLUDE_INJA_INJA_HPP_
|
|
|
|
|
#define INCLUDE_INJA_INJA_HPP_
|
|
|
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
|
|
#include <third-party/json.hpp>
|
|
|
|
|
|
|
|
|
|
// #include "environment.hpp"
|
|
|
|
@@ -14,6 +12,7 @@
|
|
|
|
|
#define INCLUDE_INJA_ENVIRONMENT_HPP_
|
|
|
|
|
|
|
|
|
|
#include <fstream>
|
|
|
|
|
#include <iostream>
|
|
|
|
|
#include <memory>
|
|
|
|
|
#include <sstream>
|
|
|
|
|
#include <string>
|
|
|
|
@@ -1461,7 +1460,9 @@ struct LexerConfig {
|
|
|
|
|
std::string statement_close_force_rstrip {"-%}"};
|
|
|
|
|
std::string line_statement {"##"};
|
|
|
|
|
std::string expression_open {"{{"};
|
|
|
|
|
std::string expression_open_force_lstrip {"{{-"};
|
|
|
|
|
std::string expression_close {"}}"};
|
|
|
|
|
std::string expression_close_force_rstrip {"-}}"};
|
|
|
|
|
std::string comment_open {"{#"};
|
|
|
|
|
std::string comment_close {"#}"};
|
|
|
|
|
std::string open_chars {"#{"};
|
|
|
|
@@ -1486,6 +1487,9 @@ struct LexerConfig {
|
|
|
|
|
if (open_chars.find(expression_open[0]) == std::string::npos) {
|
|
|
|
|
open_chars += expression_open[0];
|
|
|
|
|
}
|
|
|
|
|
if (open_chars.find(expression_open_force_lstrip[0]) == std::string::npos) {
|
|
|
|
|
open_chars += expression_open_force_lstrip[0];
|
|
|
|
|
}
|
|
|
|
|
if (open_chars.find(comment_open[0]) == std::string::npos) {
|
|
|
|
|
open_chars += comment_open[0];
|
|
|
|
|
}
|
|
|
|
@@ -1527,6 +1531,7 @@ using json = nlohmann::json;
|
|
|
|
|
|
|
|
|
|
using Arguments = std::vector<const json *>;
|
|
|
|
|
using CallbackFunction = std::function<json(Arguments &args)>;
|
|
|
|
|
using VoidCallbackFunction = std::function<void(Arguments &args)>;
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \brief Class for builtin functions and user-defined callbacks.
|
|
|
|
@@ -1583,14 +1588,15 @@ public:
|
|
|
|
|
None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const int VARIADIC {-1};
|
|
|
|
|
|
|
|
|
|
struct FunctionData {
|
|
|
|
|
Operation operation;
|
|
|
|
|
|
|
|
|
|
CallbackFunction callback;
|
|
|
|
|
explicit FunctionData(const Operation &op, const CallbackFunction &cb = CallbackFunction{}) : operation(op), callback(cb) {}
|
|
|
|
|
const Operation operation;
|
|
|
|
|
const CallbackFunction callback;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
const int VARIADIC {-1};
|
|
|
|
|
|
|
|
|
|
std::map<std::pair<std::string, int>, FunctionData> function_storage = {
|
|
|
|
|
{std::make_pair("at", 2), FunctionData { Operation::At }},
|
|
|
|
|
{std::make_pair("default", 2), FunctionData { Operation::Default }},
|
|
|
|
@@ -1642,7 +1648,7 @@ public:
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { Operation::None };
|
|
|
|
|
return FunctionData { Operation::None };
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@@ -1682,39 +1688,35 @@ struct SourceLocation {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct InjaError : public std::runtime_error {
|
|
|
|
|
std::string type;
|
|
|
|
|
std::string message;
|
|
|
|
|
const std::string type;
|
|
|
|
|
const std::string message;
|
|
|
|
|
|
|
|
|
|
bool has_location {false};
|
|
|
|
|
SourceLocation location;
|
|
|
|
|
const SourceLocation location;
|
|
|
|
|
|
|
|
|
|
InjaError(const std::string &type, const std::string &message)
|
|
|
|
|
: std::runtime_error("[inja.exception." + type + "] " + message), type(type), message(message) {}
|
|
|
|
|
explicit InjaError(const std::string &type, const std::string &message)
|
|
|
|
|
: std::runtime_error("[inja.exception." + type + "] " + message), type(type), message(message), location({0, 0}) {}
|
|
|
|
|
|
|
|
|
|
InjaError(const std::string &type, const std::string &message, SourceLocation location)
|
|
|
|
|
explicit InjaError(const std::string &type, const std::string &message, SourceLocation location)
|
|
|
|
|
: std::runtime_error("[inja.exception." + type + "] (at " + std::to_string(location.line) + ":" +
|
|
|
|
|
std::to_string(location.column) + ") " + message),
|
|
|
|
|
type(type), message(message), has_location(true), location(location) {}
|
|
|
|
|
type(type), message(message), location(location) {}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct ParserError : public InjaError {
|
|
|
|
|
ParserError(const std::string &message) : InjaError("parser_error", message) {}
|
|
|
|
|
ParserError(const std::string &message, SourceLocation location) : InjaError("parser_error", message, location) {}
|
|
|
|
|
explicit ParserError(const std::string &message, SourceLocation location) : InjaError("parser_error", message, location) {}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct RenderError : public InjaError {
|
|
|
|
|
RenderError(const std::string &message) : InjaError("render_error", message) {}
|
|
|
|
|
RenderError(const std::string &message, SourceLocation location) : InjaError("render_error", message, location) {}
|
|
|
|
|
explicit RenderError(const std::string &message, SourceLocation location) : InjaError("render_error", message, location) {}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct FileError : public InjaError {
|
|
|
|
|
FileError(const std::string &message) : InjaError("file_error", message) {}
|
|
|
|
|
FileError(const std::string &message, SourceLocation location) : InjaError("file_error", message, location) {}
|
|
|
|
|
explicit FileError(const std::string &message) : InjaError("file_error", message) {}
|
|
|
|
|
explicit FileError(const std::string &message, SourceLocation location) : InjaError("file_error", message, location) {}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct JsonError : public InjaError {
|
|
|
|
|
JsonError(const std::string &message) : InjaError("json_error", message) {}
|
|
|
|
|
JsonError(const std::string &message, SourceLocation location) : InjaError("json_error", message, location) {}
|
|
|
|
|
explicit JsonError(const std::string &message, SourceLocation location) : InjaError("json_error", message, location) {}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} // namespace inja
|
|
|
|
@@ -1874,7 +1876,7 @@ inline SourceLocation get_source_location(nonstd::string_view content, size_t po
|
|
|
|
|
size_t search_start = 0;
|
|
|
|
|
while (search_start <= sliced.size()) {
|
|
|
|
|
search_start = sliced.find("\n", search_start) + 1;
|
|
|
|
|
if (search_start <= 0) {
|
|
|
|
|
if (search_start == 0) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
count_lines += 1;
|
|
|
|
@@ -1897,6 +1899,7 @@ class Lexer {
|
|
|
|
|
enum class State {
|
|
|
|
|
Text,
|
|
|
|
|
ExpressionStart,
|
|
|
|
|
ExpressionStartForceLstrip,
|
|
|
|
|
ExpressionBody,
|
|
|
|
|
LineStart,
|
|
|
|
|
LineBody,
|
|
|
|
@@ -2146,7 +2149,7 @@ class Lexer {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
explicit Lexer(const LexerConfig &config) : config(config) {}
|
|
|
|
|
explicit Lexer(const LexerConfig &config) : config(config), state(State::Text), minus_state(MinusState::Number) {}
|
|
|
|
|
|
|
|
|
|
SourceLocation current_position() const {
|
|
|
|
|
return get_source_location(m_in, tok_start);
|
|
|
|
@@ -2158,6 +2161,11 @@ public:
|
|
|
|
|
pos = 0;
|
|
|
|
|
state = State::Text;
|
|
|
|
|
minus_state = MinusState::Number;
|
|
|
|
|
|
|
|
|
|
// Consume byte order mark (BOM) for UTF-8
|
|
|
|
|
if (inja::string_view::starts_with(m_in, "\xEF\xBB\xBF")) {
|
|
|
|
|
m_in = m_in.substr(3);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Token scan() {
|
|
|
|
@@ -2184,7 +2192,12 @@ public:
|
|
|
|
|
nonstd::string_view open_str = m_in.substr(pos);
|
|
|
|
|
bool must_lstrip = false;
|
|
|
|
|
if (inja::string_view::starts_with(open_str, config.expression_open)) {
|
|
|
|
|
state = State::ExpressionStart;
|
|
|
|
|
if (inja::string_view::starts_with(open_str, config.expression_open_force_lstrip)) {
|
|
|
|
|
state = State::ExpressionStartForceLstrip;
|
|
|
|
|
must_lstrip = true;
|
|
|
|
|
} else {
|
|
|
|
|
state = State::ExpressionStart;
|
|
|
|
|
}
|
|
|
|
|
} else if (inja::string_view::starts_with(open_str, config.statement_open)) {
|
|
|
|
|
if (inja::string_view::starts_with(open_str, config.statement_open_no_lstrip)) {
|
|
|
|
|
state = State::StatementStartNoLstrip;
|
|
|
|
@@ -2198,8 +2211,7 @@ public:
|
|
|
|
|
} else if (inja::string_view::starts_with(open_str, config.comment_open)) {
|
|
|
|
|
state = State::CommentStart;
|
|
|
|
|
must_lstrip = config.lstrip_blocks;
|
|
|
|
|
} else if ((pos == 0 || m_in[pos - 1] == '\n') &&
|
|
|
|
|
inja::string_view::starts_with(open_str, config.line_statement)) {
|
|
|
|
|
} else if ((pos == 0 || m_in[pos - 1] == '\n') && inja::string_view::starts_with(open_str, config.line_statement)) {
|
|
|
|
|
state = State::LineStart;
|
|
|
|
|
} else {
|
|
|
|
|
pos += 1; // wasn't actually an opening sequence
|
|
|
|
@@ -2221,6 +2233,11 @@ public:
|
|
|
|
|
pos += config.expression_open.size();
|
|
|
|
|
return make_token(Token::Kind::ExpressionOpen);
|
|
|
|
|
}
|
|
|
|
|
case State::ExpressionStartForceLstrip: {
|
|
|
|
|
state = State::ExpressionBody;
|
|
|
|
|
pos += config.expression_open_force_lstrip.size();
|
|
|
|
|
return make_token(Token::Kind::ExpressionOpen);
|
|
|
|
|
}
|
|
|
|
|
case State::LineStart: {
|
|
|
|
|
state = State::LineBody;
|
|
|
|
|
pos += config.line_statement.size();
|
|
|
|
@@ -2247,7 +2264,7 @@ public:
|
|
|
|
|
return make_token(Token::Kind::CommentOpen);
|
|
|
|
|
}
|
|
|
|
|
case State::ExpressionBody:
|
|
|
|
|
return scan_body(config.expression_close, Token::Kind::ExpressionClose);
|
|
|
|
|
return scan_body(config.expression_close, Token::Kind::ExpressionClose, config.expression_close_force_rstrip);
|
|
|
|
|
case State::LineBody:
|
|
|
|
|
return scan_body("\n", Token::Kind::LineStatementClose);
|
|
|
|
|
case State::StatementBody:
|
|
|
|
@@ -2313,6 +2330,7 @@ class ForArrayStatementNode;
|
|
|
|
|
class ForObjectStatementNode;
|
|
|
|
|
class IfStatementNode;
|
|
|
|
|
class IncludeStatementNode;
|
|
|
|
|
class SetStatementNode;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NodeVisitor {
|
|
|
|
@@ -2330,6 +2348,7 @@ public:
|
|
|
|
|
virtual void visit(const ForObjectStatementNode& node) = 0;
|
|
|
|
|
virtual void visit(const IfStatementNode& node) = 0;
|
|
|
|
|
virtual void visit(const IncludeStatementNode& node) = 0;
|
|
|
|
|
virtual void visit(const SetStatementNode& node) = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@@ -2359,7 +2378,7 @@ public:
|
|
|
|
|
|
|
|
|
|
class TextNode : public AstNode {
|
|
|
|
|
public:
|
|
|
|
|
size_t length;
|
|
|
|
|
const size_t length;
|
|
|
|
|
|
|
|
|
|
explicit TextNode(size_t pos, size_t length): AstNode(pos), length(length) { }
|
|
|
|
|
|
|
|
|
@@ -2379,7 +2398,7 @@ public:
|
|
|
|
|
|
|
|
|
|
class LiteralNode : public ExpressionNode {
|
|
|
|
|
public:
|
|
|
|
|
nlohmann::json value;
|
|
|
|
|
const nlohmann::json value;
|
|
|
|
|
|
|
|
|
|
explicit LiteralNode(const nlohmann::json& value, size_t pos) : ExpressionNode(pos), value(value) { }
|
|
|
|
|
|
|
|
|
@@ -2390,8 +2409,8 @@ public:
|
|
|
|
|
|
|
|
|
|
class JsonNode : public ExpressionNode {
|
|
|
|
|
public:
|
|
|
|
|
std::string name;
|
|
|
|
|
std::string ptr {""};
|
|
|
|
|
const std::string name;
|
|
|
|
|
const json::json_pointer ptr;
|
|
|
|
|
|
|
|
|
|
static std::string convert_dot_to_json_ptr(nonstd::string_view ptr_name) {
|
|
|
|
|
std::string result;
|
|
|
|
@@ -2404,9 +2423,7 @@ public:
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
explicit JsonNode(nonstd::string_view ptr_name, size_t pos) : ExpressionNode(pos), name(ptr_name) {
|
|
|
|
|
ptr = convert_dot_to_json_ptr(ptr_name);
|
|
|
|
|
}
|
|
|
|
|
explicit JsonNode(nonstd::string_view ptr_name, size_t pos) : ExpressionNode(pos), name(ptr_name), ptr(json::json_pointer(convert_dot_to_json_ptr(ptr_name))) { }
|
|
|
|
|
|
|
|
|
|
void accept(NodeVisitor& v) const {
|
|
|
|
|
v.visit(*this);
|
|
|
|
@@ -2537,18 +2554,18 @@ class ForStatementNode : public StatementNode {
|
|
|
|
|
public:
|
|
|
|
|
ExpressionListNode condition;
|
|
|
|
|
BlockNode body;
|
|
|
|
|
BlockNode *parent;
|
|
|
|
|
BlockNode *const parent;
|
|
|
|
|
|
|
|
|
|
ForStatementNode(size_t pos) : StatementNode(pos) { }
|
|
|
|
|
ForStatementNode(BlockNode *const parent, size_t pos) : StatementNode(pos), parent(parent) { }
|
|
|
|
|
|
|
|
|
|
virtual void accept(NodeVisitor& v) const = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class ForArrayStatementNode : public ForStatementNode {
|
|
|
|
|
public:
|
|
|
|
|
nonstd::string_view value;
|
|
|
|
|
const std::string value;
|
|
|
|
|
|
|
|
|
|
explicit ForArrayStatementNode(nonstd::string_view value, size_t pos) : ForStatementNode(pos), value(value) { }
|
|
|
|
|
explicit ForArrayStatementNode(const std::string& value, BlockNode *const parent, size_t pos) : ForStatementNode(parent, pos), value(value) { }
|
|
|
|
|
|
|
|
|
|
void accept(NodeVisitor& v) const {
|
|
|
|
|
v.visit(*this);
|
|
|
|
@@ -2557,10 +2574,10 @@ public:
|
|
|
|
|
|
|
|
|
|
class ForObjectStatementNode : public ForStatementNode {
|
|
|
|
|
public:
|
|
|
|
|
nonstd::string_view key;
|
|
|
|
|
nonstd::string_view value;
|
|
|
|
|
const std::string key;
|
|
|
|
|
const std::string value;
|
|
|
|
|
|
|
|
|
|
explicit ForObjectStatementNode(nonstd::string_view key, nonstd::string_view value, size_t pos) : ForStatementNode(pos), key(key), value(value) { }
|
|
|
|
|
explicit ForObjectStatementNode(const std::string& key, const std::string& value, BlockNode *const parent, size_t pos) : ForStatementNode(parent, pos), key(key), value(value) { }
|
|
|
|
|
|
|
|
|
|
void accept(NodeVisitor& v) const {
|
|
|
|
|
v.visit(*this);
|
|
|
|
@@ -2572,13 +2589,13 @@ public:
|
|
|
|
|
ExpressionListNode condition;
|
|
|
|
|
BlockNode true_statement;
|
|
|
|
|
BlockNode false_statement;
|
|
|
|
|
BlockNode *parent;
|
|
|
|
|
BlockNode *const parent;
|
|
|
|
|
|
|
|
|
|
bool is_nested;
|
|
|
|
|
const bool is_nested;
|
|
|
|
|
bool has_false_statement {false};
|
|
|
|
|
|
|
|
|
|
explicit IfStatementNode(size_t pos) : StatementNode(pos), is_nested(false) { }
|
|
|
|
|
explicit IfStatementNode(bool is_nested, size_t pos) : StatementNode(pos), is_nested(is_nested) { }
|
|
|
|
|
explicit IfStatementNode(BlockNode *const parent, size_t pos) : StatementNode(pos), parent(parent), is_nested(false) { }
|
|
|
|
|
explicit IfStatementNode(bool is_nested, BlockNode *const parent, size_t pos) : StatementNode(pos), parent(parent), is_nested(is_nested) { }
|
|
|
|
|
|
|
|
|
|
void accept(NodeVisitor& v) const {
|
|
|
|
|
v.visit(*this);
|
|
|
|
@@ -2587,7 +2604,7 @@ public:
|
|
|
|
|
|
|
|
|
|
class IncludeStatementNode : public StatementNode {
|
|
|
|
|
public:
|
|
|
|
|
std::string file;
|
|
|
|
|
const std::string file;
|
|
|
|
|
|
|
|
|
|
explicit IncludeStatementNode(const std::string& file, size_t pos) : StatementNode(pos), file(file) { }
|
|
|
|
|
|
|
|
|
@@ -2596,6 +2613,18 @@ public:
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class SetStatementNode : public StatementNode {
|
|
|
|
|
public:
|
|
|
|
|
const std::string key;
|
|
|
|
|
ExpressionListNode expression;
|
|
|
|
|
|
|
|
|
|
explicit SetStatementNode(const std::string& key, size_t pos) : StatementNode(pos), key(key) { }
|
|
|
|
|
|
|
|
|
|
void accept(NodeVisitor& v) const {
|
|
|
|
|
v.visit(*this);
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} // namespace inja
|
|
|
|
|
|
|
|
|
|
#endif // INCLUDE_INJA_NODE_HPP_
|
|
|
|
@@ -2672,6 +2701,8 @@ class StatisticsVisitor : public NodeVisitor {
|
|
|
|
|
|
|
|
|
|
void visit(const IncludeStatementNode&) { }
|
|
|
|
|
|
|
|
|
|
void visit(const SetStatementNode&) { }
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
unsigned int variable_counter;
|
|
|
|
|
|
|
|
|
@@ -2835,14 +2866,14 @@ class Parser {
|
|
|
|
|
add_json_literal(tmpl.content.c_str());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Operator
|
|
|
|
|
} else if (tok.text == "and" || tok.text == "or" || tok.text == "in" || tok.text == "not") {
|
|
|
|
|
goto parse_operator;
|
|
|
|
|
|
|
|
|
|
// Functions
|
|
|
|
|
} else if (peek_tok.kind == Token::Kind::LeftParen) {
|
|
|
|
|
operator_stack.emplace(std::make_shared<FunctionNode>(static_cast<std::string>(tok.text), tok.text.data() - tmpl.content.c_str()));
|
|
|
|
|
function_stack.emplace(operator_stack.top().get(), current_paren_level);
|
|
|
|
|
|
|
|
|
|
// Operator
|
|
|
|
|
} else if (tok.text == "and" || tok.text == "or" || tok.text == "in" || tok.text == "not") {
|
|
|
|
|
goto parse_operator;
|
|
|
|
|
function_stack.emplace(operator_stack.top().get(), current_paren_level);
|
|
|
|
|
|
|
|
|
|
// Variables
|
|
|
|
|
} else {
|
|
|
|
@@ -2964,12 +2995,12 @@ class Parser {
|
|
|
|
|
} break;
|
|
|
|
|
case Token::Kind::RightParen: {
|
|
|
|
|
current_paren_level -= 1;
|
|
|
|
|
while (operator_stack.top()->operation != FunctionStorage::Operation::ParenLeft) {
|
|
|
|
|
while (!operator_stack.empty() && operator_stack.top()->operation != FunctionStorage::Operation::ParenLeft) {
|
|
|
|
|
current_expression_list->rpn_output.emplace_back(operator_stack.top());
|
|
|
|
|
operator_stack.pop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (operator_stack.top()->operation == FunctionStorage::Operation::ParenLeft) {
|
|
|
|
|
if (!operator_stack.empty() && operator_stack.top()->operation == FunctionStorage::Operation::ParenLeft) {
|
|
|
|
|
operator_stack.pop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -2984,6 +3015,12 @@ class Parser {
|
|
|
|
|
func->callback = function_data.callback;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (operator_stack.empty()) {
|
|
|
|
|
throw_parser_error("internal error at function " + func->name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
current_expression_list->rpn_output.emplace_back(operator_stack.top());
|
|
|
|
|
operator_stack.pop();
|
|
|
|
|
function_stack.pop();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -3010,9 +3047,8 @@ class Parser {
|
|
|
|
|
if (tok.text == static_cast<decltype(tok.text)>("if")) {
|
|
|
|
|
get_next_token();
|
|
|
|
|
|
|
|
|
|
auto if_statement_node = std::make_shared<IfStatementNode>(tok.text.data() - tmpl.content.c_str());
|
|
|
|
|
auto if_statement_node = std::make_shared<IfStatementNode>(current_block, tok.text.data() - tmpl.content.c_str());
|
|
|
|
|
current_block->nodes.emplace_back(if_statement_node);
|
|
|
|
|
if_statement_node->parent = current_block;
|
|
|
|
|
if_statement_stack.emplace(if_statement_node.get());
|
|
|
|
|
current_block = &if_statement_node->true_statement;
|
|
|
|
|
current_expression_list = &if_statement_node->condition;
|
|
|
|
@@ -3035,9 +3071,8 @@ class Parser {
|
|
|
|
|
if (tok.kind == Token::Kind::Id && tok.text == static_cast<decltype(tok.text)>("if")) {
|
|
|
|
|
get_next_token();
|
|
|
|
|
|
|
|
|
|
auto if_statement_node = std::make_shared<IfStatementNode>(true, tok.text.data() - tmpl.content.c_str());
|
|
|
|
|
auto if_statement_node = std::make_shared<IfStatementNode>(true, current_block, tok.text.data() - tmpl.content.c_str());
|
|
|
|
|
current_block->nodes.emplace_back(if_statement_node);
|
|
|
|
|
if_statement_node->parent = current_block;
|
|
|
|
|
if_statement_stack.emplace(if_statement_node.get());
|
|
|
|
|
current_block = &if_statement_node->true_statement;
|
|
|
|
|
current_expression_list = &if_statement_node->condition;
|
|
|
|
@@ -3086,15 +3121,14 @@ class Parser {
|
|
|
|
|
value_token = tok;
|
|
|
|
|
get_next_token();
|
|
|
|
|
|
|
|
|
|
for_statement_node = std::make_shared<ForObjectStatementNode>(key_token.text, value_token.text, tok.text.data() - tmpl.content.c_str());
|
|
|
|
|
for_statement_node = std::make_shared<ForObjectStatementNode>(static_cast<std::string>(key_token.text), static_cast<std::string>(value_token.text), current_block, tok.text.data() - tmpl.content.c_str());
|
|
|
|
|
|
|
|
|
|
// Array type
|
|
|
|
|
} else {
|
|
|
|
|
for_statement_node = std::make_shared<ForArrayStatementNode>(value_token.text, tok.text.data() - tmpl.content.c_str());
|
|
|
|
|
for_statement_node = std::make_shared<ForArrayStatementNode>(static_cast<std::string>(value_token.text), current_block, tok.text.data() - tmpl.content.c_str());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
current_block->nodes.emplace_back(for_statement_node);
|
|
|
|
|
for_statement_node->parent = current_block;
|
|
|
|
|
for_statement_stack.emplace(for_statement_node.get());
|
|
|
|
|
current_block = &for_statement_node->body;
|
|
|
|
|
current_expression_list = &for_statement_node->condition;
|
|
|
|
@@ -3145,6 +3179,29 @@ class Parser {
|
|
|
|
|
|
|
|
|
|
get_next_token();
|
|
|
|
|
|
|
|
|
|
} else if (tok.text == static_cast<decltype(tok.text)>("set")) {
|
|
|
|
|
get_next_token();
|
|
|
|
|
|
|
|
|
|
if (tok.kind != Token::Kind::Id) {
|
|
|
|
|
throw_parser_error("expected variable name, got '" + tok.describe() + "'");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string key = static_cast<std::string>(tok.text);
|
|
|
|
|
get_next_token();
|
|
|
|
|
|
|
|
|
|
auto set_statement_node = std::make_shared<SetStatementNode>(key, tok.text.data() - tmpl.content.c_str());
|
|
|
|
|
current_block->nodes.emplace_back(set_statement_node);
|
|
|
|
|
current_expression_list = &set_statement_node->expression;
|
|
|
|
|
|
|
|
|
|
if (tok.text != static_cast<decltype(tok.text)>("=")) {
|
|
|
|
|
throw_parser_error("expected '=', got '" + tok.describe() + "'");
|
|
|
|
|
}
|
|
|
|
|
get_next_token();
|
|
|
|
|
|
|
|
|
|
if (!parse_expression(tmpl, closing)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
@@ -3292,32 +3349,30 @@ class Renderer : public NodeVisitor {
|
|
|
|
|
const json *json_input;
|
|
|
|
|
std::ostream *output_stream;
|
|
|
|
|
|
|
|
|
|
json json_loop_data;
|
|
|
|
|
json* current_loop_data = &json_loop_data["loop"];
|
|
|
|
|
json json_additional_data;
|
|
|
|
|
json* current_loop_data = &json_additional_data["loop"];
|
|
|
|
|
|
|
|
|
|
std::vector<std::shared_ptr<json>> json_tmp_stack;
|
|
|
|
|
std::stack<const json*> json_eval_stack;
|
|
|
|
|
std::stack<const JsonNode*> not_found_stack;
|
|
|
|
|
|
|
|
|
|
bool truthy(const json* data) const {
|
|
|
|
|
if (data->empty()) {
|
|
|
|
|
return false;
|
|
|
|
|
if (data->is_boolean()) {
|
|
|
|
|
return data->get<bool>();
|
|
|
|
|
} else if (data->is_number()) {
|
|
|
|
|
return (*data != 0);
|
|
|
|
|
} else if (data->is_string()) {
|
|
|
|
|
return !data->empty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
return data->get<bool>();
|
|
|
|
|
} catch (json::type_error &e) {
|
|
|
|
|
throw JsonError(e.what());
|
|
|
|
|
} else if (data->is_null()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return !data->empty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void print_json(const json* value) {
|
|
|
|
|
void print_json(const std::shared_ptr<json> value) {
|
|
|
|
|
if (value->is_string()) {
|
|
|
|
|
*output_stream << value->get_ref<const std::string &>();
|
|
|
|
|
*output_stream << value->get_ref<const json::string_t&>();
|
|
|
|
|
} else if (value->is_number_integer()) {
|
|
|
|
|
*output_stream << value->get<const json::number_integer_t>();
|
|
|
|
|
} else if (value->is_null()) {
|
|
|
|
|
} else {
|
|
|
|
|
*output_stream << value->dump();
|
|
|
|
|
}
|
|
|
|
@@ -3330,9 +3385,7 @@ class Renderer : public NodeVisitor {
|
|
|
|
|
|
|
|
|
|
if (json_eval_stack.empty()) {
|
|
|
|
|
throw_renderer_error("empty expression", expression_list);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (json_eval_stack.size() != 1) {
|
|
|
|
|
} else if (json_eval_stack.size() != 1) {
|
|
|
|
|
throw_renderer_error("malformed expression", expression_list);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -3416,17 +3469,13 @@ class Renderer : public NodeVisitor {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void visit(const JsonNode& node) {
|
|
|
|
|
auto ptr = json::json_pointer(node.ptr);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// First try to evaluate as a loop variable
|
|
|
|
|
if (json_loop_data.contains(ptr)) {
|
|
|
|
|
json_eval_stack.push(&json_loop_data.at(ptr));
|
|
|
|
|
} else {
|
|
|
|
|
json_eval_stack.push(&json_input->at(ptr));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} catch (std::exception &) {
|
|
|
|
|
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) {
|
|
|
|
@@ -3509,7 +3558,7 @@ class Renderer : public NodeVisitor {
|
|
|
|
|
case Op::Add: {
|
|
|
|
|
auto args = get_arguments<2>(node);
|
|
|
|
|
if (args[0]->is_string() && args[1]->is_string()) {
|
|
|
|
|
result_ptr = std::make_shared<json>(args[0]->get<std::string>() + args[1]->get<std::string>());
|
|
|
|
|
result_ptr = std::make_shared<json>(args[0]->get_ref<const std::string&>() + args[1]->get_ref<const std::string&>());
|
|
|
|
|
json_tmp_stack.push_back(result_ptr);
|
|
|
|
|
} else if (args[0]->is_number_integer() && args[1]->is_number_integer()) {
|
|
|
|
|
result_ptr = std::make_shared<json>(args[0]->get<int>() + args[1]->get<int>());
|
|
|
|
@@ -3741,7 +3790,7 @@ class Renderer : public NodeVisitor {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void visit(const ExpressionListNode& node) {
|
|
|
|
|
print_json(eval_expression_list(node).get());
|
|
|
|
|
print_json(eval_expression_list(node));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void visit(const StatementNode&) { }
|
|
|
|
@@ -3759,24 +3808,31 @@ class Renderer : public NodeVisitor {
|
|
|
|
|
(*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_loop_data[static_cast<std::string>(node.value)] = *it;
|
|
|
|
|
json_additional_data[static_cast<std::string>(node.value)] = *it;
|
|
|
|
|
|
|
|
|
|
size_t index = std::distance(result->begin(), it);
|
|
|
|
|
(*current_loop_data)["index"] = index;
|
|
|
|
|
(*current_loop_data)["index1"] = index + 1;
|
|
|
|
|
(*current_loop_data)["is_first"] = (index == 0);
|
|
|
|
|
(*current_loop_data)["is_last"] = (index == result->size() - 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_loop_data[static_cast<std::string>(node.value)].clear();
|
|
|
|
|
json_additional_data[static_cast<std::string>(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_loop_data["loop"];
|
|
|
|
|
current_loop_data = &json_additional_data["loop"];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -3790,25 +3846,32 @@ class Renderer : public NodeVisitor {
|
|
|
|
|
(*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_loop_data[static_cast<std::string>(node.key)] = it.key();
|
|
|
|
|
json_loop_data[static_cast<std::string>(node.value)] = it.value();
|
|
|
|
|
json_additional_data[static_cast<std::string>(node.key)] = it.key();
|
|
|
|
|
json_additional_data[static_cast<std::string>(node.value)] = it.value();
|
|
|
|
|
|
|
|
|
|
size_t index = std::distance(result->begin(), it);
|
|
|
|
|
(*current_loop_data)["index"] = index;
|
|
|
|
|
(*current_loop_data)["index1"] = index + 1;
|
|
|
|
|
(*current_loop_data)["is_first"] = (index == 0);
|
|
|
|
|
(*current_loop_data)["is_last"] = (index == result->size() - 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_loop_data[static_cast<std::string>(node.key)].clear();
|
|
|
|
|
json_loop_data[static_cast<std::string>(node.value)].clear();
|
|
|
|
|
json_additional_data[static_cast<std::string>(node.key)].clear();
|
|
|
|
|
json_additional_data[static_cast<std::string>(node.value)].clear();
|
|
|
|
|
if (!(*current_loop_data)["parent"].empty()) {
|
|
|
|
|
*current_loop_data = std::move((*current_loop_data)["parent"]);
|
|
|
|
|
} else {
|
|
|
|
|
current_loop_data = &json_loop_data["loop"];
|
|
|
|
|
current_loop_data = &json_additional_data["loop"];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -3826,12 +3889,16 @@ class Renderer : public NodeVisitor {
|
|
|
|
|
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_loop_data);
|
|
|
|
|
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) { }
|
|
|
|
@@ -3841,7 +3908,8 @@ public:
|
|
|
|
|
current_template = &tmpl;
|
|
|
|
|
json_input = &data;
|
|
|
|
|
if (loop_data) {
|
|
|
|
|
json_loop_data = *loop_data;
|
|
|
|
|
json_additional_data = *loop_data;
|
|
|
|
|
current_loop_data = &json_additional_data["loop"];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
current_template->root.accept(*this);
|
|
|
|
@@ -3906,7 +3974,9 @@ public:
|
|
|
|
|
/// Sets the opener and closer for template expressions
|
|
|
|
|
void set_expression(const std::string &open, const std::string &close) {
|
|
|
|
|
lexer_config.expression_open = open;
|
|
|
|
|
lexer_config.expression_open_force_lstrip = open + "-";
|
|
|
|
|
lexer_config.expression_close = close;
|
|
|
|
|
lexer_config.expression_close_force_rstrip = "-" + close;
|
|
|
|
|
lexer_config.update_open_chars();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -4015,7 +4085,14 @@ public:
|
|
|
|
|
@brief Adds a variadic callback
|
|
|
|
|
*/
|
|
|
|
|
void add_callback(const std::string &name, const CallbackFunction &callback) {
|
|
|
|
|
function_storage.add_callback(name, -1, callback);
|
|
|
|
|
add_callback(name, -1, callback);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
@brief Adds a variadic void callback
|
|
|
|
|
*/
|
|
|
|
|
void add_void_callback(const std::string &name, const VoidCallbackFunction &callback) {
|
|
|
|
|
add_void_callback(name, -1, callback);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@@ -4025,6 +4102,13 @@ public:
|
|
|
|
|
function_storage.add_callback(name, num_args, callback);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
@brief Adds a void callback with given number or arguments
|
|
|
|
|
*/
|
|
|
|
|
void add_void_callback(const std::string &name, int num_args, const VoidCallbackFunction &callback) {
|
|
|
|
|
function_storage.add_callback(name, num_args, [callback](Arguments& args) { callback(args); return json(); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Includes a template with a given name into the environment.
|
|
|
|
|
* Then, a template can be rendered in another template using the
|
|
|
|
|
* include "<name>" syntax.
|
|
|
|
|