瀏覽代碼

AK: Move the wildcard-matching implementation to StringUtils

Provide wrappers in the String and StringView classes, and add some tests.
howar6hill 5 年之前
父節點
當前提交
055344f346

+ 1 - 53
AK/String.cpp

@@ -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

+ 2 - 7
AK/String.h

@@ -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>
 
@@ -108,13 +109,8 @@ public:
     {
     }
 
-    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;
@@ -244,7 +240,6 @@ public:
     }
 
 private:
-    bool match_helper(const StringView& mask) const;
     RefPtr<StringImpl> m_impl;
 };
 

+ 64 - 0
AK/StringUtils.cpp

@@ -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 - 0
AK/StringUtils.h

@@ -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 - 0
AK/StringView.cpp

@@ -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);

+ 2 - 0
AK/StringView.h

@@ -28,6 +28,7 @@
 
 #include <AK/Forward.h>
 #include <AK/StdLibExtras.h>
+#include <AK/StringUtils.h>
 
 namespace AK {
 
@@ -65,6 +66,7 @@ public:
     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;

+ 1 - 1
AK/TestSuite.h

@@ -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;
         }
 

+ 1 - 0
AK/Tests/Makefile

@@ -3,6 +3,7 @@ SHARED_TEST_SOURCES = \
 	../StringImpl.cpp \
 	../StringBuilder.cpp \
 	../StringView.cpp \
+	../StringUtils.cpp \
 	../LogStream.cpp \
 	../JsonValue.cpp \
 	../JsonParser.cpp \

+ 44 - 0
AK/Tests/TestStringUtils.cpp

@@ -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 - 0
DevTools/FormCompiler/Makefile

@@ -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 \

+ 1 - 0
DevTools/IPCCompiler/Makefile

@@ -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 \

+ 1 - 0
Kernel/Makefile

@@ -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 \

+ 1 - 0
Libraries/LibC/Makefile

@@ -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 \

+ 1 - 0
Libraries/LibHTML/CodeGenerators/Generate_CSS_PropertyID_cpp/Makefile

@@ -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 \

+ 1 - 0
Libraries/LibHTML/CodeGenerators/Generate_CSS_PropertyID_h/Makefile

@@ -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 \

+ 1 - 1
Shell/main.cpp

@@ -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);