Created
June 15, 2021 02:56
-
-
Save t2ym/b4349f1f8845e11499a39a562b1384ba to your computer and use it in GitHub Desktop.
Kotlin-style trimIndent() at compile time for C++ raw literals
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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 |
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
Original author: https://davidgorski.ca/
Notes
Modifications by @t2ym (https://github.com/t2ym)
main()
by#ifdef COMPILE_TIME_TRUNCATION_EXAMPLE ... #endif
""
tostatic_assert()
to comply with C++11truncateWhitespace()
in thecompiletime
namespaceis_whitespace()
inlinetrimIndent()
Current usage