template engine
parent
943fb84211
commit
b857bc96ef
|
@ -1,7 +1,7 @@
|
|||
cmake_minimum_required(VERSION 3.20)
|
||||
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_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>
|
||||
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();
|
||||
}
|
||||
|
@ -122,31 +122,42 @@ namespace blt
|
|||
template<typename U, typename G>
|
||||
inline static constexpr bool eight_insanity_v =
|
||||
std::is_constructible_v<T, expected<U, G>&> || std::is_constructible_v<T, expected<U, G>> ||
|
||||
std::is_constructible_v<T, const expected<U, G>&> || std::is_constructible_v<T, const expected<U, G>> ||
|
||||
std::is_convertible_v<expected<U, G>&, T> || std::is_convertible_v<expected<U, G>, T> ||
|
||||
std::is_convertible_v<const expected<U, G>&, T> || std::is_convertible_v<const expected<U, G>, T>;
|
||||
std::is_constructible_v<T, const expected<U, G>&> || std::is_constructible_v<T, const expected<U, G>> ||
|
||||
std::is_convertible_v<expected<U, G>&, T> || std::is_convertible_v<expected<U, G>, T> ||
|
||||
std::is_convertible_v<const expected<U, G>&, T> || std::is_convertible_v<const expected<U, G>, T>;
|
||||
|
||||
template<typename U, typename G>
|
||||
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>, 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:
|
||||
template<typename std::enable_if_t<std::is_default_constructible_v<T>, bool> = true>
|
||||
constexpr expected() noexcept: v(T())
|
||||
{}
|
||||
|
||||
constexpr expected(const expected& copy) = delete;
|
||||
|
||||
constexpr expected(expected&& move) noexcept: v(move ? std::move(*move) : std::move(move.error()))
|
||||
// constexpr expected(const expected& copy) = delete;
|
||||
constexpr expected(const expected<T, E, true>& copy): expected<T, E, true>::v(copy.v)
|
||||
{}
|
||||
|
||||
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)
|
||||
*/
|
||||
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_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):
|
||||
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<
|
||||
(!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):
|
||||
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<
|
||||
(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):
|
||||
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<
|
||||
(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):
|
||||
v(other.has_value() ? std::forward<UF>(*other) : std::forward<GF>(other.error()))
|
||||
|
@ -182,22 +193,22 @@ namespace blt
|
|||
*/
|
||||
|
||||
template<class U = T, std::enable_if_t<!std::is_convertible_v<U, T> &&
|
||||
!std::is_same_v<remove_cvref_t<T>, void> &&
|
||||
!std::is_same_v<remove_cvref_t<U>, std::in_place_t> &&
|
||||
!std::is_same_v<expected, remove_cvref_t<U>> &&
|
||||
std::is_constructible_v<T, U> &&
|
||||
!std::is_same_v<remove_cvref_t<U>, unexpected<U>> &&
|
||||
!std::is_same_v<remove_cvref_t<U>, expected<T, E>>, bool> = true>
|
||||
!std::is_same_v<remove_cvref_t<T>, void> &&
|
||||
!std::is_same_v<remove_cvref_t<U>, std::in_place_t> &&
|
||||
!std::is_same_v<expected, remove_cvref_t<U>> &&
|
||||
std::is_constructible_v<T, U> &&
|
||||
!std::is_same_v<remove_cvref_t<U>, unexpected<U>> &&
|
||||
!std::is_same_v<remove_cvref_t<U>, expected<T, E>>, bool> = true>
|
||||
constexpr explicit expected(U&& v): v(T(std::forward<U>(v)))
|
||||
{}
|
||||
|
||||
template<class U = T, std::enable_if_t<std::is_convertible_v<U, T> &&
|
||||
!std::is_same_v<remove_cvref_t<T>, void> &&
|
||||
!std::is_same_v<remove_cvref_t<U>, std::in_place_t> &&
|
||||
!std::is_same_v<expected, remove_cvref_t<U>> &&
|
||||
std::is_constructible_v<T, U> &&
|
||||
!std::is_same_v<remove_cvref_t<U>, unexpected<U>> &&
|
||||
!std::is_same_v<remove_cvref_t<U>, expected<T, E>>, bool> = true>
|
||||
!std::is_same_v<remove_cvref_t<T>, void> &&
|
||||
!std::is_same_v<remove_cvref_t<U>, std::in_place_t> &&
|
||||
!std::is_same_v<expected, remove_cvref_t<U>> &&
|
||||
std::is_constructible_v<T, U> &&
|
||||
!std::is_same_v<remove_cvref_t<U>, unexpected<U>> &&
|
||||
!std::is_same_v<remove_cvref_t<U>, expected<T, E>>, bool> = true>
|
||||
constexpr expected(U&& v): v(T(std::forward<U>(v)))
|
||||
{}
|
||||
|
||||
|
@ -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)...))
|
||||
{}
|
||||
|
||||
expected& operator=(const expected& copy) = delete;
|
||||
|
||||
expected& operator=(expected&& move) = default;
|
||||
|
||||
[[nodiscard]] constexpr explicit operator bool() const noexcept
|
||||
{
|
||||
return std::holds_alternative<T>(v);
|
||||
|
@ -362,15 +369,14 @@ namespace blt
|
|||
};
|
||||
|
||||
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:
|
||||
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 <utility_test.h>
|
||||
#include <blt/std/utility.h>
|
||||
#include <templating_test.h>
|
||||
|
||||
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