2024-05-09 13:51:25 -04:00
|
|
|
#pragma once
|
|
|
|
/*
|
|
|
|
* 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/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef BLT_TEMPLATING_H
|
|
|
|
#define BLT_TEMPLATING_H
|
|
|
|
|
|
|
|
#include <string>
|
|
|
|
#include <string_view>
|
|
|
|
#include <utility>
|
|
|
|
#include <vector>
|
|
|
|
#include <utility>
|
|
|
|
#include <blt/std/hashmap.h>
|
|
|
|
#include <blt/std/types.h>
|
|
|
|
#include <blt/std/expected.h>
|
|
|
|
#include <variant>
|
|
|
|
|
|
|
|
namespace blt
|
|
|
|
{
|
2024-05-09 21:53:08 -04:00
|
|
|
|
2024-05-09 13:51:25 -04:00
|
|
|
template<typename Storage, typename Consumable>
|
|
|
|
class template_consumer_base_t
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
explicit template_consumer_base_t(Storage storage): storage(std::move(storage))
|
|
|
|
{}
|
|
|
|
|
|
|
|
[[nodiscard]] Consumable next(size_t offset = 0) const
|
|
|
|
{
|
|
|
|
return storage[current_index + offset];
|
|
|
|
}
|
|
|
|
|
|
|
|
void advance(size_t offset = 1)
|
|
|
|
{
|
|
|
|
current_index += offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
[[nodiscard]] bool hasNext(size_t offset = 1) const
|
|
|
|
{
|
|
|
|
return (current_index + (offset - 1)) < storage.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
[[nodiscard]] Consumable consume()
|
|
|
|
{
|
|
|
|
Consumable c = next();
|
|
|
|
advance();
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
|
|
|
[[nodiscard]] size_t getCurrentIndex() const
|
|
|
|
{
|
|
|
|
return current_index;
|
|
|
|
}
|
|
|
|
|
|
|
|
[[nodiscard]] size_t getPreviousIndex() const
|
|
|
|
{
|
|
|
|
return current_index - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
size_t current_index = 0;
|
|
|
|
Storage storage;
|
|
|
|
};
|
|
|
|
|
|
|
|
enum class template_token_t
|
|
|
|
{
|
|
|
|
//STRING, // A string of characters not $ { or }
|
|
|
|
IDENT, // $
|
2024-05-09 21:53:08 -04:00
|
|
|
ADD, // +
|
2024-05-09 13:51:25 -04:00
|
|
|
CURLY_OPEN, // {
|
|
|
|
CURLY_CLOSE, // }
|
|
|
|
IF, // IF
|
|
|
|
ELSE, // ELSE
|
|
|
|
PAR_OPEN, // (
|
|
|
|
PAR_CLOSE, // )
|
|
|
|
OR, // ||
|
|
|
|
AND, // &&
|
|
|
|
XOR, // ^
|
|
|
|
NOT, // !
|
|
|
|
QUOTE, // "
|
|
|
|
SEMI, // ;
|
|
|
|
COMMA, // ,
|
|
|
|
PERIOD, // .
|
|
|
|
FUNCTION, // ~
|
|
|
|
STRING // variable name
|
|
|
|
};
|
|
|
|
|
|
|
|
namespace detail
|
|
|
|
{
|
|
|
|
inline const blt::hashmap_t<std::string_view, template_token_t> identifiers = {
|
|
|
|
{"IF", template_token_t::IF},
|
|
|
|
{"ELSE", template_token_t::ELSE}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
inline std::string template_token_to_string(template_token_t token)
|
|
|
|
{
|
|
|
|
switch (token)
|
|
|
|
{
|
|
|
|
case template_token_t::IDENT:
|
|
|
|
return "[Template Identifier]";
|
|
|
|
case template_token_t::CURLY_OPEN:
|
|
|
|
return "[Curly Open]";
|
|
|
|
case template_token_t::CURLY_CLOSE:
|
|
|
|
return "[Curly Close]";
|
|
|
|
case template_token_t::IF:
|
|
|
|
return "[IF]";
|
|
|
|
case template_token_t::ELSE:
|
|
|
|
return "[ELSE]";
|
|
|
|
case template_token_t::PAR_OPEN:
|
|
|
|
return "[Par Open]";
|
|
|
|
case template_token_t::PAR_CLOSE:
|
|
|
|
return "[Par Close]";
|
|
|
|
case template_token_t::OR:
|
|
|
|
return "[OR]";
|
|
|
|
case template_token_t::AND:
|
|
|
|
return "[AND]";
|
|
|
|
case template_token_t::XOR:
|
|
|
|
return "[XOR]";
|
|
|
|
case template_token_t::NOT:
|
|
|
|
return "[NOT]";
|
|
|
|
case template_token_t::QUOTE:
|
|
|
|
return "[QUOTE]";
|
|
|
|
case template_token_t::FUNCTION:
|
|
|
|
return "[FUNC]";
|
|
|
|
case template_token_t::STRING:
|
|
|
|
return "[STR]";
|
|
|
|
case template_token_t::SEMI:
|
|
|
|
return "[SEMI]";
|
|
|
|
case template_token_t::COMMA:
|
|
|
|
return "[COMMA]";
|
|
|
|
case template_token_t::PERIOD:
|
|
|
|
return "[PERIOD]";
|
2024-05-09 21:53:08 -04:00
|
|
|
case template_token_t::ADD:
|
|
|
|
return "[ADD]";
|
2024-05-09 13:51:25 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
enum class template_tokenizer_failure_t
|
|
|
|
{
|
|
|
|
MISMATCHED_CURLY,
|
|
|
|
MISMATCHED_PAREN,
|
|
|
|
MISMATCHED_QUOTE,
|
|
|
|
};
|
|
|
|
|
|
|
|
enum class template_parser_failure_t
|
|
|
|
{
|
2024-05-09 21:53:08 -04:00
|
|
|
SUBSTITUTION_NOT_FOUND,
|
2024-05-09 13:51:25 -04:00
|
|
|
TOKENIZER_FAILURE,
|
2024-05-09 21:53:08 -04:00
|
|
|
NO_MATCHING_CURLY,
|
|
|
|
MISSING_IDENT_BRACES,
|
|
|
|
FUNCTION_EXPECTED_STRING,
|
|
|
|
FUNCTION_NOT_FOUND,
|
|
|
|
FUNCTION_DISCARD,
|
|
|
|
STRING_EXPECTED_CONCAT,
|
|
|
|
IF_EXPECTED_PAREN,
|
|
|
|
BOOL_EXPECTED_PAREN,
|
|
|
|
UNKNOWN_STATEMENT_ERROR,
|
|
|
|
UNKNOWN_ERROR
|
2024-05-09 13:51:25 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
struct template_token_data_t
|
|
|
|
{
|
|
|
|
template_token_t type;
|
|
|
|
size_t level;
|
|
|
|
std::string_view token;
|
|
|
|
size_t paren_level = 0;
|
|
|
|
|
|
|
|
template_token_data_t(template_token_t type, size_t level, const std::string_view& token): type(type), level(level), token(token)
|
|
|
|
{}
|
|
|
|
|
|
|
|
template_token_data_t(template_token_t type, size_t level, const std::string_view& token, size_t parenLevel):
|
|
|
|
type(type), level(level), token(token), paren_level(parenLevel)
|
|
|
|
{}
|
|
|
|
};
|
|
|
|
|
|
|
|
class template_char_consumer_t : public template_consumer_base_t<std::string_view, char>
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
explicit template_char_consumer_t(std::string_view statement): template_consumer_base_t(statement)
|
|
|
|
{}
|
|
|
|
|
|
|
|
[[nodiscard]] std::string_view from(size_t begin, size_t end)
|
|
|
|
{
|
|
|
|
return std::string_view{&storage[begin], end - begin};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class template_token_consumer_t : public template_consumer_base_t<std::vector<template_token_data_t>, template_token_data_t>
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
explicit template_token_consumer_t(const std::vector<template_token_data_t>& statement): template_consumer_base_t(statement)
|
|
|
|
{}
|
|
|
|
|
|
|
|
std::string_view from_last(std::string_view raw_string)
|
|
|
|
{
|
|
|
|
if (current_index == 0)
|
|
|
|
return "";
|
|
|
|
auto token = storage[getPreviousIndex()];
|
|
|
|
auto len = (&token.token.back() - &raw_string.front()) - last_read_index;
|
2024-05-09 21:53:49 -04:00
|
|
|
auto str = std::string_view(&raw_string[last_read_index], len);
|
2024-05-09 13:51:25 -04:00
|
|
|
last_read_index += len;
|
|
|
|
return str;
|
|
|
|
}
|
2024-05-09 21:53:08 -04:00
|
|
|
|
|
|
|
void back()
|
|
|
|
{
|
|
|
|
current_index--;
|
|
|
|
}
|
2024-05-09 13:51:25 -04:00
|
|
|
|
|
|
|
private:
|
|
|
|
size_t last_read_index = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
class template_parser_t
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
using estring = blt::expected<std::string, template_parser_failure_t>;
|
2024-05-09 21:53:08 -04:00
|
|
|
using ebool = blt::expected<bool, template_parser_failure_t>;
|
|
|
|
|
2024-05-09 13:51:25 -04:00
|
|
|
template_parser_t(blt::hashmap_t<std::string, std::string>& substitutions, template_token_consumer_t& consumer):
|
|
|
|
substitutions(substitutions), consumer(consumer)
|
|
|
|
{}
|
|
|
|
|
|
|
|
estring parse()
|
|
|
|
{
|
2024-05-09 21:53:08 -04:00
|
|
|
auto next = consumer.consume();
|
|
|
|
if (next.type == template_token_t::IDENT && consumer.next().type == template_token_t::CURLY_OPEN)
|
|
|
|
{
|
|
|
|
consumer.advance();
|
|
|
|
auto str = statement();
|
|
|
|
consumer.advance();
|
2024-05-09 13:51:25 -04:00
|
|
|
return str;
|
2024-05-09 21:53:08 -04:00
|
|
|
}
|
|
|
|
return blt::unexpected(template_parser_failure_t::MISSING_IDENT_BRACES);
|
2024-05-09 13:51:25 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
estring statement()
|
|
|
|
{
|
2024-05-09 21:53:08 -04:00
|
|
|
auto next = consumer.consume();
|
|
|
|
if (next.type == template_token_t::STRING || next.type == template_token_t::QUOTE)
|
|
|
|
{
|
|
|
|
consumer.back();
|
|
|
|
return string();
|
|
|
|
} else if (next.type == template_token_t::FUNCTION)
|
|
|
|
{
|
|
|
|
return function();
|
|
|
|
} else if (next.type == template_token_t::IDENT && consumer.hasNext() && consumer.next().type == template_token_t::CURLY_OPEN)
|
|
|
|
{
|
|
|
|
consumer.advance();
|
|
|
|
auto stmt = statement();
|
|
|
|
// should never occur
|
|
|
|
if (consumer.hasNext() && consumer.next().type != template_token_t::CURLY_CLOSE)
|
|
|
|
return blt::unexpected(template_parser_failure_t::NO_MATCHING_CURLY);
|
|
|
|
consumer.advance();
|
|
|
|
return stmt;
|
|
|
|
} else if (next.type == template_token_t::IF)
|
|
|
|
{
|
|
|
|
return if_func();
|
|
|
|
}
|
|
|
|
return blt::unexpected(template_parser_failure_t::UNKNOWN_STATEMENT_ERROR);
|
|
|
|
}
|
|
|
|
|
|
|
|
estring function()
|
|
|
|
{
|
|
|
|
auto str = consumer.consume();
|
|
|
|
if (str.type != template_token_t::STRING)
|
|
|
|
return blt::unexpected(template_parser_failure_t::FUNCTION_EXPECTED_STRING);
|
|
|
|
if (str.token == "DISCARD")
|
|
|
|
return blt::unexpected(template_parser_failure_t::FUNCTION_DISCARD);
|
|
|
|
return blt::unexpected(template_parser_failure_t::FUNCTION_NOT_FOUND);
|
|
|
|
}
|
2024-05-09 13:51:25 -04:00
|
|
|
|
2024-05-09 21:53:08 -04:00
|
|
|
estring if_func()
|
|
|
|
{
|
|
|
|
// IF(
|
|
|
|
if (consumer.consume().type != template_token_t::PAR_OPEN)
|
|
|
|
return blt::unexpected(template_parser_failure_t::IF_EXPECTED_PAREN);
|
|
|
|
// (statement)
|
|
|
|
auto bool_eval = bool_statement();
|
|
|
|
if (!bool_eval)
|
|
|
|
return blt::unexpected(bool_eval.error());
|
|
|
|
if (consumer.consume().type != template_token_t::PAR_CLOSE)
|
|
|
|
return blt::unexpected(template_parser_failure_t::IF_EXPECTED_PAREN);
|
|
|
|
auto true_statement = statement();
|
|
|
|
estring false_statement = blt::unexpected(template_parser_failure_t::UNKNOWN_ERROR);
|
|
|
|
if (consumer.next().type == template_token_t::ELSE)
|
|
|
|
{
|
|
|
|
consumer.advance();
|
|
|
|
false_statement = statement();
|
|
|
|
}
|
|
|
|
if (bool_eval.value())
|
|
|
|
{
|
|
|
|
return true_statement;
|
|
|
|
} else
|
|
|
|
{
|
|
|
|
if (false_statement)
|
|
|
|
return false_statement;
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
estring string()
|
|
|
|
{
|
|
|
|
auto next = consumer.consume();
|
|
|
|
if (next.type == template_token_t::STRING)
|
|
|
|
{
|
|
|
|
if (!substitutions.contains(next.token))
|
|
|
|
return blt::unexpected(template_parser_failure_t::SUBSTITUTION_NOT_FOUND);
|
|
|
|
if (consumer.next().type == template_token_t::SEMI)
|
|
|
|
{
|
|
|
|
consumer.advance();
|
|
|
|
return substitutions[next.token];
|
|
|
|
}
|
|
|
|
if (consumer.next().type != template_token_t::ADD)
|
|
|
|
return blt::unexpected(template_parser_failure_t::STRING_EXPECTED_CONCAT);
|
|
|
|
consumer.advance();
|
|
|
|
auto str = string();
|
|
|
|
if (str)
|
|
|
|
return substitutions[next.token] + str.value();
|
|
|
|
else
|
|
|
|
return str;
|
|
|
|
} else
|
|
|
|
{
|
|
|
|
if (consumer.next().type == template_token_t::SEMI)
|
|
|
|
{
|
|
|
|
consumer.advance();
|
|
|
|
return std::string(next.token);
|
|
|
|
}
|
|
|
|
auto str = string();
|
|
|
|
if (str)
|
|
|
|
return std::string(next.token) + str.value();
|
|
|
|
else
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ebool bool_statement()
|
|
|
|
{
|
|
|
|
auto next = consumer.next();
|
|
|
|
if (next.type == template_token_t::PAR_OPEN)
|
|
|
|
{
|
|
|
|
consumer.advance();
|
|
|
|
auto b = bool_statement();
|
|
|
|
if (consumer.consume().type != template_token_t::PAR_CLOSE)
|
|
|
|
return blt::unexpected(template_parser_failure_t::BOOL_EXPECTED_PAREN);
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
return bool_expression();
|
|
|
|
}
|
|
|
|
|
|
|
|
ebool bool_expression()
|
|
|
|
{
|
|
|
|
auto next = consumer.next();
|
|
|
|
if (next.type == template_token_t::PAR_OPEN)
|
|
|
|
return bool_statement();
|
|
|
|
consumer.advance();
|
|
|
|
if (next.type == template_token_t::NOT)
|
|
|
|
{
|
|
|
|
auto b = bool_statement();
|
|
|
|
if (b)
|
|
|
|
return !b.value();
|
|
|
|
else
|
|
|
|
return b;
|
|
|
|
} else if (next.type == template_token_t::STRING)
|
|
|
|
{
|
|
|
|
auto bool_val = next.token.empty();
|
|
|
|
next = consumer.next();
|
|
|
|
if (next.type == template_token_t::PAR_CLOSE)
|
|
|
|
return bool_val;
|
|
|
|
consumer.advance();
|
|
|
|
if (next.type == template_token_t::AND)
|
|
|
|
{
|
|
|
|
auto other_val = bool_expression();
|
|
|
|
if (!other_val)
|
|
|
|
return other_val;
|
|
|
|
return bool_val && other_val.value();
|
|
|
|
} else if (next.type == template_token_t::OR)
|
|
|
|
{
|
|
|
|
auto other_val = bool_expression();
|
|
|
|
if (!other_val)
|
|
|
|
return other_val;
|
|
|
|
return bool_val || other_val.value();
|
|
|
|
} else if (next.type == template_token_t::XOR)
|
|
|
|
{
|
|
|
|
auto other_val = bool_expression();
|
|
|
|
if (!other_val)
|
|
|
|
return other_val;
|
|
|
|
return bool_val ^ other_val.value();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return unexpected(template_parser_failure_t::UNKNOWN_ERROR);
|
2024-05-09 13:51:25 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
blt::hashmap_t<std::string, std::string>& substitutions;
|
|
|
|
template_token_consumer_t& consumer;
|
|
|
|
};
|
|
|
|
|
|
|
|
class template_engine_t
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
inline std::string& operator[](const std::string& key)
|
|
|
|
{
|
|
|
|
return substitutions[key];
|
|
|
|
}
|
|
|
|
|
|
|
|
inline std::string& operator[](std::string_view key)
|
|
|
|
{
|
|
|
|
return substitutions[key];
|
|
|
|
}
|
|
|
|
|
|
|
|
inline template_engine_t& set(std::string_view key, std::string_view replacement)
|
|
|
|
{
|
|
|
|
substitutions[key] = replacement;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
static blt::expected<std::vector<template_token_data_t>, template_tokenizer_failure_t> process_string(std::string_view str);
|
|
|
|
|
|
|
|
blt::expected<std::string, template_parser_failure_t> evaluate(std::string_view str);
|
|
|
|
|
|
|
|
private:
|
|
|
|
blt::hashmap_t<std::string, std::string> substitutions;
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif //BLT_TEMPLATING_H
|