AK: Add a StringView method to count the number of lines in a string

We already have a helper to split a StringView by line while considering
"\n", "\r", and "\r\n". Add an analagous method to just count the number
of lines in the same manner.
This commit is contained in:
Timothy Flynn 2024-03-08 09:57:35 -05:00 committed by Tim Flynn
parent 07a27b2ec0
commit 82ea53cf10
Notes: sideshowbarker 2024-07-17 01:10:58 +09:00
3 changed files with 79 additions and 30 deletions

View file

@ -68,6 +68,48 @@ Vector<StringView> StringView::split_view(StringView separator, SplitBehavior sp
return parts; return parts;
} }
template<typename Callback>
static void for_each_line(StringView string, Callback&& callback)
{
char const* characters = string.characters_without_null_termination();
size_t substart = 0;
bool last_ch_was_cr = false;
for (size_t i = 0; i < string.length(); ++i) {
char ch = characters[i];
bool split_view = false;
switch (ch) {
case '\n':
if (last_ch_was_cr)
substart = i + 1;
else
split_view = true;
last_ch_was_cr = false;
break;
case '\r':
split_view = true;
last_ch_was_cr = true;
break;
default:
last_ch_was_cr = false;
break;
}
if (split_view) {
callback(string.substring_view(substart, i - substart));
substart = i + 1;
}
}
if (size_t taillen = string.length() - substart; taillen != 0)
callback(string.substring_view(substart, taillen));
}
Vector<StringView> StringView::lines(ConsiderCarriageReturn consider_carriage_return) const Vector<StringView> StringView::lines(ConsiderCarriageReturn consider_carriage_return) const
{ {
if (is_empty()) if (is_empty())
@ -76,36 +118,24 @@ Vector<StringView> StringView::lines(ConsiderCarriageReturn consider_carriage_re
if (consider_carriage_return == ConsiderCarriageReturn::No) if (consider_carriage_return == ConsiderCarriageReturn::No)
return split_view('\n', SplitBehavior::KeepEmpty); return split_view('\n', SplitBehavior::KeepEmpty);
Vector<StringView> v; Vector<StringView> lines;
size_t substart = 0; for_each_line(*this, [&](auto line) { lines.append(line); });
bool last_ch_was_cr = false;
bool split_view = false; return lines;
for (size_t i = 0; i < length(); ++i) { }
char ch = characters_without_null_termination()[i];
if (ch == '\n') { size_t StringView::count_lines(ConsiderCarriageReturn consider_carriage_return) const
split_view = true; {
if (last_ch_was_cr) { if (is_empty())
substart = i + 1; return 1;
split_view = false;
} if (consider_carriage_return == ConsiderCarriageReturn::No)
} return count('\n') + 1;
if (ch == '\r') {
split_view = true; size_t lines = 0;
last_ch_was_cr = true; for_each_line(*this, [&](auto) { ++lines; });
} else {
last_ch_was_cr = false; return lines;
}
if (split_view) {
size_t sublen = i - substart;
v.append(substring_view(substart, sublen));
substart = i + 1;
}
split_view = false;
}
size_t taillen = length() - substart;
if (taillen != 0)
v.append(substring_view(substart, taillen));
return v;
} }
bool StringView::starts_with(char ch) const bool StringView::starts_with(char ch) const

View file

@ -240,6 +240,7 @@ public:
Yes, Yes,
}; };
[[nodiscard]] Vector<StringView> lines(ConsiderCarriageReturn = ConsiderCarriageReturn::Yes) const; [[nodiscard]] Vector<StringView> lines(ConsiderCarriageReturn = ConsiderCarriageReturn::Yes) const;
[[nodiscard]] size_t count_lines(ConsiderCarriageReturn = ConsiderCarriageReturn::Yes) const;
// Create a new substring view of this string view, starting either at the beginning of // Create a new substring view of this string view, starting either at the beginning of
// the given substring view, or after its end, and continuing until the end of this string // the given substring view, or after its end, and continuing until the end of this string

View file

@ -105,6 +105,24 @@ TEST_CASE(lines)
EXPECT_EQ(test_string_vector.at(2).is_empty(), true); EXPECT_EQ(test_string_vector.at(2).is_empty(), true);
} }
TEST_CASE(count_lines)
{
EXPECT_EQ(""sv.count_lines(), 1u);
EXPECT_EQ("foo"sv.count_lines(), 1u);
EXPECT_EQ("foo\nbar"sv.count_lines(), 2u);
EXPECT_EQ("foo\rbar"sv.count_lines(), 2u);
EXPECT_EQ("foo\rbar"sv.count_lines(StringView::ConsiderCarriageReturn::No), 1u);
EXPECT_EQ("foo\r\nbar"sv.count_lines(), 2u);
EXPECT_EQ("foo\r\nbar"sv.count_lines(StringView::ConsiderCarriageReturn::No), 2u);
EXPECT_EQ("foo\nbar\nbax"sv.count_lines(), 3u);
EXPECT_EQ("foo\rbar\rbaz"sv.count_lines(), 3u);
EXPECT_EQ("foo\rbar\rbaz"sv.count_lines(StringView::ConsiderCarriageReturn::No), 1u);
EXPECT_EQ("foo\r\nbar\r\nbaz"sv.count_lines(), 3u);
EXPECT_EQ("foo\r\nbar\r\nbaz"sv.count_lines(StringView::ConsiderCarriageReturn::No), 3u);
}
TEST_CASE(find) TEST_CASE(find)
{ {
auto test_string_view = "aabbcc_xy_ccbbaa"sv; auto test_string_view = "aabbcc_xy_ccbbaa"sv;