mirror of https://github.com/PurpleI2P/i2pd.git
I2P: End-to-End encrypted and anonymous Internet
https://i2pd.website/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
660 lines
23 KiB
660 lines
23 KiB
3 years ago
|
#ifndef INCLUDE_INJA_PARSER_HPP_
|
||
|
#define INCLUDE_INJA_PARSER_HPP_
|
||
|
|
||
|
#include <limits>
|
||
|
#include <stack>
|
||
|
#include <string>
|
||
|
#include <utility>
|
||
|
#include <vector>
|
||
|
|
||
|
#include "config.hpp"
|
||
|
#include "exceptions.hpp"
|
||
|
#include "function_storage.hpp"
|
||
|
#include "lexer.hpp"
|
||
|
#include "node.hpp"
|
||
|
#include "template.hpp"
|
||
|
#include "token.hpp"
|
||
|
#include "utils.hpp"
|
||
|
|
||
|
namespace inja {
|
||
|
|
||
|
/*!
|
||
|
* \brief Class for parsing an inja Template.
|
||
|
*/
|
||
|
class Parser {
|
||
|
const ParserConfig& config;
|
||
|
|
||
|
Lexer lexer;
|
||
|
TemplateStorage& template_storage;
|
||
|
const FunctionStorage& function_storage;
|
||
|
|
||
|
Token tok, peek_tok;
|
||
|
bool have_peek_tok {false};
|
||
|
|
||
|
size_t current_paren_level {0};
|
||
|
size_t current_bracket_level {0};
|
||
|
size_t current_brace_level {0};
|
||
|
|
||
|
std::string_view literal_start;
|
||
|
|
||
|
BlockNode* current_block {nullptr};
|
||
|
ExpressionListNode* current_expression_list {nullptr};
|
||
|
std::stack<std::pair<FunctionNode*, size_t>> function_stack;
|
||
|
std::vector<std::shared_ptr<ExpressionNode>> arguments;
|
||
|
|
||
|
std::stack<std::shared_ptr<FunctionNode>> operator_stack;
|
||
|
std::stack<IfStatementNode*> if_statement_stack;
|
||
|
std::stack<ForStatementNode*> for_statement_stack;
|
||
|
std::stack<BlockStatementNode*> block_statement_stack;
|
||
|
|
||
|
inline void throw_parser_error(const std::string& message) const {
|
||
|
INJA_THROW(ParserError(message, lexer.current_position()));
|
||
|
}
|
||
|
|
||
|
inline void get_next_token() {
|
||
|
if (have_peek_tok) {
|
||
|
tok = peek_tok;
|
||
|
have_peek_tok = false;
|
||
|
} else {
|
||
|
tok = lexer.scan();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
inline void get_peek_token() {
|
||
|
if (!have_peek_tok) {
|
||
|
peek_tok = lexer.scan();
|
||
|
have_peek_tok = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
inline void add_literal(const char* content_ptr) {
|
||
|
std::string_view data_text(literal_start.data(), tok.text.data() - literal_start.data() + tok.text.size());
|
||
|
arguments.emplace_back(std::make_shared<LiteralNode>(data_text, data_text.data() - content_ptr));
|
||
|
}
|
||
|
|
||
|
inline void add_operator() {
|
||
|
auto function = operator_stack.top();
|
||
|
operator_stack.pop();
|
||
|
|
||
|
for (int i = 0; i < function->number_args; ++i) {
|
||
|
function->arguments.insert(function->arguments.begin(), arguments.back());
|
||
|
arguments.pop_back();
|
||
|
}
|
||
|
arguments.emplace_back(function);
|
||
|
}
|
||
|
|
||
|
void add_to_template_storage(std::string_view path, std::string& template_name) {
|
||
|
if (template_storage.find(template_name) != template_storage.end()) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
std::string original_path = static_cast<std::string>(path);
|
||
|
std::string original_name = template_name;
|
||
|
|
||
|
if (config.search_included_templates_in_files) {
|
||
|
// Build the relative path
|
||
|
template_name = original_path + original_name;
|
||
|
if (template_name.compare(0, 2, "./") == 0) {
|
||
|
template_name.erase(0, 2);
|
||
|
}
|
||
|
|
||
|
if (template_storage.find(template_name) == template_storage.end()) {
|
||
|
// Load file
|
||
|
std::ifstream file;
|
||
|
file.open(template_name);
|
||
|
if (!file.fail()) {
|
||
|
std::string text((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||
|
|
||
|
auto include_template = Template(text);
|
||
|
template_storage.emplace(template_name, include_template);
|
||
|
parse_into_template(template_storage[template_name], template_name);
|
||
|
return;
|
||
|
} else if (!config.include_callback) {
|
||
|
INJA_THROW(FileError("failed accessing file at '" + template_name + "'"));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Try include callback
|
||
|
if (config.include_callback) {
|
||
|
auto include_template = config.include_callback(original_path, original_name);
|
||
|
template_storage.emplace(template_name, include_template);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
std::string parse_filename(const Token& tok) const {
|
||
|
if (tok.kind != Token::Kind::String) {
|
||
|
throw_parser_error("expected string, got '" + tok.describe() + "'");
|
||
|
}
|
||
|
|
||
|
if (tok.text.length() < 2) {
|
||
|
throw_parser_error("expected filename, got '" + static_cast<std::string>(tok.text) + "'");
|
||
|
}
|
||
|
|
||
|
// Remove first and last character ""
|
||
|
return std::string {tok.text.substr(1, tok.text.length() - 2)};
|
||
|
}
|
||
|
|
||
|
bool parse_expression(Template& tmpl, Token::Kind closing) {
|
||
|
while (tok.kind != closing && tok.kind != Token::Kind::Eof) {
|
||
|
// Literals
|
||
|
switch (tok.kind) {
|
||
|
case Token::Kind::String: {
|
||
|
if (current_brace_level == 0 && current_bracket_level == 0) {
|
||
|
literal_start = tok.text;
|
||
|
add_literal(tmpl.content.c_str());
|
||
|
}
|
||
|
} break;
|
||
|
case Token::Kind::Number: {
|
||
|
if (current_brace_level == 0 && current_bracket_level == 0) {
|
||
|
literal_start = tok.text;
|
||
|
add_literal(tmpl.content.c_str());
|
||
|
}
|
||
|
} break;
|
||
|
case Token::Kind::LeftBracket: {
|
||
|
if (current_brace_level == 0 && current_bracket_level == 0) {
|
||
|
literal_start = tok.text;
|
||
|
}
|
||
|
current_bracket_level += 1;
|
||
|
} break;
|
||
|
case Token::Kind::LeftBrace: {
|
||
|
if (current_brace_level == 0 && current_bracket_level == 0) {
|
||
|
literal_start = tok.text;
|
||
|
}
|
||
|
current_brace_level += 1;
|
||
|
} break;
|
||
|
case Token::Kind::RightBracket: {
|
||
|
if (current_bracket_level == 0) {
|
||
|
throw_parser_error("unexpected ']'");
|
||
|
}
|
||
|
|
||
|
current_bracket_level -= 1;
|
||
|
if (current_brace_level == 0 && current_bracket_level == 0) {
|
||
|
add_literal(tmpl.content.c_str());
|
||
|
}
|
||
|
} break;
|
||
|
case Token::Kind::RightBrace: {
|
||
|
if (current_brace_level == 0) {
|
||
|
throw_parser_error("unexpected '}'");
|
||
|
}
|
||
|
|
||
|
current_brace_level -= 1;
|
||
|
if (current_brace_level == 0 && current_bracket_level == 0) {
|
||
|
add_literal(tmpl.content.c_str());
|
||
|
}
|
||
|
} break;
|
||
|
case Token::Kind::Id: {
|
||
|
get_peek_token();
|
||
|
|
||
|
// Data Literal
|
||
|
if (tok.text == static_cast<decltype(tok.text)>("true") || tok.text == static_cast<decltype(tok.text)>("false") ||
|
||
|
tok.text == static_cast<decltype(tok.text)>("null")) {
|
||
|
if (current_brace_level == 0 && current_bracket_level == 0) {
|
||
|
literal_start = tok.text;
|
||
|
add_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);
|
||
|
|
||
|
// Variables
|
||
|
} else {
|
||
|
arguments.emplace_back(std::make_shared<DataNode>(static_cast<std::string>(tok.text), tok.text.data() - tmpl.content.c_str()));
|
||
|
}
|
||
|
|
||
|
// Operators
|
||
|
} break;
|
||
|
case Token::Kind::Equal:
|
||
|
case Token::Kind::NotEqual:
|
||
|
case Token::Kind::GreaterThan:
|
||
|
case Token::Kind::GreaterEqual:
|
||
|
case Token::Kind::LessThan:
|
||
|
case Token::Kind::LessEqual:
|
||
|
case Token::Kind::Plus:
|
||
|
case Token::Kind::Minus:
|
||
|
case Token::Kind::Times:
|
||
|
case Token::Kind::Slash:
|
||
|
case Token::Kind::Power:
|
||
|
case Token::Kind::Percent:
|
||
|
case Token::Kind::Dot: {
|
||
|
|
||
|
parse_operator:
|
||
|
FunctionStorage::Operation operation;
|
||
|
switch (tok.kind) {
|
||
|
case Token::Kind::Id: {
|
||
|
if (tok.text == "and") {
|
||
|
operation = FunctionStorage::Operation::And;
|
||
|
} else if (tok.text == "or") {
|
||
|
operation = FunctionStorage::Operation::Or;
|
||
|
} else if (tok.text == "in") {
|
||
|
operation = FunctionStorage::Operation::In;
|
||
|
} else if (tok.text == "not") {
|
||
|
operation = FunctionStorage::Operation::Not;
|
||
|
} else {
|
||
|
throw_parser_error("unknown operator in parser.");
|
||
|
}
|
||
|
} break;
|
||
|
case Token::Kind::Equal: {
|
||
|
operation = FunctionStorage::Operation::Equal;
|
||
|
} break;
|
||
|
case Token::Kind::NotEqual: {
|
||
|
operation = FunctionStorage::Operation::NotEqual;
|
||
|
} break;
|
||
|
case Token::Kind::GreaterThan: {
|
||
|
operation = FunctionStorage::Operation::Greater;
|
||
|
} break;
|
||
|
case Token::Kind::GreaterEqual: {
|
||
|
operation = FunctionStorage::Operation::GreaterEqual;
|
||
|
} break;
|
||
|
case Token::Kind::LessThan: {
|
||
|
operation = FunctionStorage::Operation::Less;
|
||
|
} break;
|
||
|
case Token::Kind::LessEqual: {
|
||
|
operation = FunctionStorage::Operation::LessEqual;
|
||
|
} break;
|
||
|
case Token::Kind::Plus: {
|
||
|
operation = FunctionStorage::Operation::Add;
|
||
|
} break;
|
||
|
case Token::Kind::Minus: {
|
||
|
operation = FunctionStorage::Operation::Subtract;
|
||
|
} break;
|
||
|
case Token::Kind::Times: {
|
||
|
operation = FunctionStorage::Operation::Multiplication;
|
||
|
} break;
|
||
|
case Token::Kind::Slash: {
|
||
|
operation = FunctionStorage::Operation::Division;
|
||
|
} break;
|
||
|
case Token::Kind::Power: {
|
||
|
operation = FunctionStorage::Operation::Power;
|
||
|
} break;
|
||
|
case Token::Kind::Percent: {
|
||
|
operation = FunctionStorage::Operation::Modulo;
|
||
|
} break;
|
||
|
case Token::Kind::Dot: {
|
||
|
operation = FunctionStorage::Operation::AtId;
|
||
|
} break;
|
||
|
default: {
|
||
|
throw_parser_error("unknown operator in parser.");
|
||
|
}
|
||
|
}
|
||
|
auto function_node = std::make_shared<FunctionNode>(operation, tok.text.data() - tmpl.content.c_str());
|
||
|
|
||
|
while (!operator_stack.empty() &&
|
||
|
((operator_stack.top()->precedence > function_node->precedence) ||
|
||
|
(operator_stack.top()->precedence == function_node->precedence && function_node->associativity == FunctionNode::Associativity::Left)) &&
|
||
|
(operator_stack.top()->operation != FunctionStorage::Operation::ParenLeft)) {
|
||
|
add_operator();
|
||
|
}
|
||
|
|
||
|
operator_stack.emplace(function_node);
|
||
|
} break;
|
||
|
case Token::Kind::Comma: {
|
||
|
if (current_brace_level == 0 && current_bracket_level == 0) {
|
||
|
if (function_stack.empty()) {
|
||
|
throw_parser_error("unexpected ','");
|
||
|
}
|
||
|
|
||
|
function_stack.top().first->number_args += 1;
|
||
|
}
|
||
|
} break;
|
||
|
case Token::Kind::Colon: {
|
||
|
if (current_brace_level == 0 && current_bracket_level == 0) {
|
||
|
throw_parser_error("unexpected ':'");
|
||
|
}
|
||
|
} break;
|
||
|
case Token::Kind::LeftParen: {
|
||
|
current_paren_level += 1;
|
||
|
operator_stack.emplace(std::make_shared<FunctionNode>(FunctionStorage::Operation::ParenLeft, tok.text.data() - tmpl.content.c_str()));
|
||
|
|
||
|
get_peek_token();
|
||
|
if (peek_tok.kind == Token::Kind::RightParen) {
|
||
|
if (!function_stack.empty() && function_stack.top().second == current_paren_level - 1) {
|
||
|
function_stack.top().first->number_args = 0;
|
||
|
}
|
||
|
}
|
||
|
} break;
|
||
|
case Token::Kind::RightParen: {
|
||
|
current_paren_level -= 1;
|
||
|
while (!operator_stack.empty() && operator_stack.top()->operation != FunctionStorage::Operation::ParenLeft) {
|
||
|
add_operator();
|
||
|
}
|
||
|
|
||
|
if (!operator_stack.empty() && operator_stack.top()->operation == FunctionStorage::Operation::ParenLeft) {
|
||
|
operator_stack.pop();
|
||
|
}
|
||
|
|
||
|
if (!function_stack.empty() && function_stack.top().second == current_paren_level) {
|
||
|
auto func = function_stack.top().first;
|
||
|
auto function_data = function_storage.find_function(func->name, func->number_args);
|
||
|
if (function_data.operation == FunctionStorage::Operation::None) {
|
||
|
throw_parser_error("unknown function " + func->name);
|
||
|
}
|
||
|
func->operation = function_data.operation;
|
||
|
if (function_data.operation == FunctionStorage::Operation::Callback) {
|
||
|
func->callback = function_data.callback;
|
||
|
}
|
||
|
|
||
|
if (operator_stack.empty()) {
|
||
|
throw_parser_error("internal error at function " + func->name);
|
||
|
}
|
||
|
|
||
|
add_operator();
|
||
|
function_stack.pop();
|
||
|
}
|
||
|
}
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
get_next_token();
|
||
|
}
|
||
|
|
||
|
while (!operator_stack.empty()) {
|
||
|
add_operator();
|
||
|
}
|
||
|
|
||
|
if (arguments.size() == 1) {
|
||
|
current_expression_list->root = arguments[0];
|
||
|
arguments = {};
|
||
|
} else if (arguments.size() > 1) {
|
||
|
throw_parser_error("malformed expression");
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool parse_statement(Template& tmpl, Token::Kind closing, std::string_view path) {
|
||
|
if (tok.kind != Token::Kind::Id) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (tok.text == static_cast<decltype(tok.text)>("if")) {
|
||
|
get_next_token();
|
||
|
|
||
|
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_stack.emplace(if_statement_node.get());
|
||
|
current_block = &if_statement_node->true_statement;
|
||
|
current_expression_list = &if_statement_node->condition;
|
||
|
|
||
|
if (!parse_expression(tmpl, closing)) {
|
||
|
return false;
|
||
|
}
|
||
|
} else if (tok.text == static_cast<decltype(tok.text)>("else")) {
|
||
|
if (if_statement_stack.empty()) {
|
||
|
throw_parser_error("else without matching if");
|
||
|
}
|
||
|
auto& if_statement_data = if_statement_stack.top();
|
||
|
get_next_token();
|
||
|
|
||
|
if_statement_data->has_false_statement = true;
|
||
|
current_block = &if_statement_data->false_statement;
|
||
|
|
||
|
// Chained else if
|
||
|
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, current_block, tok.text.data() - tmpl.content.c_str());
|
||
|
current_block->nodes.emplace_back(if_statement_node);
|
||
|
if_statement_stack.emplace(if_statement_node.get());
|
||
|
current_block = &if_statement_node->true_statement;
|
||
|
current_expression_list = &if_statement_node->condition;
|
||
|
|
||
|
if (!parse_expression(tmpl, closing)) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
} else if (tok.text == static_cast<decltype(tok.text)>("endif")) {
|
||
|
if (if_statement_stack.empty()) {
|
||
|
throw_parser_error("endif without matching if");
|
||
|
}
|
||
|
|
||
|
// Nested if statements
|
||
|
while (if_statement_stack.top()->is_nested) {
|
||
|
if_statement_stack.pop();
|
||
|
}
|
||
|
|
||
|
auto& if_statement_data = if_statement_stack.top();
|
||
|
get_next_token();
|
||
|
|
||
|
current_block = if_statement_data->parent;
|
||
|
if_statement_stack.pop();
|
||
|
} else if (tok.text == static_cast<decltype(tok.text)>("block")) {
|
||
|
get_next_token();
|
||
|
|
||
|
if (tok.kind != Token::Kind::Id) {
|
||
|
throw_parser_error("expected block name, got '" + tok.describe() + "'");
|
||
|
}
|
||
|
|
||
|
const std::string block_name = static_cast<std::string>(tok.text);
|
||
|
|
||
|
auto block_statement_node = std::make_shared<BlockStatementNode>(current_block, block_name, tok.text.data() - tmpl.content.c_str());
|
||
|
current_block->nodes.emplace_back(block_statement_node);
|
||
|
block_statement_stack.emplace(block_statement_node.get());
|
||
|
current_block = &block_statement_node->block;
|
||
|
auto success = tmpl.block_storage.emplace(block_name, block_statement_node);
|
||
|
if (!success.second) {
|
||
|
throw_parser_error("block with the name '" + block_name + "' does already exist");
|
||
|
}
|
||
|
|
||
|
get_next_token();
|
||
|
} else if (tok.text == static_cast<decltype(tok.text)>("endblock")) {
|
||
|
if (block_statement_stack.empty()) {
|
||
|
throw_parser_error("endblock without matching block");
|
||
|
}
|
||
|
|
||
|
auto& block_statement_data = block_statement_stack.top();
|
||
|
get_next_token();
|
||
|
|
||
|
current_block = block_statement_data->parent;
|
||
|
block_statement_stack.pop();
|
||
|
} else if (tok.text == static_cast<decltype(tok.text)>("for")) {
|
||
|
get_next_token();
|
||
|
|
||
|
// options: for a in arr; for a, b in obj
|
||
|
if (tok.kind != Token::Kind::Id) {
|
||
|
throw_parser_error("expected id, got '" + tok.describe() + "'");
|
||
|
}
|
||
|
|
||
|
Token value_token = tok;
|
||
|
get_next_token();
|
||
|
|
||
|
// Object type
|
||
|
std::shared_ptr<ForStatementNode> for_statement_node;
|
||
|
if (tok.kind == Token::Kind::Comma) {
|
||
|
get_next_token();
|
||
|
if (tok.kind != Token::Kind::Id) {
|
||
|
throw_parser_error("expected id, got '" + tok.describe() + "'");
|
||
|
}
|
||
|
|
||
|
Token key_token = std::move(value_token);
|
||
|
value_token = tok;
|
||
|
get_next_token();
|
||
|
|
||
|
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>(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_stack.emplace(for_statement_node.get());
|
||
|
current_block = &for_statement_node->body;
|
||
|
current_expression_list = &for_statement_node->condition;
|
||
|
|
||
|
if (tok.kind != Token::Kind::Id || tok.text != static_cast<decltype(tok.text)>("in")) {
|
||
|
throw_parser_error("expected 'in', got '" + tok.describe() + "'");
|
||
|
}
|
||
|
get_next_token();
|
||
|
|
||
|
if (!parse_expression(tmpl, closing)) {
|
||
|
return false;
|
||
|
}
|
||
|
} else if (tok.text == static_cast<decltype(tok.text)>("endfor")) {
|
||
|
if (for_statement_stack.empty()) {
|
||
|
throw_parser_error("endfor without matching for");
|
||
|
}
|
||
|
|
||
|
auto& for_statement_data = for_statement_stack.top();
|
||
|
get_next_token();
|
||
|
|
||
|
current_block = for_statement_data->parent;
|
||
|
for_statement_stack.pop();
|
||
|
} else if (tok.text == static_cast<decltype(tok.text)>("include")) {
|
||
|
get_next_token();
|
||
|
|
||
|
std::string template_name = parse_filename(tok);
|
||
|
add_to_template_storage(path, template_name);
|
||
|
|
||
|
current_block->nodes.emplace_back(std::make_shared<IncludeStatementNode>(template_name, tok.text.data() - tmpl.content.c_str()));
|
||
|
|
||
|
get_next_token();
|
||
|
} else if (tok.text == static_cast<decltype(tok.text)>("extends")) {
|
||
|
get_next_token();
|
||
|
|
||
|
std::string template_name = parse_filename(tok);
|
||
|
add_to_template_storage(path, template_name);
|
||
|
|
||
|
current_block->nodes.emplace_back(std::make_shared<ExtendsStatementNode>(template_name, tok.text.data() - tmpl.content.c_str()));
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void parse_into(Template& tmpl, std::string_view path) {
|
||
|
lexer.start(tmpl.content);
|
||
|
current_block = &tmpl.root;
|
||
|
|
||
|
for (;;) {
|
||
|
get_next_token();
|
||
|
switch (tok.kind) {
|
||
|
case Token::Kind::Eof: {
|
||
|
if (!if_statement_stack.empty()) {
|
||
|
throw_parser_error("unmatched if");
|
||
|
}
|
||
|
if (!for_statement_stack.empty()) {
|
||
|
throw_parser_error("unmatched for");
|
||
|
}
|
||
|
}
|
||
|
return;
|
||
|
case Token::Kind::Text: {
|
||
|
current_block->nodes.emplace_back(std::make_shared<TextNode>(tok.text.data() - tmpl.content.c_str(), tok.text.size()));
|
||
|
} break;
|
||
|
case Token::Kind::StatementOpen: {
|
||
|
get_next_token();
|
||
|
if (!parse_statement(tmpl, Token::Kind::StatementClose, path)) {
|
||
|
throw_parser_error("expected statement, got '" + tok.describe() + "'");
|
||
|
}
|
||
|
if (tok.kind != Token::Kind::StatementClose) {
|
||
|
throw_parser_error("expected statement close, got '" + tok.describe() + "'");
|
||
|
}
|
||
|
} break;
|
||
|
case Token::Kind::LineStatementOpen: {
|
||
|
get_next_token();
|
||
|
if (!parse_statement(tmpl, Token::Kind::LineStatementClose, path)) {
|
||
|
throw_parser_error("expected statement, got '" + tok.describe() + "'");
|
||
|
}
|
||
|
if (tok.kind != Token::Kind::LineStatementClose && tok.kind != Token::Kind::Eof) {
|
||
|
throw_parser_error("expected line statement close, got '" + tok.describe() + "'");
|
||
|
}
|
||
|
} break;
|
||
|
case Token::Kind::ExpressionOpen: {
|
||
|
get_next_token();
|
||
|
|
||
|
auto expression_list_node = std::make_shared<ExpressionListNode>(tok.text.data() - tmpl.content.c_str());
|
||
|
current_block->nodes.emplace_back(expression_list_node);
|
||
|
current_expression_list = expression_list_node.get();
|
||
|
|
||
|
if (!parse_expression(tmpl, Token::Kind::ExpressionClose)) {
|
||
|
throw_parser_error("expected expression, got '" + tok.describe() + "'");
|
||
|
}
|
||
|
|
||
|
if (tok.kind != Token::Kind::ExpressionClose) {
|
||
|
throw_parser_error("expected expression close, got '" + tok.describe() + "'");
|
||
|
}
|
||
|
} break;
|
||
|
case Token::Kind::CommentOpen: {
|
||
|
get_next_token();
|
||
|
if (tok.kind != Token::Kind::CommentClose) {
|
||
|
throw_parser_error("expected comment close, got '" + tok.describe() + "'");
|
||
|
}
|
||
|
} break;
|
||
|
default: {
|
||
|
throw_parser_error("unexpected token '" + tok.describe() + "'");
|
||
|
} break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public:
|
||
|
explicit Parser(const ParserConfig& parser_config, const LexerConfig& lexer_config, TemplateStorage& template_storage,
|
||
|
const FunctionStorage& function_storage)
|
||
|
: config(parser_config), lexer(lexer_config), template_storage(template_storage), function_storage(function_storage) {}
|
||
|
|
||
|
Template parse(std::string_view input, std::string_view path) {
|
||
|
auto result = Template(static_cast<std::string>(input));
|
||
|
parse_into(result, path);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
Template parse(std::string_view input) {
|
||
|
return parse(input, "./");
|
||
|
}
|
||
|
|
||
|
void parse_into_template(Template& tmpl, std::string_view filename) {
|
||
|
std::string_view path = filename.substr(0, filename.find_last_of("/\\") + 1);
|
||
|
|
||
|
// StringRef path = sys::path::parent_path(filename);
|
||
|
auto sub_parser = Parser(config, lexer.get_config(), template_storage, function_storage);
|
||
|
sub_parser.parse_into(tmpl, path);
|
||
|
}
|
||
|
|
||
|
std::string load_file(const std::string& filename) {
|
||
|
std::ifstream file;
|
||
|
file.open(filename);
|
||
|
if (file.fail()) {
|
||
|
INJA_THROW(FileError("failed accessing file at '" + filename + "'"));
|
||
|
}
|
||
|
std::string text((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||
|
return text;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
} // namespace inja
|
||
|
|
||
|
#endif // INCLUDE_INJA_PARSER_HPP_
|