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 <algorithm>
6#include <cctype>
8namespace cheatah::string {
10namespace {
11char up(char c) { return static_cast<char>(std::toupper(static_cast<unsigned char>(c))); }
12char lo(char c) { return static_cast<char>(std::tolower(static_cast<unsigned char>(c))); }
13bool in_set(char c, std::string_view set) { return set.find(c) != std::string_view::npos; }
14} // namespace
16std::string upper(std::string_view s) {
17 std::string r(s);
18 for (char& c : r) c = up(c);
19 return r;
21std::string lower(std::string_view s) {
22 std::string r(s);
23 for (char& c : r) c = lo(c);
24 return r;
26std::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;
34std::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;
44std::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;
54std::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));
59std::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));
64std::string strip(std::string_view s, std::string_view chars) { return rstrip(lstrip(s, chars), chars); }
66bool startswith(std::string_view s, std::string_view prefix) {
67 return s.size() >= prefix.size() && s.compare(0, prefix.size(), prefix) == 0;
69bool 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;
73bool contains(std::string_view s, std::string_view sub) { return s.find(sub) != std::string_view::npos; }
75long 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);
80long 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);
86long 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);
90long count(std::string_view s, std::string_view sub) {
91 if (sub.empty()) return static_cast<long>(s.size()) + 1; // Python: len+1
92 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;
101std::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;
114std::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;
128std::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;
141std::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;
158std::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);
164std::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;
170std::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);
175std::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);
182std::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);
191namespace {
192template <typename Pred>
193bool 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)); });
197} // namespace
199bool isdigit(std::string_view s) { return all_of_nonempty(s, [](unsigned char c) { return std::isdigit(c) != 0; }); }
200bool isalpha(std::string_view s) { return all_of_nonempty(s, [](unsigned char c) { return std::isalpha(c) != 0; }); }
201bool isalnum(std::string_view s) { return all_of_nonempty(s, [](unsigned char c) { return std::isalnum(c) != 0; }); }
202bool isspace(std::string_view s) { return all_of_nonempty(s, [](unsigned char c) { return std::isspace(c) != 0; }); }
204bool 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;
213bool 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;
223} // namespace cheatah::string