template engine
parent
943fb84211
commit
b857bc96ef
|
@ -1,7 +1,7 @@
|
||||||
cmake_minimum_required(VERSION 3.20)
|
cmake_minimum_required(VERSION 3.20)
|
||||||
include(cmake/color.cmake)
|
include(cmake/color.cmake)
|
||||||
|
|
||||||
set(BLT_VERSION 0.16.22)
|
set(BLT_VERSION 0.16.23)
|
||||||
set(BLT_TEST_VERSION 0.0.1)
|
set(BLT_TEST_VERSION 0.0.1)
|
||||||
|
|
||||||
set(BLT_TARGET BLT)
|
set(BLT_TARGET BLT)
|
||||||
|
|
|
@ -0,0 +1,270 @@
|
||||||
|
#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
|
||||||
|
{
|
||||||
|
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, // $
|
||||||
|
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]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class template_tokenizer_failure_t
|
||||||
|
{
|
||||||
|
MISMATCHED_CURLY,
|
||||||
|
MISMATCHED_PAREN,
|
||||||
|
MISMATCHED_QUOTE,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class template_parser_failure_t
|
||||||
|
{
|
||||||
|
TOKENIZER_FAILURE,
|
||||||
|
NO_MATCHING_CURLY
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
auto str = std::string_view(&raw_string[last_read_index], len);
|
||||||
|
last_read_index += len;
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t last_read_index = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class template_parser_t
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using estring = blt::expected<std::string, template_parser_failure_t>;
|
||||||
|
template_parser_t(blt::hashmap_t<std::string, std::string>& substitutions, template_token_consumer_t& consumer):
|
||||||
|
substitutions(substitutions), consumer(consumer)
|
||||||
|
{}
|
||||||
|
|
||||||
|
estring parse()
|
||||||
|
{
|
||||||
|
consumer.advance(2);
|
||||||
|
auto str = statement();
|
||||||
|
if (!str)
|
||||||
|
return str;
|
||||||
|
// 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 str;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
estring statement()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
|
@ -75,7 +75,7 @@ namespace blt
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename E2>
|
template<typename E2>
|
||||||
inline friend constexpr bool operator==(const unexpected& x, const unexpected <E2>& y)
|
inline friend constexpr bool operator==(const unexpected& x, const unexpected<E2>& y)
|
||||||
{
|
{
|
||||||
return x.error() == y.error();
|
return x.error() == y.error();
|
||||||
}
|
}
|
||||||
|
@ -130,23 +130,34 @@ namespace blt
|
||||||
inline static constexpr bool four_insanity_v =
|
inline static constexpr bool four_insanity_v =
|
||||||
std::is_constructible_v<unexpected<E>, expected<U, G>&> || std::is_constructible_v<unexpected<E>, expected<U, G>> ||
|
std::is_constructible_v<unexpected<E>, expected<U, G>&> || std::is_constructible_v<unexpected<E>, expected<U, G>> ||
|
||||||
std::is_constructible_v<unexpected<E>, const expected<U, G>&> || std::is_constructible_v<unexpected<E>, const expected<U, G>>;
|
std::is_constructible_v<unexpected<E>, const expected<U, G>&> || std::is_constructible_v<unexpected<E>, const expected<U, G>>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
template<typename std::enable_if_t<std::is_default_constructible_v<T>, bool> = true>
|
template<typename std::enable_if_t<std::is_default_constructible_v<T>, bool> = true>
|
||||||
constexpr expected() noexcept: v(T())
|
constexpr expected() noexcept: v(T())
|
||||||
{}
|
{}
|
||||||
|
|
||||||
constexpr expected(const expected& copy) = delete;
|
// constexpr expected(const expected& copy) = delete;
|
||||||
|
constexpr expected(const expected<T, E, true>& copy): expected<T, E, true>::v(copy.v)
|
||||||
constexpr expected(expected&& move) noexcept: v(move ? std::move(*move) : std::move(move.error()))
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
expected& operator=(const expected& copy)
|
||||||
|
{
|
||||||
|
v = copy.v;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr expected(expected&& move) noexcept: v(std::move(move.v))
|
||||||
|
{}
|
||||||
|
|
||||||
|
expected& operator=(expected&& move)
|
||||||
|
{
|
||||||
|
std::swap(v, move.v);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (4)...(5)
|
* (4)...(5)
|
||||||
*/
|
*/
|
||||||
template<class U, class G, class UF = std::add_lvalue_reference_t<const U>, class GF = const G&, std::enable_if_t<
|
template<class U, class G, class UF = std::add_lvalue_reference_t<const U>, class GF = const G&, std::enable_if_t<
|
||||||
(!std::is_convertible_v<UF, T> || !std::is_convertible_v<GF, E>) && (std::is_constructible_v<T, UF> || std::is_void_v<U>) &&
|
(!std::is_convertible_v<UF, T> || !std::is_convertible_v<GF, E>) && (std::is_constructible_v<T, UF> || std::is_void_v<U>) &&
|
||||||
std::is_constructible_v<E, GF> && !eight_insanity_v < U, G>&& !four_insanity_v<U, G>, bool> = true>
|
std::is_constructible_v<E, GF> && !eight_insanity_v<U, G> && !four_insanity_v<U, G>, bool> = true>
|
||||||
|
|
||||||
constexpr explicit expected(const expected<U, G>& other):
|
constexpr explicit expected(const expected<U, G>& other):
|
||||||
v(other.has_value() ? std::forward<UF>(*other) : std::forward<GF>(other.error()))
|
v(other.has_value() ? std::forward<UF>(*other) : std::forward<GF>(other.error()))
|
||||||
|
@ -154,7 +165,7 @@ namespace blt
|
||||||
|
|
||||||
template<class U, class G, class UF = U, class GF = G, std::enable_if_t<
|
template<class U, class G, class UF = U, class GF = G, std::enable_if_t<
|
||||||
(!std::is_convertible_v<UF, T> || !std::is_convertible_v<GF, E>) && (std::is_constructible_v<T, UF> || std::is_void_v<U>) &&
|
(!std::is_convertible_v<UF, T> || !std::is_convertible_v<GF, E>) && (std::is_constructible_v<T, UF> || std::is_void_v<U>) &&
|
||||||
std::is_constructible_v<E, GF> && !eight_insanity_v < U, G>&& !four_insanity_v<U, G>, bool> = true>
|
std::is_constructible_v<E, GF> && !eight_insanity_v<U, G> && !four_insanity_v<U, G>, bool> = true>
|
||||||
|
|
||||||
constexpr explicit expected(expected<U, G>&& other):
|
constexpr explicit expected(expected<U, G>&& other):
|
||||||
v(other.has_value() ? std::forward<UF>(*other) : std::forward<GF>(other.error()))
|
v(other.has_value() ? std::forward<UF>(*other) : std::forward<GF>(other.error()))
|
||||||
|
@ -162,7 +173,7 @@ namespace blt
|
||||||
|
|
||||||
template<class U, class G, class UF = std::add_lvalue_reference_t<const U>, class GF = const G&, std::enable_if_t<
|
template<class U, class G, class UF = std::add_lvalue_reference_t<const U>, class GF = const G&, std::enable_if_t<
|
||||||
(std::is_convertible_v<UF, T> && std::is_convertible_v<GF, E>) && (std::is_constructible_v<T, UF> || std::is_void_v<U>) &&
|
(std::is_convertible_v<UF, T> && std::is_convertible_v<GF, E>) && (std::is_constructible_v<T, UF> || std::is_void_v<U>) &&
|
||||||
std::is_constructible_v<E, GF> && !eight_insanity_v < U, G>&& !four_insanity_v<U, G>, bool> = true>
|
std::is_constructible_v<E, GF> && !eight_insanity_v<U, G> && !four_insanity_v<U, G>, bool> = true>
|
||||||
|
|
||||||
constexpr expected(const expected<U, G>& other):
|
constexpr expected(const expected<U, G>& other):
|
||||||
v(other.has_value() ? std::forward<UF>(*other) : std::forward<GF>(other.error()))
|
v(other.has_value() ? std::forward<UF>(*other) : std::forward<GF>(other.error()))
|
||||||
|
@ -170,7 +181,7 @@ namespace blt
|
||||||
|
|
||||||
template<class U, class G, class UF = U, class GF = G, std::enable_if_t<
|
template<class U, class G, class UF = U, class GF = G, std::enable_if_t<
|
||||||
(std::is_convertible_v<UF, T> && std::is_convertible_v<GF, E>) && (std::is_constructible_v<T, UF> || std::is_void_v<U>) &&
|
(std::is_convertible_v<UF, T> && std::is_convertible_v<GF, E>) && (std::is_constructible_v<T, UF> || std::is_void_v<U>) &&
|
||||||
std::is_constructible_v<E, GF> && !eight_insanity_v < U, G>&& !four_insanity_v<U, G>, bool> = true>
|
std::is_constructible_v<E, GF> && !eight_insanity_v<U, G> && !four_insanity_v<U, G>, bool> = true>
|
||||||
|
|
||||||
constexpr expected(expected<U, G>&& other):
|
constexpr expected(expected<U, G>&& other):
|
||||||
v(other.has_value() ? std::forward<UF>(*other) : std::forward<GF>(other.error()))
|
v(other.has_value() ? std::forward<UF>(*other) : std::forward<GF>(other.error()))
|
||||||
|
@ -252,10 +263,6 @@ namespace blt
|
||||||
constexpr explicit expected(unexpect_t, std::initializer_list<U> il, Args&& ... args): v(E(il, std::forward<Args>(args)...))
|
constexpr explicit expected(unexpect_t, std::initializer_list<U> il, Args&& ... args): v(E(il, std::forward<Args>(args)...))
|
||||||
{}
|
{}
|
||||||
|
|
||||||
expected& operator=(const expected& copy) = delete;
|
|
||||||
|
|
||||||
expected& operator=(expected&& move) = default;
|
|
||||||
|
|
||||||
[[nodiscard]] constexpr explicit operator bool() const noexcept
|
[[nodiscard]] constexpr explicit operator bool() const noexcept
|
||||||
{
|
{
|
||||||
return std::holds_alternative<T>(v);
|
return std::holds_alternative<T>(v);
|
||||||
|
@ -362,15 +369,14 @@ namespace blt
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T, typename E>
|
template<typename T, typename E>
|
||||||
class expected<T, E, true> : expected<T, E, false>
|
class expected<T, E, false> : public expected<T, E, true>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using expected<T, E, false>::expected;
|
using expected<T, E, true>::expected;
|
||||||
|
|
||||||
constexpr expected(const expected& copy): expected<T, E, false>::v(copy ? *copy : copy.error())
|
constexpr expected(const expected<T, E, false>& copy) = delete;
|
||||||
{}
|
|
||||||
|
|
||||||
expected& operator=(const expected& copy) = default;
|
expected& operator=(const expected& copy) = delete;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,226 @@
|
||||||
|
/*
|
||||||
|
* <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 '|':
|
||||||
|
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;
|
||||||
|
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))
|
||||||
|
break;
|
||||||
|
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;
|
||||||
|
return_str.reserve(str.size());
|
||||||
|
|
||||||
|
template_token_consumer_t consumer{tokens.value()};
|
||||||
|
|
||||||
|
template_parser_t parser(substitutions, consumer);
|
||||||
|
|
||||||
|
while (consumer.hasNext())
|
||||||
|
{
|
||||||
|
while (consumer.hasNext(2))
|
||||||
|
{
|
||||||
|
if (consumer.next().type == template_token_t::IDENT && consumer.next().type == template_token_t::CURLY_OPEN)
|
||||||
|
{
|
||||||
|
return_str += consumer.from_last(str);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (auto result = parser.parse())
|
||||||
|
return_str += result.value();
|
||||||
|
else
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return return_str;
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,7 @@
|
||||||
#include <blt/parse/argparse.h>
|
#include <blt/parse/argparse.h>
|
||||||
#include <utility_test.h>
|
#include <utility_test.h>
|
||||||
#include <blt/std/utility.h>
|
#include <blt/std/utility.h>
|
||||||
|
#include <templating_test.h>
|
||||||
|
|
||||||
namespace blt::test
|
namespace blt::test
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
#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_TEST_H
|
||||||
|
#define BLT_TEMPLATING_TEST_H
|
||||||
|
|
||||||
|
namespace blt::test
|
||||||
|
{
|
||||||
|
void template_test();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //BLT_TEMPLATING_TEST_H
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* <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 <templating_test.h>
|
||||||
|
#include <string>
|
||||||
|
#include <blt/std/logging.h>
|
||||||
|
#include <blt/parse/templating.h>
|
||||||
|
|
||||||
|
const std::string shader_test_string = R"("
|
||||||
|
#version 300 es
|
||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
${LAYOUT_STRING} out vec4 FragColor;
|
||||||
|
in vec2 uv;
|
||||||
|
in vec2 pos;
|
||||||
|
|
||||||
|
uniform sampler2D tex;
|
||||||
|
|
||||||
|
vec4 linear_iter(vec4 i, vec4 p, float factor){
|
||||||
|
return (i + p) * factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
FragColor = texture(tex, uv);
|
||||||
|
}
|
||||||
|
|
||||||
|
")";
|
||||||
|
|
||||||
|
void process_string(const std::string& str)
|
||||||
|
{
|
||||||
|
BLT_DEBUG(str);
|
||||||
|
auto results = blt::template_engine_t::process_string(str);
|
||||||
|
if (results)
|
||||||
|
{
|
||||||
|
auto val = results.value();
|
||||||
|
for (auto& v : val)
|
||||||
|
{
|
||||||
|
BLT_TRACE_STREAM << (blt::template_token_to_string(v.type));
|
||||||
|
}
|
||||||
|
BLT_TRACE_STREAM << "\n";
|
||||||
|
for (auto& v : val)
|
||||||
|
{
|
||||||
|
BLT_TRACE("{%s: %s}", blt::template_token_to_string(v.type).c_str(), std::string(v.token).c_str());
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
auto error = results.error();
|
||||||
|
switch (error)
|
||||||
|
{
|
||||||
|
case blt::template_tokenizer_failure_t::MISMATCHED_CURLY:
|
||||||
|
BLT_ERROR("Tokenizer Failure: Mismatched curly");
|
||||||
|
break;
|
||||||
|
case blt::template_tokenizer_failure_t::MISMATCHED_PAREN:
|
||||||
|
BLT_ERROR("Tokenizer Failure: Mismatched parenthesis");
|
||||||
|
break;
|
||||||
|
case blt::template_tokenizer_failure_t::MISMATCHED_QUOTE:
|
||||||
|
BLT_ERROR("Tokenizer Failure: Mismatched Quotes");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
BLT_DEBUG("--------------------------");
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace blt::test
|
||||||
|
{
|
||||||
|
void template_test()
|
||||||
|
{
|
||||||
|
process_string(shader_test_string);
|
||||||
|
process_string("~hello");
|
||||||
|
process_string("hello");
|
||||||
|
process_string("hello ${WORLD}");
|
||||||
|
process_string("layout (location = ${IF(LAYOUT_LOCATION) LAYOUT_LOCATION ELSE ~DISCARD})");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue