#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 . */ #ifndef BLT_TEMPLATING_H #define BLT_TEMPLATING_H #include #include #include #include #include #include #include #include #include #include namespace blt { class template_engine_t; template 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, // $ ADD, // + 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 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]"; case template_token_t::ADD: return "[ADD]"; } } enum class template_tokenizer_failure_t { MISMATCHED_CURLY, MISMATCHED_PAREN, MISMATCHED_QUOTE, }; enum class template_parser_failure_t { SUBSTITUTION_NOT_FOUND, TOKENIZER_FAILURE, NO_MATCHING_CURLY, MISSING_IDENT_BRACES, FUNCTION_EXPECTED_STRING, FUNCTION_NOT_FOUND, FUNCTION_DISCARD, STRING_EXPECTED_CONCAT, IF_EXPECTED_PAREN, IF_EXPECTED_CURLY, BOOL_EXPECTED_PAREN, BOOL_TYPE_NOT_FOUND, UNKNOWN_STATEMENT_ERROR, UNKNOWN_ERROR }; 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 { 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, template_token_data_t> { public: explicit template_token_consumer_t(const std::vector& statement, std::string_view raw_string): template_consumer_base_t(statement), raw_string(raw_string) {} void set_marker() { // when setting the marker, we need to go from the last closing brace auto index = storage.begin() + getCurrentIndex(); while (index->type != template_token_t::CURLY_CLOSE) index--; last_read_index = ((&index->token.front() + index->token.size()) - &raw_string[last_read_index]); } std::string_view from_last() { if (!hasNext()) return std::string_view(&raw_string[last_read_index], raw_string.size() - last_read_index); auto token = storage[getCurrentIndex()]; auto len = ((&token.token.back()) - &raw_string[last_read_index]); auto str = std::string_view(&raw_string[last_read_index], len); return str; } void back() { current_index--; } auto prev() { if (current_index == 0) throw std::runtime_error("Current Index cannot be zero!"); return storage[current_index - 1]; } private: std::string_view raw_string; size_t last_read_index = 0; }; 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; } inline bool contains(std::string_view token) { return substitutions.contains(token); } inline auto get(std::string_view token) { return internal_evaluate(substitutions[token], true); } static blt::expected, template_tokenizer_failure_t> process_string(std::string_view str); blt::expected evaluate(std::string_view str) { auto eval = internal_evaluate(str, false); if (eval.has_value()) return eval; else if (eval.error() == template_parser_failure_t::FUNCTION_DISCARD) return ""; else return eval; } private: blt::expected internal_evaluate(std::string_view str, bool discard); blt::hashmap_t substitutions; }; class template_parser_t { public: using estring = blt::expected; using ebool = blt::expected; template_parser_t(template_engine_t& engine, template_token_consumer_t& consumer): engine(engine), consumer(consumer) {} estring parse() { 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(); return str; } return blt::unexpected(template_parser_failure_t::MISSING_IDENT_BRACES); } private: estring statement(); estring function(); estring if_func(); estring string(); ebool bool_statement(); ebool bool_value(); ebool bool_expression(); template_engine_t& engine; template_token_consumer_t& consumer; }; } #endif //BLT_TEMPLATING_H