Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@t2ym
Created June 15, 2021 02:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save t2ym/b4349f1f8845e11499a39a562b1384ba to your computer and use it in GitHub Desktop.
Save t2ym/b4349f1f8845e11499a39a562b1384ba to your computer and use it in GitHub Desktop.
Kotlin-style trimIndent() at compile time for C++ raw literals
/*
* Blog of the original author: https://davidgorski.ca/posts/truncate-string-whitespace-compiletime-cpp/
* Original source gist: https://gist.github.com/dgski/810ede7c4a80917c0adc99c6852fee9a
*
* Modifications by @t2ym (https://github.com/t2ym)
* - Rearranged as a header file
* - Surrounded main() by #ifdef COMPILE_TIME_TRUNCATION_EXAMPLE ... #endif
* - Added an argument "" to static_assert() to comply with C++11
* - Put truncateWhitespace() in the compiletime namespace
* - Made is_whitespace() inline
* - Added Kotlin-style trimIndent()
*/
#ifndef COMPILE_TIME_TRUNCATION_H
#define COMPILE_TIME_TRUNCATION_H
#include <iterator>
namespace compiletime {
template<std::size_t MaxSize = 30>
class string
{
char m_data[MaxSize] = { 0 };
std::size_t m_size;
public:
constexpr string() : m_data(), m_size(0) {}
constexpr string(const char* str) : m_data(), m_size(0) {
for(int i =0; i<MaxSize; ++i) {
m_data[m_size++] = str[i];
}
}
constexpr char const* data() const { return m_data; }
constexpr operator const char*() const { return data(); }
constexpr void push_back(char c) { m_data[m_size++] = c; }
constexpr char& operator[](std::size_t i) { return m_data[i]; }
constexpr char const& operator[](std::size_t i) const { return m_data[i]; }
constexpr size_t size() const { return m_size; }
constexpr const char* begin() const { return std::begin(m_data); }
constexpr const char* end() const { return std::begin(m_data) + m_size; }
};
inline constexpr bool is_whitespace(char c) {
return
(c == ' ') ||
(c == '\t') ||
(c == '\n') ||
(c == '\v') ||
(c == '\f') ||
(c == '\r');
}
template<std::size_t N>
constexpr auto truncateWhitespace(compiletime::string<N> str) {
compiletime::string<N> result;
bool previousIsWhitespace = false;
for(char c : str) {
if(c == '\n') {
continue;
} else if(is_whitespace(c)) {
if(previousIsWhitespace) {
continue;
}
previousIsWhitespace = true;
} else {
previousIsWhitespace = false;
}
result.push_back(c);
}
return result;
}
template<std::size_t N>
constexpr auto truncateWhitespace(const char (&str)[N])
{
compiletime::string<N> tmp(str);
return truncateWhitespace(tmp);
}
template<std::size_t N>
constexpr std::size_t detect_min_indent(compiletime::string<N> str) {
std::size_t min_indent = 0;
std::size_t tmp_indent = 0;
bool is_blankline = true;
bool no_min_indent = true;
for(char c : str) {
if(c == '\0') {
break;
} else if(c == '\n') {
if (!is_blankline) {
if (no_min_indent || min_indent > tmp_indent) {
min_indent = tmp_indent;
no_min_indent = false;
}
}
tmp_indent = 0;
is_blankline = true;
} else if(is_whitespace(c)) {
if (is_blankline) {
tmp_indent++;
}
} else {
is_blankline = false;
}
}
if (!is_blankline) {
if (no_min_indent || min_indent > tmp_indent) {
min_indent = tmp_indent;
no_min_indent = false;
}
}
return min_indent;
}
// Kotlin-style trimIndent()
// Note: No tabs are supported
template<std::size_t N>
constexpr auto trimIndent(compiletime::string<N> str) {
compiletime::string<N> result;
std::size_t min_indent = detect_min_indent(str);
std::size_t tmp_indent = 0;
bool is_firstline = true;
bool is_blankline = true;
for (char c : str) {
if (is_firstline) {
if (c == '\0') {
result.push_back(c);
break;
} else if (c == '\n') {
if (is_blankline) {
} else {
result.push_back(c);
}
is_firstline = false;
tmp_indent = 0;
continue;
} else if(is_whitespace(c)){
if (is_blankline) {
// prepending whitespaces in the first line are ignored
} else {
result.push_back(c);
}
} else {
result.push_back(c);
is_blankline = false;
}
}
else {
if (c == '\0') {
result.push_back(c);
break;
} else if (c == '\n') {
result.push_back(c);
tmp_indent = 0;
continue;
} else if(is_whitespace(c)) {
if (tmp_indent < min_indent) {
} else {
result.push_back(c);
}
} else {
result.push_back(c);
}
}
tmp_indent++;
}
return result;
}
// Kotlin-style trimIndent()
template<std::size_t N>
constexpr auto trimIndent(const char (&str)[N])
{
compiletime::string<N> tmp(str);
return trimIndent(tmp);
}
} // namespace compiletime
#ifdef COMPILE_TIME_TRUNCATION_EXAMPLE
int main() {
using namespace compiletime;
// Old approach: Messy code
const char query1[] =
" SELECT "
" u.id, "
" u.user_name, "
" u.ref_id, "
" u.postal_code, "
" u.email, "
" o.transaction.id "
" FROM "
" users u "
" JOIN "
" orders o ON o.user_id = u.id "
" WHERE "
" u.id=? AND u.active=? ";
// Raw string literal: Lot's of whitespace chars included
const char query2[] = R"(
SELECT
u.id,
u.user_name,
u.ref_id,
u.postal_code,
u.email,
o.transaction.id
FROM
users u
JOIN
orders o ON o.user_id = u.id
WHERE
u.id=? AND u.active=?
)";
// Raw string literal slimmed at compile time
constexpr auto query3 = truncateWhitespace(R"(
SELECT
u.id,
u.user_name,
u.ref_id,
u.postal_code,
u.email,
o.transaction.id
FROM
users u
JOIN
orders o ON o.user_id = u.id
WHERE
u.id=? AND u.active=?
)");
static_assert(query3.size() == 154, "");
return 0;
}
#endif // COMPILE_TIME_TRUNCATION_EXAMPLE
#endif // COMPILE_TIME_TRUNCATION_H
@t2ym
Copy link
Author

