diff --git a/libc/config/baremetal/entrypoints.txt b/libc/config/baremetal/entrypoints.txt --- a/libc/config/baremetal/entrypoints.txt +++ b/libc/config/baremetal/entrypoints.txt @@ -31,6 +31,7 @@ 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 @@ 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 --- a/libc/config/darwin/arm/entrypoints.txt +++ b/libc/config/darwin/arm/entrypoints.txt @@ -31,6 +31,7 @@ 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 @@ 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 --- a/libc/config/gpu/entrypoints.txt +++ b/libc/config/gpu/entrypoints.txt @@ -30,6 +30,7 @@ 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 @@ 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 --- a/libc/config/linux/aarch64/entrypoints.txt +++ b/libc/config/linux/aarch64/entrypoints.txt @@ -40,6 +40,7 @@ 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 @@ 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 --- a/libc/config/linux/arm/entrypoints.txt +++ b/libc/config/linux/arm/entrypoints.txt @@ -31,6 +31,7 @@ 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 @@ 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 --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -40,6 +40,7 @@ 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 @@ 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 --- a/libc/config/windows/entrypoints.txt +++ b/libc/config/windows/entrypoints.txt @@ -31,6 +31,7 @@ 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 @@ 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 --- a/libc/spec/bsd_ext.td +++ b/libc/spec/bsd_ext.td @@ -18,6 +18,25 @@ ] >; + 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 @@ let Headers = [ String, + Strings, SysWait, ]; } diff --git a/libc/src/__support/ctype_utils.h b/libc/src/__support/ctype_utils.h --- a/libc/src/__support/ctype_utils.h +++ b/libc/src/__support/ctype_utils.h @@ -36,6 +36,12 @@ 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 --- a/libc/src/ctype/tolower.cpp +++ b/libc/src/ctype/tolower.cpp @@ -15,10 +15,6 @@ // 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 --- a/libc/src/string/CMakeLists.txt +++ b/libc/src/string/CMakeLists.txt @@ -115,6 +115,17 @@ .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 @@ .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.h b/libc/src/string/strcasecmp.h new file mode 100644 --- /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/ctype/tolower.cpp b/libc/src/string/strcasecmp.cpp copy from libc/src/ctype/tolower.cpp copy to libc/src/string/strcasecmp.cpp --- a/libc/src/ctype/tolower.cpp +++ b/libc/src/string/strcasecmp.cpp @@ -1,4 +1,4 @@ -//===-- Implementation of tolower------------------------------------------===// +//===-- 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. @@ -6,19 +6,20 @@ // //===----------------------------------------------------------------------===// -#include "src/ctype/tolower.h" -#include "src/__support/ctype_utils.h" +#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 { -// 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, 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/strncasecmp.h b/libc/src/string/strncasecmp.h new file mode 100644 --- /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/src/string/strncasecmp.cpp b/libc/src/string/strncasecmp.cpp new file mode 100644 --- /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/test/src/string/CMakeLists.txt b/libc/test/src/string/CMakeLists.txt --- a/libc/test/src/string/CMakeLists.txt +++ b/libc/test/src/string/CMakeLists.txt @@ -104,6 +104,16 @@ 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 @@ 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 --- /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 --- a/libc/test/src/string/strcmp_test.cpp +++ b/libc/test/src/string/strcmp_test.cpp @@ -95,3 +95,14 @@ // '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 --- /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 --- a/libc/test/src/string/strncmp_test.cpp +++ b/libc/test/src/string/strncmp_test.cpp @@ -156,3 +156,14 @@ 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); +}