Skip to content

Commit

Permalink
AK: Move the wildcard-matching implementation to StringUtils
Browse files Browse the repository at this point in the history
Provide wrappers in the String and StringView classes, and add some tests.
  • Loading branch information
howar6hill authored and awesomekling committed Mar 2, 2020
1 parent 2a30a02 commit 055344f
Show file tree
Hide file tree
Showing 16 changed files with 147 additions and 62 deletions.
54 changes: 1 addition & 53 deletions AK/String.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -331,59 +331,7 @@ String String::repeated(char ch, size_t count)

bool String::matches(const StringView& mask, CaseSensitivity case_sensitivity) const
{
if (case_sensitivity == CaseSensitivity::CaseInsensitive) {
String this_lower = this->to_lowercase();
String mask_lower = String(mask).to_lowercase();
return this_lower.match_helper(mask_lower);
}

return match_helper(mask);
}

bool String::match_helper(const StringView& mask) const
{
if (is_null())
return false;

const char* string_ptr = characters();
const char* mask_ptr = mask.characters_without_null_termination();
const char* mask_end = mask_ptr + mask.length();

// Match string against mask directly unless we hit a *
while ((*string_ptr) && (mask_ptr < mask_end) && (*mask_ptr != '*')) {
if ((*mask_ptr != *string_ptr) && (*mask_ptr != '?'))
return false;
mask_ptr++;
string_ptr++;
}

const char* cp = nullptr;
const char* mp = nullptr;

while (*string_ptr) {
if ((mask_ptr < mask_end) && (*mask_ptr == '*')) {
// If we have only a * left, there is no way to not match.
if (++mask_ptr == mask_end)
return true;
mp = mask_ptr;
cp = string_ptr + 1;
} else if ((mask_ptr < mask_end) && ((*mask_ptr == *string_ptr) || (*mask_ptr == '?'))) {
mask_ptr++;
string_ptr++;
} else if ((cp != nullptr) && (mp != nullptr)) {
mask_ptr = mp;
string_ptr = cp++;
} else {
break;
}
}

// Handle any trailing mask
while ((mask_ptr < mask_end) && (*mask_ptr == '*'))
mask_ptr++;

// If we 'ate' all of the mask and the string then we match.
return (mask_ptr == mask_end) && !*string_ptr;
return StringUtils::matches(*this, mask, case_sensitivity);
}

bool String::contains(const String& needle) const
Expand Down
9 changes: 2 additions & 7 deletions AK/String.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <AK/Forward.h>
#include <AK/RefPtr.h>
#include <AK/StringImpl.h>
#include <AK/StringUtils.h>
#include <AK/StringView.h>
#include <AK/Traits.h>

Expand Down Expand Up @@ -108,13 +109,8 @@ class String {
{
}

enum class CaseSensitivity {
CaseInsensitive,
CaseSensitive,
};

static String repeated(char, size_t count);
bool matches(const StringView& pattern, CaseSensitivity = CaseSensitivity::CaseInsensitive) const;
bool matches(const StringView& mask, CaseSensitivity = CaseSensitivity::CaseInsensitive) const;

// FIXME: These should be shared between String and StringView somehow!
int to_int(bool& ok) const;
Expand Down Expand Up @@ -244,7 +240,6 @@ class String {
}

private:
bool match_helper(const StringView& mask) const;
RefPtr<StringImpl> m_impl;
};

Expand Down
64 changes: 64 additions & 0 deletions AK/StringUtils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#include <AK/String.h>
#include <AK/StringUtils.h>
#include <AK/StringView.h>