t2ym commented Jun 15, 2021

Original author: https://davidgorski.ca/

Notes

  • Requires C++14

Modifications by @t2ym (https://github.com/t2ym)

  • Rearranged as a header file
  • Surrounded main() by #ifdef COMPILE_TIME_TRUNCATION_EXAMPLE ... #endif
  • Added an argument "" to static_assert() to comply with C++11
  • Put truncateWhitespace() in the compiletime namespace
  • Made is_whitespace() inline
  • Added Kotlin-style trimIndent()

Current usage

@t2ym
Copy link
Author

t2ym commented Jun 15, 2021

Example trim_indent.cc

  • Note: strings trim_indent | less to verify the truncated strings are really compiled as constant strings
#include <iostream>

//#define COMPILE_TIME_TRUNCATION_EXAMPLE
#include "compile_time_truncation.h"

#ifndef COMPILE_TIME_TRUNCATION_EXAMPLE
int main(int argc, char *argv[]) {
    constexpr auto query1 = compiletime::truncateWhitespace(R"(
        SELECT
            u.id,
            u.user_name,
            u.ref_id,
            u.postal_code,
            u.email,
            o.transaction.id
        FROM
            users u
        JOIN
            orders o ON o.user_id = u.id
        WHERE
            u.id=? AND u.active=?
    )");
    std::cout << query1 << std::endl;
    constexpr auto query2 = compiletime::trimIndent(R"(
        SELECT
            u.id,
            u.user_name,
            u.ref_id,
            u.postal_code,
            u.email,
            o.transaction.id
        FROM
            users u
        JOIN
            orders o ON o.user_id = u.id
        WHERE
            u.id=? AND u.active=?
    )");
    std::cout << query2 << std::endl;

    return 0;
}
#endif

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment