Source
stdlib/string/string.cpp
1
// Copyright (c) 2026 BigBrain LLC. MIT-licensed (see LICENSE).2
// Original work; see ACKNOWLEDGMENTS.md for the open-source ideas we build upon.3
#include "string.hpp"5
#include <algorithm>6
#include <cctype>8
namespace cheatah::string {10
namespace {11
char up(char c) { return static_cast<char>(std::toupper(static_cast<unsigned char>(c))); }12
char lo(char c) { return static_cast<char>(std::tolower(static_cast<unsigned char>(c))); }13
bool in_set(char c, std::string_view set) { return set.find(c) != std::string_view::npos; }14
} // namespace16
std::string upper(std::string_view s) {17
std::string r(s);18
for (char& c : r) c = up(c);19
return r;20
}21
std::string lower(std::string_view s) {22
std::string r(s);23
for (char& c : r) c = lo(c);24
return r;25
}26
std::string capitalize(std::string_view s) {27
std::string r(s);28
if (!r.empty()) {29
r[0] = up(r[0]);30
for (std::size_t i = 1; i < r.size(); ++i) r[i] = lo(r[i]);31
}32
return r;33
}34
std::string title(std::string_view s) {35
std::string r(s);36
bool prev_alpha = false;37
for (char& c : r) {38
const bool alpha = std::isalpha(static_cast<unsigned char>(c)) != 0;39
if (alpha) c = prev_alpha ? lo(c) : up(c);40
prev_alpha = alpha;41
}42
return r;43
}44
std::string swapcase(std::string_view s) {45
std::string r(s);46
for (char& c : r) {47
const auto u = static_cast<unsigned char>(c);48
if (std::islower(u)) c = up(c);49
else if (std::isupper(u)) c = lo(c);50
}51
return r;52
}54
std::string lstrip(std::string_view s, std::string_view chars) {55
std::size_t i = 0;56
while (i < s.size() && in_set(s[i], chars)) ++i;57
return std::string(s.substr(i));58
}59
std::string rstrip(std::string_view s, std::string_view chars) {60
std::size_t n = s.size();61
while (n > 0 && in_set(s[n - 1], chars)) --n;62
return std::string(s.substr(0, n));63
}64
std::string strip(std::string_view s, std::string_view chars) { return rstrip(lstrip(s, chars), chars); }66
bool startswith(std::string_view s, std::string_view prefix) {67
return s.size() >= prefix.size() && s.compare(0, prefix.size(), prefix) == 0;68
}69
bool endswith(std::string_view s, std::string_view suffix) {70
return s.size() >= suffix.size() &&71
s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0;72
}73
bool contains(std::string_view s, std::string_view sub) { return s.find(sub) != std::string_view::npos; }75
long find(std::string_view s, std::string_view sub) {76
const auto p = s.find(sub);77
return p == std::string_view::npos ? -1 : static_cast<long>(p);78
}80
long find(std::string_view s, std::string_view sub, long start) {81
if (start < 0) start = 0;82
if (static_cast<std::size_t>(start) > s.size()) return -1;83
const auto p = s.find(sub, static_cast<std::size_t>(start));84
return p == std::string_view::npos ? -1 : static_cast<long>(p);85
}86
long rfind(std::string_view s, std::string_view sub) {87
const auto p = s.rfind(sub);88
return p == std::string_view::npos ? -1 : static_cast<long>(p);89
}90
long count(std::string_view s, std::string_view sub) {91
if (sub.empty()) return static_cast<long>(s.size()) + 1; // Python: len+192
long n = 0;93
std::size_t pos = 0;94
while ((pos = s.find(sub, pos)) != std::string_view::npos) {95
++n;96
pos += sub.size();97
}98
return n;99
}101
std::string replace(std::string_view s, std::string_view from, std::string_view to) {102
if (from.empty()) return std::string(s);103
std::string r;104
std::size_t prev = 0, pos = 0;105
while ((pos = s.find(from, prev)) != std::string_view::npos) {106
r.append(s.substr(prev, pos - prev));107
r.append(to);108
prev = pos + from.size();109
}110
r.append(s.substr(prev));111
return r;112
}114
std::vector<std::string> split(std::string_view s, std::string_view sep) {115
std::vector<std::string> out;116
if (sep.empty()) {117
out.emplace_back(s);118
return out;119
}120
std::size_t prev = 0, pos = 0;121
while ((pos = s.find(sep, prev)) != std::string_view::npos) {122
out.emplace_back(s.substr(prev, pos - prev));123
prev = pos + sep.size();124
}125
out.emplace_back(s.substr(prev));126
return out;127
}128
std::vector<std::string> split(std::string_view s) {129
std::vector<std::string> out;130
const std::size_t n = s.size();131
std::size_t i = 0;132
while (i < n) {133
while (i < n && in_set(s[i], whitespace)) ++i;134
if (i >= n) break;135
const std::size_t start = i;136
while (i < n && !in_set(s[i], whitespace)) ++i;137
out.emplace_back(s.substr(start, i - start));138
}139
return out;140
}141
std::vector<std::string> splitlines(std::string_view s) {142
std::vector<std::string> out;143
const std::size_t n = s.size();144
std::size_t i = 0, start = 0;145
while (i < n) {146
if (s[i] == '\n' || s[i] == '\r') {147
out.emplace_back(s.substr(start, i - start));148
if (s[i] == '\r' && i + 1 < n && s[i + 1] == '\n') ++i;149
++i;150
start = i;151
} else {152
++i;153
}154
}155
if (start < n) out.emplace_back(s.substr(start));156
return out;157
}158
std::string capwords(std::string_view s) {159
std::vector<std::string> words = split(s);160
for (std::string& w : words) w = capitalize(w);161
return join(" ", words);162
}164
std::string ljust(std::string_view s, std::size_t width, std::string_view fill) {165
const char f = fill.empty() ? ' ' : fill[0];166
std::string r(s);167
if (r.size() < width) r.append(width - r.size(), f);168
return r;169
}170
std::string rjust(std::string_view s, std::size_t width, std::string_view fill) {171
const char f = fill.empty() ? ' ' : fill[0];172
if (s.size() >= width) return std::string(s);173
return std::string(width - s.size(), f) + std::string(s);174
}175
std::string center(std::string_view s, std::size_t width, std::string_view fill) {176
const char f = fill.empty() ? ' ' : fill[0];177
if (s.size() >= width) return std::string(s);178
const std::size_t total = width - s.size();179
const std::size_t left = total / 2;180
return std::string(left, f) + std::string(s) + std::string(total - left, f);181
}182
std::string zfill(std::string_view s, std::size_t width) {183
if (s.size() >= width) return std::string(s);184
const std::size_t pad = width - s.size();185
if (!s.empty() && (s[0] == '+' || s[0] == '-')) {186
return std::string(1, s[0]) + std::string(pad, '0') + std::string(s.substr(1));187
}188
return std::string(pad, '0') + std::string(s);189
}191
namespace {192
template <typename Pred>193
bool all_of_nonempty(std::string_view s, Pred p) {194
if (s.empty()) return false;195
return std::ranges::all_of(s, [&p](char c) { return p(static_cast<unsigned char>(c)); });196
}197
} // namespace199
bool isdigit(std::string_view s) { return all_of_nonempty(s, [](unsigned char c) { return std::isdigit(c) != 0; }); }200
bool isalpha(std::string_view s) { return all_of_nonempty(s, [](unsigned char c) { return std::isalpha(c) != 0; }); }201
bool isalnum(std::string_view s) { return all_of_nonempty(s, [](unsigned char c) { return std::isalnum(c) != 0; }); }202
bool isspace(std::string_view s) { return all_of_nonempty(s, [](unsigned char c) { return std::isspace(c) != 0; }); }204
bool isupper(std::string_view s) {205
bool has = false;206
for (char c : s) {207
const auto u = static_cast<unsigned char>(c);208
if (std::islower(u)) return false;209
if (std::isupper(u)) has = true;210
}211
return has;212
}213
bool islower(std::string_view s) {214
bool has = false;215
for (char c : s) {216
const auto u = static_cast<unsigned char>(c);217
if (std::isupper(u)) return false;218
if (std::islower(u)) has = true;219
}220
return has;221
}223
} // namespace cheatah::string