2024-05-09 13:51:25 -04:00
|
|
|
/*
|
|
|
|
* <Short Description>
|
|
|
|
* Copyright (C) 2024 Brett Terpstra
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
#include <blt/parse/templating.h>
|
|
|
|
#include <blt/std/string.h>
|
|
|
|
#include <cctype>
|
|
|
|
#include "blt/std/logging.h"
|
|
|
|
|
|
|
|
namespace blt
|
|
|
|
{
|
|
|
|
|
|
|
|
bool isNonStringNext(char c)
|
|
|
|
{
|
|
|
|
switch (c)
|
|
|
|
{
|
|
|
|
case '$':
|
|
|
|
case '{':
|
|
|
|
case '}':
|
|
|
|
case '(':
|
|
|
|
case ')':
|
|
|
|
case '"':
|
|
|
|
case '^':
|
|
|
|
case '!':
|
|
|
|
case '&':
|
|
|
|
case ';':
|
|
|
|
case ',':
|
|
|
|
case '.':
|
|
|
|
case '|':
|
2024-05-09 21:53:08 -04:00
|
|
|
case '+':
|
2024-05-09 13:51:25 -04:00
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
blt::expected<std::vector<template_token_data_t>, template_tokenizer_failure_t> template_engine_t::process_string(std::string_view str)
|
|
|
|
{
|
|
|
|
std::vector<template_token_data_t> tokens;
|
|
|
|
|
|
|
|
template_char_consumer_t consumer(str);
|
|
|
|
|
|
|
|
i64 start = -1;
|
|
|
|
size_t paren_level = 0;
|
|
|
|
size_t level = 0;
|
|
|
|
bool open = false;
|
|
|
|
while (consumer.hasNext())
|
|
|
|
{
|
|
|
|
i64 current_start = static_cast<i64>(consumer.getCurrentIndex());
|
|
|
|
char c = consumer.consume();
|
|
|
|
switch (c)
|
|
|
|
{
|
|
|
|
case '$':
|
|
|
|
tokens.emplace_back(template_token_t::IDENT, level, consumer.from(current_start, current_start + 1));
|
|
|
|
if (consumer.next() == '{')
|
|
|
|
{
|
|
|
|
paren_level = 0;
|
|
|
|
open = true;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
case '{':
|
|
|
|
tokens.emplace_back(template_token_t::CURLY_OPEN, level, consumer.from(current_start, current_start + 1));
|
|
|
|
if (open)
|
|
|
|
level++;
|
|
|
|
continue;
|
|
|
|
case '}':
|
|
|
|
tokens.emplace_back(template_token_t::CURLY_CLOSE, level, consumer.from(current_start, current_start + 1));
|
|
|
|
if (open)
|
|
|
|
level--;
|
|
|
|
if (level == 0)
|
|
|
|
{
|
|
|
|
open = false;
|
|
|
|
if (paren_level != 0)
|
|
|
|
return blt::unexpected(template_tokenizer_failure_t::MISMATCHED_PAREN);
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
case '(':
|
|
|
|
tokens.emplace_back(template_token_t::PAR_OPEN, level, consumer.from(current_start, current_start + 1), paren_level);
|
|
|
|
paren_level++;
|
|
|
|
break;
|
|
|
|
case ')':
|
|
|
|
tokens.emplace_back(template_token_t::PAR_CLOSE, level, consumer.from(current_start, current_start + 1), paren_level);
|
|
|
|
paren_level--;
|
|
|
|
break;
|
|
|
|
case '"':
|
|
|
|
tokens.emplace_back(template_token_t::QUOTE, level, consumer.from(current_start, current_start + 1));
|
|
|
|
// if we just encountered a quote, we need to consume characters until we find its matching quote
|
|
|
|
// only if we are currently inside a template though...
|
|
|
|
if (open)
|
|
|
|
{
|
|
|
|
current_start = static_cast<i64>(consumer.getCurrentIndex());
|
|
|
|
while (consumer.hasNext() && consumer.next() != '"')
|
|
|
|
consumer.advance();
|
|
|
|
if (!consumer.hasNext())
|
|
|
|
return blt::unexpected(template_tokenizer_failure_t::MISMATCHED_QUOTE);
|
|
|
|
tokens.emplace_back(template_token_t::STRING, level, consumer.from(current_start, consumer.getCurrentIndex()));
|
|
|
|
consumer.advance();
|
|
|
|
current_start = static_cast<i64>(consumer.getCurrentIndex());
|
|
|
|
tokens.emplace_back(template_token_t::QUOTE, level, consumer.from(current_start, current_start + 1));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case '^':
|
|
|
|
tokens.emplace_back(template_token_t::XOR, level, consumer.from(current_start, current_start + 1));
|
|
|
|
break;
|
|
|
|
case '!':
|
|
|
|
tokens.emplace_back(template_token_t::NOT, level, consumer.from(current_start, current_start + 1));
|
|
|
|
break;
|
|
|
|
case ';':
|
|
|
|
tokens.emplace_back(template_token_t::SEMI, level, consumer.from(current_start, current_start + 1));
|
|
|
|
break;
|
|
|
|
case ',':
|
|
|
|
tokens.emplace_back(template_token_t::COMMA, level, consumer.from(current_start, current_start + 1));
|
|
|
|
break;
|
2024-05-09 21:53:08 -04:00
|
|
|
case '+':
|
|
|
|
tokens.emplace_back(template_token_t::ADD, level, consumer.from(current_start, current_start + 1));
|
|
|
|
break;
|
2024-05-09 13:51:25 -04:00
|
|
|
case '.':
|
|
|
|
tokens.emplace_back(template_token_t::PERIOD, level, consumer.from(current_start, current_start + 1));
|
|
|
|
break;
|
|
|
|
case '~':
|
|
|
|
tokens.emplace_back(template_token_t::FUNCTION, level, consumer.from(current_start, current_start + 1));
|
|
|
|
break;
|
|
|
|
case '|':
|
|
|
|
if (consumer.hasNext() && consumer.next() == '|')
|
|
|
|
{
|
|
|
|
consumer.advance();
|
|
|
|
tokens.emplace_back(template_token_t::OR, level, consumer.from(current_start, current_start + 2));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
start = current_start;
|
|
|
|
break;
|
|
|
|
case '&':
|
|
|
|
if (consumer.hasNext() && consumer.next() == '&')
|
|
|
|
{
|
|
|
|
consumer.advance();
|
|
|
|
tokens.emplace_back(template_token_t::AND, level, consumer.from(current_start, current_start + 2));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
start = current_start;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// do not add whitespace to anything
|
|
|
|
if (std::isspace(c))
|
2024-05-10 12:56:48 -04:00
|
|
|
{
|
2024-05-09 13:51:25 -04:00
|
|
|
break;
|
2024-05-10 12:56:48 -04:00
|
|
|
}
|
2024-05-09 13:51:25 -04:00
|
|
|
if (start == -1)
|
|
|
|
start = current_start;
|
|
|
|
if (consumer.hasNext() && (isNonStringNext(consumer.next()) || std::isspace(consumer.next())))
|
|
|
|
{
|
|
|
|
tokens.emplace_back(template_token_t::STRING, level, consumer.from(start, consumer.getCurrentIndex()));
|
|
|
|
start = -1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (start != -1)
|
|
|
|
tokens.emplace_back(template_token_t::STRING, level, consumer.from(start, consumer.getCurrentIndex()));
|
|
|
|
|
|
|
|
for (auto& token : tokens)
|
|
|
|
{
|
|
|
|
if (token.type == template_token_t::STRING && detail::identifiers.contains(token.token))
|
|
|
|
token.type = detail::identifiers.at(token.token);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (level != 0)
|
|
|
|
return unexpected(template_tokenizer_failure_t::MISMATCHED_CURLY);
|
|
|
|
|
|
|
|
return tokens;
|
|
|
|
}
|
|
|
|
|
|
|
|
blt::expected<std::string, template_parser_failure_t> template_engine_t::evaluate(std::string_view str)
|
|
|
|
{
|
|
|
|
auto tokens = process_string(str);
|
|
|
|
|
|
|
|
if (!tokens)
|
|
|
|
{
|
|
|
|
switch (tokens.error())
|
|
|
|
{
|
|
|
|
case template_tokenizer_failure_t::MISMATCHED_CURLY:
|
|
|
|
BLT_ERROR("Mismatched curly braces");
|
|
|
|
break;
|
|
|
|
case template_tokenizer_failure_t::MISMATCHED_PAREN:
|
|
|
|
BLT_ERROR("Mismatched parentheses");
|
|
|
|
break;
|
|
|
|
case template_tokenizer_failure_t::MISMATCHED_QUOTE:
|
|
|
|
BLT_ERROR("Mismatched quotes");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return blt::unexpected(template_parser_failure_t::TOKENIZER_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string return_str;
|
2024-05-10 12:56:48 -04:00
|
|
|
//return_str.reserve(str.size());
|
2024-05-09 13:51:25 -04:00
|
|
|
|
2024-05-10 12:56:48 -04:00
|
|
|
template_token_consumer_t consumer{tokens.value(), str};
|
2024-05-09 13:51:25 -04:00
|
|
|
|
2024-05-10 12:56:48 -04:00
|
|
|
template_parser_t parser(*this, consumer);
|
2024-05-09 13:51:25 -04:00
|
|
|
|
|
|
|
while (consumer.hasNext())
|
|
|
|
{
|
|
|
|
while (consumer.hasNext(2))
|
|
|
|
{
|
2024-05-09 21:53:08 -04:00
|
|
|
if (consumer.next().type == template_token_t::IDENT && consumer.next(1).type == template_token_t::CURLY_OPEN)
|
2024-05-09 13:51:25 -04:00
|
|
|
{
|
2024-05-10 12:56:48 -04:00
|
|
|
return_str += consumer.from_last();
|
2024-05-09 13:51:25 -04:00
|
|
|
break;
|
|
|
|
}
|
2024-05-09 21:53:08 -04:00
|
|
|
consumer.advance();
|
2024-05-09 13:51:25 -04:00
|
|
|
}
|
2024-05-10 12:56:48 -04:00
|
|
|
if (!consumer.hasNext(2))
|
|
|
|
break;
|
|
|
|
|
2024-05-09 13:51:25 -04:00
|
|
|
if (auto result = parser.parse())
|
2024-05-10 12:56:48 -04:00
|
|
|
{
|
|
|
|
BLT_DEBUG("Result parser: %s", result.value().c_str());
|
2024-05-09 13:51:25 -04:00
|
|
|
return_str += result.value();
|
2024-05-10 12:56:48 -04:00
|
|
|
}else
|
2024-05-09 21:53:08 -04:00
|
|
|
{
|
|
|
|
if (result.error() == template_parser_failure_t::FUNCTION_DISCARD)
|
|
|
|
continue;
|
2024-05-09 13:51:25 -04:00
|
|
|
return result;
|
2024-05-09 21:53:08 -04:00
|
|
|
}
|
2024-05-10 12:56:48 -04:00
|
|
|
consumer.set_marker();
|
2024-05-09 13:51:25 -04:00
|
|
|
}
|
2024-05-10 12:56:48 -04:00
|
|
|
while(consumer.hasNext())
|
|
|
|
consumer.advance();
|
|
|
|
return_str += consumer.from_last();
|
2024-05-10 01:53:50 -04:00
|
|
|
|
2024-05-09 13:51:25 -04:00
|
|
|
return return_str;
|
|
|
|
}
|
|
|
|
}
|