compiletimestring.h 7.17 KB
/* Copyright (C) 2019
 *
 * This file is part of the osdev components suite
 *
 * 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 2, 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
 */
#ifndef OSDEV_COMPONENTS_MQTT_COMPILETIMESTRING_H
#define OSDEV_COMPONENTS_MQTT_COMPILETIMESTRING_H

/// @file
///
/// Support for compiletime string building.
///
/// In some cases it is necessary to embed string messages as read only string literals
/// so that they can be used in low memory conditions for example.
/// This header defines two macro's that can be used to build managed string literals.
/// These managed string literals can be concatenated and searched at compiletime.
/// When the compiler is done with this code only the embedded string literal that are
/// used by normal expressions remain in the binary.
/// The implementation is a mix of meta template programming and the use of constexpr
/// expressions and is based on an idea found on <a href="http://stackoverflow.com/a/15912824">stackoverflow</a>.
/// This code is meant to handle small strings (up to say 100 characters). Larger strings can trigger the maximum
/// template recursion depth of the compiler (-ftemplate-depth). This depth is set at 900. So in principle strings
/// of 900 characters can be handled. However this is very inefficient and will prolong compiletime severly.
///
/// Here follows an example of how this code can be used.
/// @code
/// auto str1 = OSDEV_COMPONENTS_CSTRING("This is a test");
/// auto str2 = OSDEV_COMPONENTS_CSTRING(" with concatenation");
/// auto str3 = str1 + str2;
/// const char* s = str3.chars;
/// @endcode
/// str3.chars contains the compiletime concatenated string.
///
/// When examing the binary one will see that it contains the null terminated string literal
/// @code
/// This is a test with concatenation^@
/// @endcode
/// The str1 and str2 literals are discarded since they are never used in a non compiletime way.
/// @note This code is not meant to handle string literals with internal \0 characters. Beware!

#include <limits>

#include "metaprogrammingdefs.h"

/// @brief Build a managed substring string literal from a string literal input
/// The strings are build compile time.
/// The upper bound is one past the last character that the caller is interested in.
#define OSDEV_COMPONENTS_CSTRING_BOUNDED(string_literal, lower, upper)                             \
    [] {                                                                                        \
        constexpr std::size_t lb = (lower);                                                     \
        constexpr std::size_t ub = (upper);                                                     \
        static_assert(lb <= ub, "lower bound must be smaller than or equal to upper bound");    \
        static_assert(ub < sizeof(string_literal),                                              \
            "upper bound must be smaller than or equal to the length of the string");           \
        struct constexpr_string_type                                                            \
        {                                                                                       \
            const char* chars = string_literal;                                                 \
        };                                                                                      \
        return typename osdev::components::mqtt::apply_bounded_range<lb, ub,                             \
            osdev::components::mqtt::string_builder<constexpr_string_type>::template produce>::result{}; \
    }()

/// @brief Build a managed string literal from a string literal input
#define OSDEV_COMPONENTS_CSTRING(string_literal) \
    OSDEV_COMPONENTS_CSTRING_BOUNDED(string_literal, 0, sizeof(string_literal) - 1)

namespace osdev {
namespace components {
namespace mqtt {

/// @brief Managed string literal.
/// This class is used to hold string literals at compile time.
template <char... str>
struct compiletime_string
{
    /// @brief Declaration of the string
    static constexpr const char chars[sizeof...(str) + 1] = { str..., '\0' };
};

/// @brief Definition of the string
template <char... str>
constexpr const char compiletime_string<str...>::chars[sizeof...(str) + 1];

/// @brief Managed string literal builder.
/// This class is used to build string literals at compile time.
template <typename lambda_str_type>
struct string_builder
{
    /// @brief maps indices list on the char array
    template <std::size_t... indices>
    struct produce
    {
        typedef compiletime_string<lambda_str_type{}.chars[indices]...> result;
    };
};

/// @brief Adapter for coupling other producers to string_builder.
/// tparam T The type of producer to adapt.
/// @note The adapter must define its return type as return_t and provide a static method produce() which produces the result.
///       The return type must be a kind of array type of chars (char[len], std::array<char,len>, etc).
template <typename T>
struct adapt_for_string_builder
{
    static constexpr typename T::return_t chars = T::produce();
};

/// @brief Concatenate two managed string literals
/// The literals are packed in a variadic template.
/// These templates are formed by the MLOGIC_COMMON_CSTRING* macro's
/// The concatenation is done at compiletime.
template <char... str0, char... str1>
compiletime_string<str0..., str1...> operator+(compiletime_string<str0...>, compiletime_string<str1...>)
{
    return {};
}

/// @brief Search for a character in a string literal from the right side.
/// The character index is returned relative to the left side.
/// If the character is not found a std::size_t(-1) is returned.
/// The default search starts at the end of the string. The index
/// can be given to start the search at another point in the string.
/// The index is given as nr of characters from the right end side of
/// the string. To proceed with a search see following example.
/// @code
/// constexpr const char s[] = "This is a test";
/// constexpr std::size_t index = osdev_components::mqtt::rfind(s, 'i'); // gives 5
/// static_assert(index == 5, "");
/// constexpr std::size_t index2 = osdev::components::mqtt::rfind(s, 'i', sizeof(s) - index); // gives 2
/// static_assert(index2 == 2, "");
/// @endcode
/// This function should only be used at compile time!
template <std::size_t N>
constexpr std::size_t rfind(const char (&str)[N], char c, std::size_t index = 0)
{
    return index >= N ? std::numeric_limits<std::size_t>::max() : str[N - index - 1] == c ? N - index - 1 : rfind(str, c, index + 1);
}

}       // End namespace mqtt
}       // End namespace components
}       // End namespace osdev

#endif  // OSDEV_COMPONENTS_MQTT_COMPILETIMESTRING_H