diff --git a/libc/config/baremetal/entrypoints.txt b/libc/config/baremetal/entrypoints.txt index 6efa4484b8ab4..db645c06fe265 100644 --- a/libc/config/baremetal/entrypoints.txt +++ b/libc/config/baremetal/entrypoints.txt @@ -31,6 +31,7 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.string.memset libc.src.string.stpcpy libc.src.string.stpncpy + libc.src.string.strcasecmp libc.src.string.strcat libc.src.string.strchr libc.src.string.strcmp @@ -39,6 +40,7 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.string.strlcat libc.src.string.strlcpy libc.src.string.strlen + libc.src.string.strncasecmp libc.src.string.strncat libc.src.string.strncmp libc.src.string.strncpy diff --git a/libc/config/darwin/arm/entrypoints.txt b/libc/config/darwin/arm/entrypoints.txt index 4a9af696f728b..cf2931963cfa8 100644 --- a/libc/config/darwin/arm/entrypoints.txt +++ b/libc/config/darwin/arm/entrypoints.txt @@ -31,6 +31,7 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.string.memset libc.src.string.stpcpy libc.src.string.stpncpy + libc.src.string.strcasecmp libc.src.string.strcat libc.src.string.strchr libc.src.string.strcmp @@ -39,6 +40,7 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.string.strlcat libc.src.string.strlcpy libc.src.string.strlen + libc.src.string.strncasecmp libc.src.string.strncat libc.src.string.strncmp libc.src.string.strncpy diff --git a/libc/config/gpu/entrypoints.txt b/libc/config/gpu/entrypoints.txt index 8b058df246c6a..57b314f3d231f 100644 --- a/libc/config/gpu/entrypoints.txt +++ b/libc/config/gpu/entrypoints.txt @@ -30,6 +30,7 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.string.memset libc.src.string.stpcpy libc.src.string.stpncpy + libc.src.string.strcasecmp libc.src.string.strcat libc.src.string.strchr libc.src.string.strcmp @@ -38,6 +39,7 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.string.strlcat libc.src.string.strlcpy libc.src.string.strlen + libc.src.string.strncasecmp libc.src.string.strncat libc.src.string.strncmp libc.src.string.strncpy diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt index 14e0cc934bb26..7223975eb8355 100644 --- a/libc/config/linux/aarch64/entrypoints.txt +++ b/libc/config/linux/aarch64/entrypoints.txt @@ -40,6 +40,7 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.string.memset libc.src.string.stpcpy libc.src.string.stpncpy + libc.src.string.strcasecmp libc.src.string.strcat libc.src.string.strchr libc.src.string.strcmp @@ -51,6 +52,7 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.string.strlcat libc.src.string.strlcpy libc.src.string.strlen + libc.src.string.strncasecmp libc.src.string.strncat libc.src.string.strncmp libc.src.string.strncpy diff --git a/libc/config/linux/arm/entrypoints.txt b/libc/config/linux/arm/entrypoints.txt index 725365231af5c..f4228f1201460 100644 --- a/libc/config/linux/arm/entrypoints.txt +++ b/libc/config/linux/arm/entrypoints.txt @@ -31,6 +31,7 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.string.memset libc.src.string.stpcpy libc.src.string.stpncpy + libc.src.string.strcasecmp libc.src.string.strcat libc.src.string.strchr libc.src.string.strcmp @@ -39,6 +40,7 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.string.strlcat libc.src.string.strlcpy libc.src.string.strlen + libc.src.string.strncasecmp libc.src.string.strncat libc.src.string.strncmp libc.src.string.strncpy diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt index 138c574cf4890..2c1c938c5fff5 100644 --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -40,6 +40,7 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.string.memset libc.src.string.stpcpy libc.src.string.stpncpy + libc.src.string.strcasecmp libc.src.string.strcat libc.src.string.strchr libc.src.string.strcmp @@ -52,6 +53,7 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.string.strlcat libc.src.string.strlcpy libc.src.string.strlen + libc.src.string.strncasecmp libc.src.string.strncat libc.src.string.strncmp libc.src.string.strncpy diff --git a/libc/config/windows/entrypoints.txt b/libc/config/windows/entrypoints.txt index 61f28fae5bbc1..01c33f9d68d46 100644 --- a/libc/config/windows/entrypoints.txt +++ b/libc/config/windows/entrypoints.txt @@ -31,6 +31,7 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.string.memset libc.src.string.stpcpy libc.src.string.stpncpy + libc.src.string.strcasecmp libc.src.string.strcat libc.src.string.strchr libc.src.string.strcmp @@ -39,6 +40,7 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.string.strlcat libc.src.string.strlcpy libc.src.string.strlen + libc.src.string.strncasecmp libc.src.string.strncat libc.src.string.strncmp libc.src.string.strncpy diff --git a/libc/spec/bsd_ext.td b/libc/spec/bsd_ext.td index 5779ab79c4f1d..9ee07ce4e182f 100644 --- a/libc/spec/bsd_ext.td +++ b/libc/spec/bsd_ext.td @@ -18,6 +18,25 @@ def BsdExtensions : StandardSpec<"BSDExtensions"> { ] >; + HeaderSpec Strings = HeaderSpec< + "strings.h", + [], // Macros + [], // Types + [], // Enumerations + [ + FunctionSpec< + "strcasecmp", + RetValSpec, + [ArgSpec, ArgSpec] + >, + FunctionSpec< + "strncasecmp", + RetValSpec, + [ArgSpec, ArgSpec, ArgSpec] + >, + ] + >; + HeaderSpec SysWait = HeaderSpec< "sys/wait.h", [], // Macros @@ -34,6 +53,7 @@ def BsdExtensions : StandardSpec<"BSDExtensions"> { let Headers = [ String, + Strings, SysWait, ]; } diff --git a/libc/src/__support/ctype_utils.h b/libc/src/__support/ctype_utils.h index f3f4b366d91d9..f673981224439 100644 --- a/libc/src/__support/ctype_utils.h +++ b/libc/src/__support/ctype_utils.h @@ -36,6 +36,12 @@ static constexpr bool isspace(unsigned ch) { return ch == ' ' || (ch - '\t') < 5; } +static constexpr int tolower(int ch) { + if (isupper(ch)) + return ch + ('a' - 'A'); + return ch; +} + } // namespace internal } // namespace __llvm_libc diff --git a/libc/src/ctype/tolower.cpp b/libc/src/ctype/tolower.cpp index 8fbb5aaded48a..2c4b851a615e0 100644 --- a/libc/src/ctype/tolower.cpp +++ b/libc/src/ctype/tolower.cpp @@ -15,10 +15,6 @@ namespace __llvm_libc { // TODO: Currently restricted to default locale. // These should be extended using locale information. -LLVM_LIBC_FUNCTION(int, tolower, (int c)) { - if (internal::isupper(c)) - return c + ('a' - 'A'); - return c; -} +LLVM_LIBC_FUNCTION(int, tolower, (int c)) { return internal::tolower(c); } } // namespace __llvm_libc diff --git a/libc/src/string/CMakeLists.txt b/libc/src/string/CMakeLists.txt index cdce0bc69ec30..ec61735f41542 100644 --- a/libc/src/string/CMakeLists.txt +++ b/libc/src/string/CMakeLists.txt @@ -115,6 +115,17 @@ add_entrypoint_object( .memory_utils.strcmp_implementation ) +add_entrypoint_object( + strcasecmp + SRCS + strcasecmp.cpp + HDRS + strcasecmp.h + DEPENDS + .memory_utils.strcmp_implementation + libc.src.__support.ctype_utils +) + add_entrypoint_object( strcoll SRCS @@ -232,6 +243,17 @@ add_entrypoint_object( .memory_utils.strcmp_implementation ) +add_entrypoint_object( + strncasecmp + SRCS + strncasecmp.cpp + HDRS + strncasecmp.h + DEPENDS + .memory_utils.strcmp_implementation + libc.src.__support.ctype_utils +) + add_entrypoint_object( strncpy SRCS diff --git a/libc/src/string/strcasecmp.cpp b/libc/src/string/strcasecmp.cpp new file mode 100644 index 0000000000000..d7a080aec949e --- /dev/null +++ b/libc/src/string/strcasecmp.cpp @@ -0,0 +1,25 @@ +//===-- Implementation of strcasecmp --------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/string/strcasecmp.h" + +#include "src/__support/common.h" +#include "src/__support/ctype_utils.h" +#include "src/string/memory_utils/strcmp_implementations.h" + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(int, strcasecmp, (const char *left, const char *right)) { + auto case_cmp = [](char a, char b) { + return __llvm_libc::internal::tolower(a) - + __llvm_libc::internal::tolower(b); + }; + return strcmp_implementation(left, right, case_cmp); +} + +} // namespace __llvm_libc diff --git a/libc/src/string/strcasecmp.h b/libc/src/string/strcasecmp.h new file mode 100644 index 0000000000000..ce0a4e5b613cc --- /dev/null +++ b/libc/src/string/strcasecmp.h @@ -0,0 +1,18 @@ +//===-- Implementation header for strcasecmp --------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_STRING_STRCASECMP_H +#define LLVM_LIBC_SRC_STRING_STRCASECMP_H + +namespace __llvm_libc { + +int strcasecmp(const char *left, const char *right); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_STRING_STRCASECMP_H diff --git a/libc/src/string/strncasecmp.cpp b/libc/src/string/strncasecmp.cpp new file mode 100644 index 0000000000000..0fb2f81b17fe1 --- /dev/null +++ b/libc/src/string/strncasecmp.cpp @@ -0,0 +1,26 @@ +//===-- Implementation of strncasecmp -------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/string/strncasecmp.h" + +#include "src/__support/common.h" +#include "src/__support/ctype_utils.h" +#include "src/string/memory_utils/strcmp_implementations.h" + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(int, strncasecmp, + (const char *left, const char *right, size_t n)) { + auto case_cmp = [](char a, char b) { + return __llvm_libc::internal::tolower(a) - + __llvm_libc::internal::tolower(b); + }; + return strncmp_implementation(left, right, n, case_cmp); +} + +} // namespace __llvm_libc diff --git a/libc/src/string/strncasecmp.h b/libc/src/string/strncasecmp.h new file mode 100644 index 0000000000000..5ef2dcd4cd120 --- /dev/null +++ b/libc/src/string/strncasecmp.h @@ -0,0 +1,20 @@ +//===-- Implementation header for strcasecmp --------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_STRING_STRCASECMP_H +#define LLVM_LIBC_SRC_STRING_STRCASECMP_H + +#include + +namespace __llvm_libc { + +int strncasecmp(const char *left, const char *right, size_t n); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_STRING_STRCASECMP_H diff --git a/libc/test/src/string/CMakeLists.txt b/libc/test/src/string/CMakeLists.txt index b5de08b2fbb03..99dfabbd93a39 100644 --- a/libc/test/src/string/CMakeLists.txt +++ b/libc/test/src/string/CMakeLists.txt @@ -104,6 +104,16 @@ add_libc_unittest( libc.src.string.strcmp ) +add_libc_unittest( + strcasecmp_test + SUITE + libc_string_unittests + SRCS + strcasecmp_test.cpp + DEPENDS + libc.src.string.strcasecmp +) + add_libc_unittest( strcoll_test SUITE @@ -218,6 +228,16 @@ add_libc_unittest( libc.src.string.strncmp ) +add_libc_unittest( + strncasecmp_test + SUITE + libc_string_unittests + SRCS + strncasecmp_test.cpp + DEPENDS + libc.src.string.strncasecmp +) + add_libc_unittest( strncpy_test SUITE diff --git a/libc/test/src/string/strcasecmp_test.cpp b/libc/test/src/string/strcasecmp_test.cpp new file mode 100644 index 0000000000000..a8200104d22c2 --- /dev/null +++ b/libc/test/src/string/strcasecmp_test.cpp @@ -0,0 +1,46 @@ +//===-- Unittests for strcasecmp ------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/string/strcasecmp.h" +#include "utils/UnitTest/Test.h" + +TEST(LlvmLibcStrCaseCmpTest, EmptyStringsShouldReturnZero) { + const char *s1 = ""; + const char *s2 = ""; + int result = __llvm_libc::strcasecmp(s1, s2); + ASSERT_EQ(result, 0); + + // Verify operands reversed. + result = __llvm_libc::strcasecmp(s2, s1); + ASSERT_EQ(result, 0); +} + +TEST(LlvmLibcStrCaseCmpTest, EmptyStringShouldNotEqualNonEmptyString) { + const char *empty = ""; + const char *s2 = "abc"; + int result = __llvm_libc::strcasecmp(empty, s2); + // This should be '\0' - 'a' = -97 + ASSERT_LT(result, 0); + + // Similar case if empty string is second argument. + const char *s3 = "123"; + result = __llvm_libc::strcasecmp(s3, empty); + // This should be '1' - '\0' = 49 + ASSERT_GT(result, 0); +} + +TEST(LlvmLibcStrCaseCmpTest, Case) { + const char *s1 = "aB"; + const char *s2 = "ab"; + int result = __llvm_libc::strcasecmp(s1, s2); + ASSERT_EQ(result, 0); + + // Verify operands reversed. + result = __llvm_libc::strcasecmp(s2, s1); + ASSERT_EQ(result, 0); +} diff --git a/libc/test/src/string/strcmp_test.cpp b/libc/test/src/string/strcmp_test.cpp index f80cdfa3d44d6..9f707dbf1581b 100644 --- a/libc/test/src/string/strcmp_test.cpp +++ b/libc/test/src/string/strcmp_test.cpp @@ -95,3 +95,14 @@ TEST(LlvmLibcStrCmpTest, StringArgumentSwapChangesSign) { // 'a' - 'b' = -1. ASSERT_EQ(result, -1); } + +TEST(LlvmLibcStrCmpTest, Case) { + const char *s1 = "aB"; + const char *s2 = "ab"; + int result = __llvm_libc::strcmp(s1, s2); + ASSERT_LT(result, 0); + + // Verify operands reversed. + result = __llvm_libc::strcmp(s2, s1); + ASSERT_GT(result, 0); +} diff --git a/libc/test/src/string/strncasecmp_test.cpp b/libc/test/src/string/strncasecmp_test.cpp new file mode 100644 index 0000000000000..0188c7fca74e9 --- /dev/null +++ b/libc/test/src/string/strncasecmp_test.cpp @@ -0,0 +1,48 @@ +//===-- Unittests for strncasecmp -----------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/string/strncasecmp.h" +#include "utils/UnitTest/Test.h" + +TEST(LlvmLibcStrNCaseCmpTest, + EmptyStringsShouldReturnZeroWithSufficientLength) { + const char *s1 = ""; + const char *s2 = ""; + int result = __llvm_libc::strncasecmp(s1, s2, 1); + ASSERT_EQ(result, 0); + + // Verify operands reversed. + result = __llvm_libc::strncasecmp(s2, s1, 1); + ASSERT_EQ(result, 0); +} + +TEST(LlvmLibcStrNCaseCmpTest, + EmptyStringShouldNotEqualNonEmptyStringWithSufficientLength) { + const char *empty = ""; + const char *s2 = "abc"; + int result = __llvm_libc::strncasecmp(empty, s2, 3); + // This should be '\0' - 'a' = -97 + ASSERT_LT(result, 0); + + // Similar case if empty string is second argument. + const char *s3 = "123"; + result = __llvm_libc::strncasecmp(s3, empty, 3); + // This should be '1' - '\0' = 49 + ASSERT_GT(result, 0); +} + +TEST(LlvmLibcStrNCaseCmpTest, Case) { + const char *s1 = "aB"; + const char *s2 = "ab"; + int result = __llvm_libc::strncasecmp(s1, s2, 2); + ASSERT_EQ(result, 0); + + // Verify operands reversed. + result = __llvm_libc::strncasecmp(s2, s1, 2); + ASSERT_EQ(result, 0); +} diff --git a/libc/test/src/string/strncmp_test.cpp b/libc/test/src/string/strncmp_test.cpp index 8855e0f59db0a..52add6b516e8d 100644 --- a/libc/test/src/string/strncmp_test.cpp +++ b/libc/test/src/string/strncmp_test.cpp @@ -156,3 +156,14 @@ TEST(LlvmLibcStrNCmpTest, StringComparisonEndsOnNullByteEvenWithLongerLength) { result = __llvm_libc::strncmp(s2, s1, 7); ASSERT_EQ(result, 0); } + +TEST(LlvmLibcStrNCmpTest, Case) { + const char *s1 = "aB"; + const char *s2 = "ab"; + int result = __llvm_libc::strncmp(s1, s2, 2); + ASSERT_LT(result, 0); + + // Verify operands reversed. + result = __llvm_libc::strncmp(s2, s1, 2); + ASSERT_GT(result, 0); +}