cheatah
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 <cctype>
7namespace cheatah::string {
9namespace {
10char up(char c) { return static_cast<char>(std::toupper(static_cast<unsigned char>(c))); }
11char lo(char c) { return static_cast<char>(std::tolower(static_cast<unsigned char>(c))); }
12bool in_set(char c, std::string_view set) { return set.find(c) != std::string_view::npos; }
13} // namespace
15std::string upper(std::string_view s) {
16 std::string r(s);
17 for (char& c : r) c = up(c);
18 return r;
20std::string lower(std::string_view s) {
21 std::string r(s);
22 for (char& c : r) c = lo(c);
23 return r;
25std::string capitalize(std::string_view s) {
26 std::string r(s);
27 if (!r.empty()) {
28 r[0] = up(r[0]);
29 for (std::size_t i = 1; i < r.size(); ++i) r[i] = lo(r[i]);
30 }
31 return r;
33std::string title(std::string_view s) {
34 std::string r(s);
35 bool prev_alpha = false;
36 for (char& c : r) {
37 const bool alpha = std::isalpha(static_cast<unsigned char>(c)) != 0;
38 if (alpha) c = prev_alpha ? lo(c) : up(c);
39 prev_alpha = alpha;
40 }
41 return r;
43std::string swapcase(std::string_view s) {
44 std::string r(s);
45 for (char& c : r) {
46 const unsigned char u = static_cast<unsigned char>(c);
47 if (std::islower(u)) c = up(c);
48 else if (std::isupper(u)) c = lo(c);
49 }
50 return r;
53std::string lstrip(std::string_view s, std::string_view chars) {
54 std::size_t i = 0;
55 while (i < s.size() && in_set(s[i], chars)) ++i;
56 return std::string(s.substr(i));
58std::string rstrip(std::string_view s, std::string_view chars) {
59 std::size_t n = s.size();
60 while (n > 0 && in_set(s[n - 1], chars)) --n;
61 return std::string(s.substr(0, n));
63std::string strip(std::string_view s, std::string_view chars) { return rstrip(lstrip(s, chars), chars); }
65bool startswith(std::string_view s, std::string_view prefix) {
66 return s.size() >= prefix.size() && s.compare(0, prefix.size(), prefix) == 0;
68bool endswith(std::string_view s, std::string_view suffix) {
69 return s.size() >= suffix.size() &&
70 s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0;
72bool contains(std::string_view s, std::string_view sub) { return s.find(sub) != std::string_view::npos; }
74long find(std::string_view s, std::string_view sub) {
75 const auto p = s.find(sub);
76 return p == std::string_view::npos ? -1 : static_cast<long>(p);
79long find(std::string_view s, std::string_view sub, long start) {
80 if (start < 0) start = 0;
81 if (static_cast<std::size_t>(start) > s.size()) return -1;
82 const auto p = s.find(sub, static_cast<std::size_t>(start));
83 return p == std::string_view::npos ? -1 : static_cast<long>(p);
85long rfind(std::string_view s, std::string_view sub) {
86 const auto p = s.rfind(sub);
87 return p == std::string_view::npos ? -1 : static_cast<long>(p);
89long count(std::string_view s, std::string_view sub) {
90 if (sub.empty()) return static_cast<long>(s.size()) + 1; // Python: len+1
91 long n = 0;
92 std::size_t pos = 0;
93 while ((pos = s.find(sub, pos)) != std::string_view::npos) {
94 ++n;
95 pos += sub.size();
96 }
97 return n;
100std::string replace(std::string_view s, std::string_view from, std::string_view to) {
101 if (from.empty()) return std::string(s);
102 std::string r;
103 std::size_t prev = 0, pos;
104 while ((pos = s.find(from, prev)) != std::string_view::npos) {
105 r.append(s.substr(prev, pos - prev));
106 r.append(to);
107 prev = pos + from.size();
108 }
109 r.append(s.substr(prev));
110 return r;
113std::vector<std::string> split(std::string_view s, std::string_view sep) {
114 std::vector<std::string> out;
115 if (sep.empty()) {
116 out.emplace_back(s);
117 return out;
118 }
119 std::size_t prev = 0, pos;
120 while ((pos = s.find(sep, prev)) != std::string_view::npos) {
121 out.emplace_back(s.substr(prev, pos - prev));
122 prev = pos + sep.size();
123 }
124 out.emplace_back(s.substr(prev));
125 return out;
127std::vector<std::string> split(std::string_view s) {
128 std::vector<std::string> out;
129 const std::size_t n = s.size();
130 std::size_t i = 0;
131 while (i < n) {
132 while (i < n && in_set(s[i], whitespace)) ++i;
133 if (i >= n) break;
134 const std::size_t start = i;
135 while (i < n && !in_set(s[i], whitespace)) ++i;
136 out.emplace_back(s.substr(start, i - start));
137 }
138 return out;
140std::vector<std::string> splitlines(std::string_view s) {
141 std::vector<std::string> out;
142 const std::size_t n = s.size();
143 std::size_t i = 0, start = 0;
144 while (i < n) {
145 if (s[i] == '\n' || s[i] == '\r') {
146 out.emplace_back(s.substr(start, i - start));
147 if (s[i] == '\r' && i + 1 < n && s[i + 1] == '\n') ++i;
148 ++i;
149 start = i;
150 } else {
151 ++i;
152 }
153 }
154 if (start < n) out.emplace_back(s.substr(start));
155 return out;
157std::string capwords(std::string_view s) {
158 std::vector<std::string> words = split(s);
159 for (std::string& w : words) w = capitalize(w);
160 return join(" ", words);
163std::string ljust(std::string_view s, std::size_t width, std::string_view fill) {
164 const char f = fill.empty() ? ' ' : fill[0];
165 std::string r(s);
166 if (r.size() < width) r.append(width - r.size(), f);
167 return r;
169std::string rjust(std::string_view s, std::size_t width, std::string_view fill) {
170 const char f = fill.empty() ? ' ' : fill[0];
171 if (s.size() >= width) return std::string(s);
172 return std::string(width - s.size(), f) + std::string(s);
174std::string center(std::string_view s, std::size_t width, std::string_view fill) {
175 const char f = fill.empty() ? ' ' : fill[0];
176 if (s.size() >= width) return std::string(s);
177 const std::size_t total = width - s.size();
178 const std::size_t left = total / 2;
179 return std::string(left, f) + std::string(s) + std::string(total - left, f);
181std::string zfill(std::string_view s, std::size_t width) {
182 if (s.size() >= width) return std::string(s);
183 const std::size_t pad = width - s.size();
184 if (!s.empty() && (s[0] == '+' || s[0] == '-')) {
185 return std::string(1, s[0]) + std::string(pad, '0') + std::string(s.substr(1));
186 }
187 return std::string(pad, '0') + std::string(s);
190namespace {
191template <typename Pred>
192bool all_of_nonempty(std::string_view s, Pred p) {
193 if (s.empty()) return false;
194 for (char c : s) {
195 if (!p(static_cast<unsigned char>(c))) return false;
196 }
197 return true;
199} // namespace
201bool isdigit(std::string_view s) { return all_of_nonempty(s, [](unsigned char c) { return std::isdigit(c) != 0; }); }
202bool isalpha(std::string_view s) { return all_of_nonempty(s, [](unsigned char c) { return std::isalpha(c) != 0; }); }
203bool isalnum(std::string_view s) { return all_of_nonempty(s, [](unsigned char c) { return std::isalnum(c) != 0; }); }
204bool isspace(std::string_view s) { return all_of_nonempty(s, [](unsigned char c) { return std::isspace(c) != 0; }); }
206bool isupper(std::string_view s) {
207 bool has = false;
208 for (char c : s) {
209 const unsigned char u = static_cast<unsigned char>(c);
210 if (std::islower(u)) return false;
211 if (std::isupper(u)) has = true;
212 }
213 return has;
215bool islower(std::string_view s) {
216 bool has = false;
217 for (char c : s) {
218 const unsigned char u = static_cast<unsigned char>(c);
219 if (std::isupper(u)) return false;
220 if (std::islower(u)) has = true;
221 }
222 return has;
225} // namespace cheatah::string