namespace AK {

namespace StringUtils {

bool matches(const StringView& str, const StringView& mask, CaseSensitivity case_sensitivity)
{
if (str.is_null() || mask.is_null())
return str.is_null() && mask.is_null();

if (case_sensitivity == CaseSensitivity::CaseInsensitive) {
const String str_lower = String(str).to_lowercase();
const String mask_lower = String(mask).to_lowercase();
return matches(str_lower, mask_lower, CaseSensitivity::CaseSensitive);
}

const char* string_ptr = str.characters_without_null_termination();
const char* string_end = string_ptr + str.length();
const char* mask_ptr = mask.characters_without_null_termination();
const char* mask_end = mask_ptr + mask.length();

// Match string against mask directly unless we hit a *
while ((string_ptr < string_end) && (mask_ptr < mask_end) && (*mask_ptr != '*')) {
if ((*mask_ptr != *string_ptr) && (*mask_ptr != '?'))
return false;
mask_ptr++;
string_ptr++;
}

const char* cp = nullptr;
const char* mp = nullptr;

while (string_ptr < string_end) {
if ((mask_ptr < mask_end) && (*mask_ptr == '*')) {
// If we have only a * left, there is no way to not match.
if (++mask_ptr == mask_end)
return true;
mp = mask_ptr;
cp = string_ptr + 1;
} else if ((mask_ptr < mask_end) && ((*mask_ptr == *string_ptr) || (*mask_ptr == '?'))) {
mask_ptr++;
string_ptr++;
} else if ((cp != nullptr) && (mp != nullptr)) {
mask_ptr = mp;
string_ptr = cp++;
} else {
break;
}
}

// Handle any trailing mask
while ((mask_ptr < mask_end) && (*mask_ptr == '*'))
mask_ptr++;

// If we 'ate' all of the mask and the string then we match.
return (mask_ptr == mask_end) && string_ptr == string_end;
}

}

}
20 changes: 20 additions & 0 deletions AK/StringUtils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once

#include <AK/Forward.h>

namespace AK {

enum class CaseSensitivity {
CaseInsensitive,
CaseSensitive,
};

namespace StringUtils {

bool matches(const StringView& str, const StringView& mask, CaseSensitivity = CaseSensitivity::CaseInsensitive);

}

}

using AK::CaseSensitivity;
5 changes: 5 additions & 0 deletions AK/StringView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ bool StringView::ends_with(const StringView& str) const
return !memcmp(characters_without_null_termination() + length() - str.length(), str.characters_without_null_termination(), str.length());
}

bool StringView::matches(const StringView& mask, CaseSensitivity case_sensitivity) const
{
return StringUtils::matches(*this, mask, case_sensitivity);
}

StringView StringView::substring_view(size_t start, size_t length) const
{
ASSERT(start + length <= m_length);
Expand Down
2 changes: 2 additions & 0 deletions AK/StringView.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

#include <AK/Forward.h>
#include <AK/StdLibExtras.h>
#include <AK/StringUtils.h>

namespace AK {

Expand Down Expand Up @@ -65,6 +66,7 @@ class StringView {
bool ends_with(const StringView&) const;
bool starts_with(char) const;
bool ends_with(char) const;
bool matches(const StringView& mask, CaseSensitivity = CaseSensitivity::CaseInsensitive) const;

StringView substring_view(size_t start, size_t length) const;
Vector<StringView> split_view(char, bool keep_empty = false) const;
Expand Down
2 changes: 1 addition & 1 deletion AK/TestSuite.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ NonnullRefPtrVector<TestCase> TestSuite::find_cases(const String& search, bool f
{
NonnullRefPtrVector<TestCase> matches;
for (const auto& t : m_cases) {
if (!search.is_empty() && !t.name().matches(search, String::CaseSensitivity::CaseInsensitive)) {
if (!search.is_empty() && !t.name().matches(search, CaseSensitivity::CaseInsensitive)) {
continue;
}

Expand Down
1 change: 1 addition & 0 deletions AK/Tests/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ SHARED_TEST_SOURCES = \
../StringImpl.cpp \
../StringBuilder.cpp \
../StringView.cpp \
../StringUtils.cpp \
../LogStream.cpp \
../JsonValue.cpp \
../JsonParser.cpp \
Expand Down
44 changes: 44 additions & 0 deletions AK/Tests/TestStringUtils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include <AK/StringUtils.h>
#include <AK/TestSuite.h>

TEST_CASE(matches_null)
{
EXPECT(AK::StringUtils::matches(StringView(), StringView()));

EXPECT(!AK::StringUtils::matches(StringView(), ""));
EXPECT(!AK::StringUtils::matches(StringView(), "*"));
EXPECT(!AK::StringUtils::matches(StringView(), "?"));
EXPECT(!AK::StringUtils::matches(StringView(), "a"));

EXPECT(!AK::StringUtils::matches("", StringView()));
EXPECT(!AK::StringUtils::matches("a", StringView()));
}

TEST_CASE(matches_empty)
{
EXPECT(AK::StringUtils::matches("", ""));

EXPECT(AK::StringUtils::matches("", "*"));
EXPECT(!AK::StringUtils::matches("", "?"));
EXPECT(!AK::StringUtils::matches("", "a"));

EXPECT(!AK::StringUtils::matches("a", ""));
}

TEST_CASE(matches_case_sensitive)
{
EXPECT(AK::StringUtils::matches("a", "a", CaseSensitivity::CaseSensitive));
EXPECT(!AK::StringUtils::matches("a", "A", CaseSensitivity::CaseSensitive));
EXPECT(!AK::StringUtils::matches("A", "a", CaseSensitivity::CaseSensitive));
}

TEST_CASE(matches_case_insensitive)
{
EXPECT(!AK::StringUtils::matches("aa", "a"));
EXPECT(AK::StringUtils::matches("aa", "*"));
EXPECT(!AK::StringUtils::matches("cb", "?a"));
EXPECT(AK::StringUtils::matches("adceb", "a*b"));
EXPECT(!AK::StringUtils::matches("acdcb", "a*c?b"));
}

TEST_MAIN(StringUtils)
1 change: 1 addition & 0 deletions DevTools/FormCompiler/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ OBJS = \
../../AK/StringImpl.o \
../../AK/StringBuilder.o \
../../AK/StringView.o \
../../AK/StringUtils.o \
../../AK/JsonValue.o \
../../AK/JsonParser.o \
../../AK/LogStream.o \
Expand Down
1 change: 1 addition & 0 deletions DevTools/IPCCompiler/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ OBJS = \
../../AK/StringImpl.o \
../../AK/StringBuilder.o \
../../AK/StringView.o \
../../AK/StringUtils.o \
../../AK/JsonValue.o \
../../AK/JsonParser.o \
../../AK/LogStream.o \
Expand Down
1 change: 1 addition & 0 deletions Kernel/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ OBJS = \
../AK/StringBuilder.o \
../AK/StringImpl.o \
../AK/StringView.o \
../AK/StringUtils.o \
../Libraries/LibELF/ELFImage.o \
../Libraries/LibELF/ELFLoader.o \
../Libraries/LibBareMetal/Output/Console.o \
Expand Down
1 change: 1 addition & 0 deletions Libraries/LibC/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ AK_OBJS = \
../../AK/String.o \
../../AK/StringView.o \
../../AK/StringBuilder.o \
../../AK/StringUtils.o \
../../AK/FileSystemPath.o \
../../AK/URL.o \
../../AK/JsonValue.o \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ OBJS = \
../../../../AK/StringImpl.o \
../../../../AK/StringBuilder.o \
../../../../AK/StringView.o \
../../../../AK/StringUtils.o \
../../../../AK/JsonValue.o \
../../../../AK/JsonParser.o \
../../../../AK/LogStream.o \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ PROGRAM = Generate_CSS_PropertyID_h

OBJS = \
Generate_CSS_PropertyID_h.o \
../../../../AK/StringUtils.o \
../../../../AK/String.o \
../../../../AK/StringImpl.o \
../../../../AK/StringBuilder.o \
Expand Down
2 changes: 1 addition & 1 deletion Shell/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,7 @@ static Vector<String> expand_globs(const StringView& path, const StringView& bas
if (name[0] == '.' && part[0] != '.')
continue;

if (name.matches(part, String::CaseSensitivity::CaseSensitive)) {
if (name.matches(part, CaseSensitivity::CaseSensitive)) {

StringBuilder nested_base;
nested_base.append(new_base);
Expand Down

0 comments on commit 055344f

Please sign in to comment.