[libomp] Add kmp_str_ref (ADT 1/2) (#176162)
libomp currently has two limitations: 1) although it's C++, it doesn't link against the C++ stdlib 2) it cannot link against the implementation of LLVM ADTs These limitations shall not be altered at the moment. As a result, this commit introduces kmp_str_ref, which is similar to LLVM's StringRef. It currently only includes methods I need at the moment, but it's easily extensible.
This commit is contained in:
parent
5fbc81a2f2
commit
2d8a394129
9 changed files with 978 additions and 3 deletions
|
|
@ -81,6 +81,7 @@ if(STUBS_LIBRARY)
|
|||
else()
|
||||
# Get C++ files
|
||||
set(LIBOMP_CXXFILES
|
||||
kmp_adt.cpp
|
||||
kmp_alloc.cpp
|
||||
kmp_atomic.cpp
|
||||
kmp_csupport.cpp
|
||||
|
|
|
|||
63
openmp/runtime/src/kmp_adt.cpp
Normal file
63
openmp/runtime/src/kmp_adt.cpp
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* kmp_adt.cpp -- Advanced Data Types used internally
|
||||
*
|
||||
* FIXME: This is in intermediate solution until we agree and implement some
|
||||
* common resource according to
|
||||
* https://discourse.llvm.org/t/meta-rfc-adts-without-c-runtime-dependency/90317.
|
||||
* As soon as we will have this common resource that can be used for runtimes
|
||||
* such as openmp that want to avoid the link dependency to the C++ STL, this
|
||||
* shall be refactored.
|
||||
*/
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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 "kmp_adt.h"
|
||||
#include "kmp.h"
|
||||
#include "kmp_str.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
|
||||
bool kmp_str_ref::consume_integer(int &value, bool allow_zero,
|
||||
bool allow_negative) {
|
||||
kmp_str_ref orig = *this; // save state
|
||||
bool is_negative = consume_front("-");
|
||||
if (is_negative && !allow_negative) {
|
||||
*this = orig;
|
||||
return false;
|
||||
}
|
||||
size_t num_digits = count_while(
|
||||
[](char c) { return isdigit(static_cast<unsigned char>(c)) != 0; });
|
||||
if (!num_digits) {
|
||||
*this = orig;
|
||||
return false;
|
||||
}
|
||||
value = __kmp_basic_str_to_int(data, num_digits);
|
||||
if (value == INT_MAX) {
|
||||
*this = orig;
|
||||
return false;
|
||||
}
|
||||
drop_front(num_digits);
|
||||
if (is_negative)
|
||||
value = -value;
|
||||
if (!allow_zero && value == 0) {
|
||||
*this = orig;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
char *kmp_str_ref::copy() const {
|
||||
char *copy_str = static_cast<char *>(KMP_INTERNAL_MALLOC(len + 1));
|
||||
if (!copy_str)
|
||||
KMP_FATAL(MemoryAllocFailed);
|
||||
memcpy(copy_str, data, len);
|
||||
copy_str[len] = '\0';
|
||||
return copy_str;
|
||||
}
|
||||
142
openmp/runtime/src/kmp_adt.h
Normal file
142
openmp/runtime/src/kmp_adt.h
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* kmp_adt.h -- Advanced Data Types used internally
|
||||
*
|
||||
* FIXME: This is in intermediate solution until we agree and implement some
|
||||
* common resource according to
|
||||
* https://discourse.llvm.org/t/meta-rfc-adts-without-c-runtime-dependency/90317.
|
||||
* As soon as we will have this common resource that can be used for runtimes
|
||||
* such as openmp that want to avoid the link dependency to the C++ STL, this
|
||||
* shall be refactored.
|
||||
*/
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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 KMP_ADT_H
|
||||
#define KMP_ADT_H
|
||||
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
|
||||
/// kmp_str_ref is a non-owning string class (similar to llvm::StringRef).
|
||||
class kmp_str_ref final {
|
||||
const char *data;
|
||||
size_t len;
|
||||
|
||||
public:
|
||||
static constexpr size_t npos = SIZE_MAX;
|
||||
|
||||
kmp_str_ref(const char *str) : data(str), len(str ? strlen(str) : 0) {}
|
||||
kmp_str_ref(const char *str, size_t len) : data(str), len(len) {
|
||||
assert((data || !len) && "len must be 0 for nullptr data");
|
||||
}
|
||||
|
||||
kmp_str_ref(const kmp_str_ref &other) = default;
|
||||
kmp_str_ref &operator=(const kmp_str_ref &other) = default;
|
||||
|
||||
/// Check if the string starts with the given prefix and remove it from the
|
||||
/// string afterwards.
|
||||
bool consume_front(kmp_str_ref prefix) {
|
||||
if (len < prefix.len)
|
||||
return false;
|
||||
if (empty() || prefix.empty()) // avoid calling memcmp on potential nullptr
|
||||
return true;
|
||||
if (memcmp(data, prefix.data, prefix.len) != 0)
|
||||
return false;
|
||||
drop_front(prefix.len);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Start consuming an integer from the start of the string and remove it from
|
||||
/// the string afterwards.
|
||||
/// The maximum integer value that can currently be parsed is INT_MAX - 1.
|
||||
bool consume_integer(int &value, bool allow_zero = true,
|
||||
bool allow_negative = false);
|
||||
|
||||
/// Get an own duplicate of the string.
|
||||
/// Must be freed with KMP_INTERNAL_FREE().
|
||||
char *copy() const;
|
||||
|
||||
/// Count the number of characters in the string while the predicate returns
|
||||
/// true.
|
||||
template <typename Fn> size_t count_while(const Fn &predicate) const {
|
||||
static_assert(std::is_invocable_r_v<bool, Fn, char>,
|
||||
"predicate must be callable as bool(char)");
|
||||
size_t n = find_if_not(predicate);
|
||||
return n == npos ? len : n;
|
||||
}
|
||||
|
||||
/// Drop the first n characters from the string.
|
||||
/// (Limit n to the length of the string.)
|
||||
void drop_front(size_t n) {
|
||||
if (n > len)
|
||||
n = len;
|
||||
data += n;
|
||||
len -= n;
|
||||
}
|
||||
|
||||
/// Drop characters from the string while the predicate returns true.
|
||||
template <typename Fn> void drop_while(const Fn &predicate) {
|
||||
static_assert(std::is_invocable_r_v<bool, Fn, char>,
|
||||
"predicate must be callable as bool(char)");
|
||||
drop_front(count_while(predicate));
|
||||
}
|
||||
|
||||
/// Check if the string is empty.
|
||||
bool empty() const { return len == 0; }
|
||||
|
||||
/// Return the index of the first character in the string for which the
|
||||
/// predicate returns true.
|
||||
/// Returns npos if no match is found.
|
||||
template <typename Fn> size_t find_if(const Fn &predicate) const {
|
||||
static_assert(std::is_invocable_r_v<bool, Fn, char>,
|
||||
"predicate must be callable as bool(char)");
|
||||
size_t i = 0;
|
||||
while (i < len && !predicate(data[i]))
|
||||
++i;
|
||||
return i < len ? i : npos;
|
||||
}
|
||||
|
||||
/// Return the index of the first character in the string for which the
|
||||
/// predicate returns false.
|
||||
/// Returns npos if no match is found.
|
||||
template <typename Fn> size_t find_if_not(const Fn &predicate) const {
|
||||
static_assert(std::is_invocable_r_v<bool, Fn, char>,
|
||||
"predicate must be callable as bool(char)");
|
||||
return find_if([predicate](char c) { return !predicate(c); });
|
||||
}
|
||||
|
||||
/// Get the length of the string.
|
||||
size_t length() const { return len; }
|
||||
size_t size() const { return length(); }
|
||||
|
||||
/// Drop space from the start of the string.
|
||||
void skip_space() {
|
||||
drop_while([](char c) {
|
||||
return static_cast<bool>(isspace(static_cast<unsigned char>(c)));
|
||||
});
|
||||
}
|
||||
|
||||
/// Construct a new string with the longest prefix of the original string that
|
||||
/// satisfies the predicate. Doesn't modify the original string.
|
||||
template <typename Fn> kmp_str_ref take_while(const Fn &predicate) const {
|
||||
static_assert(std::is_invocable_r_v<bool, Fn, char>,
|
||||
"predicate must be callable as bool(char)");
|
||||
return kmp_str_ref(data, count_while(predicate));
|
||||
}
|
||||
|
||||
/// Iterator support (raw pointers work as iterators for contiguous storage)
|
||||
const char *begin() const { return data; }
|
||||
const char *end() const { return data + len; }
|
||||
};
|
||||
|
||||
#endif // KMP_ADT_H
|
||||
|
|
@ -619,13 +619,13 @@ char *__kmp_str_token(
|
|||
return token;
|
||||
} // __kmp_str_token
|
||||
|
||||
int __kmp_basic_str_to_int(char const *str) {
|
||||
int __kmp_basic_str_to_int(char const *str, size_t maxlen) {
|
||||
int result;
|
||||
char const *t;
|
||||
|
||||
result = 0;
|
||||
|
||||
for (t = str; *t != '\0'; ++t) {
|
||||
for (t = str; *t != '\0' && maxlen > 0; ++t, --maxlen) {
|
||||
if (*t < '0' || *t > '9')
|
||||
break;
|
||||
// Cap parsing to create largest integer
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#ifndef KMP_STR_H
|
||||
#define KMP_STR_H
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
|
||||
|
|
@ -112,7 +113,7 @@ int __kmp_str_match_true(char const *data);
|
|||
void __kmp_str_replace(char *str, char search_for, char replace_with);
|
||||
void __kmp_str_split(char *str, char delim, char **head, char **tail);
|
||||
char *__kmp_str_token(char *str, char const *delim, char **buf);
|
||||
int __kmp_basic_str_to_int(char const *str);
|
||||
int __kmp_basic_str_to_int(char const *str, size_t maxlen = SIZE_MAX);
|
||||
int __kmp_str_to_int(char const *str, char sentinel);
|
||||
|
||||
void __kmp_str_to_size(char const *str, size_t *out, size_t dfactor,
|
||||
|
|
|
|||
4
openmp/runtime/unittests/ADT/CMakeLists.txt
Normal file
4
openmp/runtime/unittests/ADT/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
add_openmp_unittest(ADTTests
|
||||
TestStringRef.cpp
|
||||
)
|
||||
|
||||
736
openmp/runtime/unittests/ADT/TestStringRef.cpp
Normal file
736
openmp/runtime/unittests/ADT/TestStringRef.cpp
Normal file
|
|
@ -0,0 +1,736 @@
|
|||
//===- TestStringRef.cpp - Tests for kmp_str_ref class -------------------===//
|
||||
//
|
||||
// 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 "kmp_adt.h"
|
||||
#include "kmp.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
|
||||
// Helper to compare kmp_str_ref content with a C string
|
||||
static bool equals(const kmp_str_ref &s, const char *expected) {
|
||||
size_t expected_len = strlen(expected);
|
||||
if (s.length() != expected_len)
|
||||
return false;
|
||||
return memcmp(s.begin(), expected, expected_len) == 0;
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Construction and Basic Properties
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
TEST(kmp_str_ref_test, ConstructFromCString) {
|
||||
kmp_str_ref s("Hello");
|
||||
EXPECT_EQ(s.length(), 5u);
|
||||
EXPECT_TRUE(equals(s, "Hello"));
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, ConstructFromCStringWithLength) {
|
||||
kmp_str_ref s("Hello World", 5);
|
||||
EXPECT_EQ(s.length(), 5u);
|
||||
EXPECT_TRUE(equals(s, "Hello"));
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, ConstructEmpty) {
|
||||
kmp_str_ref s("");
|
||||
EXPECT_EQ(s.length(), 0u);
|
||||
EXPECT_TRUE(s.empty());
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, ConstructFromNullptr) {
|
||||
const char *null_str = nullptr;
|
||||
kmp_str_ref s(null_str);
|
||||
EXPECT_EQ(s.length(), 0u);
|
||||
EXPECT_TRUE(s.empty());
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, ConstructFromNullptrWithLength) {
|
||||
kmp_str_ref s(nullptr, 0);
|
||||
EXPECT_EQ(s.length(), 0u);
|
||||
EXPECT_TRUE(s.empty());
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, NullptrOperations) {
|
||||
const char *null_str = nullptr;
|
||||
kmp_str_ref s(null_str);
|
||||
|
||||
// Iterators on a null-backed string must be consistent and yield no
|
||||
// elements.
|
||||
EXPECT_EQ(s.begin(), s.end());
|
||||
EXPECT_EQ(s.end() - s.begin(), 0);
|
||||
|
||||
std::string result;
|
||||
for (char c : s)
|
||||
result += c;
|
||||
EXPECT_TRUE(result.empty());
|
||||
|
||||
// Consuming the empty prefix must succeed and leave the string empty.
|
||||
EXPECT_TRUE(s.consume_front(""));
|
||||
EXPECT_TRUE(s.empty());
|
||||
|
||||
// Consuming a non-empty prefix must fail without dereferencing data.
|
||||
EXPECT_FALSE(s.consume_front("x"));
|
||||
|
||||
// count_while must not dereference data when the string is empty.
|
||||
EXPECT_EQ(s.count_while([](char) { return true; }), 0u);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, Length) {
|
||||
EXPECT_EQ(kmp_str_ref("").length(), 0u);
|
||||
EXPECT_EQ(kmp_str_ref("a").length(), 1u);
|
||||
EXPECT_EQ(kmp_str_ref("hello").length(), 5u);
|
||||
EXPECT_EQ(kmp_str_ref("hello world").length(), 11u);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, Size) {
|
||||
EXPECT_EQ(kmp_str_ref("").size(), 0u);
|
||||
EXPECT_EQ(kmp_str_ref("a").size(), 1u);
|
||||
EXPECT_EQ(kmp_str_ref("hello").size(), 5u);
|
||||
EXPECT_EQ(kmp_str_ref("hello world").size(), 11u);
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// empty
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
TEST(kmp_str_ref_test, EmptyString) {
|
||||
kmp_str_ref s("");
|
||||
EXPECT_TRUE(s.empty());
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, NonEmptyString) {
|
||||
kmp_str_ref s("hello");
|
||||
EXPECT_FALSE(s.empty());
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, EmptyAfterConsumeFront) {
|
||||
kmp_str_ref s("hello");
|
||||
EXPECT_FALSE(s.empty());
|
||||
|
||||
s.consume_front("hello");
|
||||
|
||||
EXPECT_TRUE(s.empty());
|
||||
EXPECT_EQ(s.length(), 0u);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, EmptyAfterDropFront) {
|
||||
kmp_str_ref s("abc");
|
||||
EXPECT_FALSE(s.empty());
|
||||
|
||||
s.drop_front(3);
|
||||
|
||||
EXPECT_TRUE(s.empty());
|
||||
EXPECT_EQ(s.length(), 0u);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, EmptyAfterDropWhile) {
|
||||
kmp_str_ref s("12345");
|
||||
EXPECT_FALSE(s.empty());
|
||||
|
||||
s.drop_while([](char c) {
|
||||
return static_cast<bool>(isdigit(static_cast<unsigned char>(c)));
|
||||
});
|
||||
|
||||
EXPECT_TRUE(s.empty());
|
||||
EXPECT_EQ(s.length(), 0u);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, EmptyAfterConsumeInteger) {
|
||||
kmp_str_ref s("42");
|
||||
int value = 0;
|
||||
EXPECT_FALSE(s.empty());
|
||||
|
||||
s.consume_integer(value);
|
||||
|
||||
EXPECT_TRUE(s.empty());
|
||||
EXPECT_EQ(s.length(), 0u);
|
||||
EXPECT_EQ(value, 42);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, NotEmptyAfterPartialConsume) {
|
||||
kmp_str_ref s("123abc");
|
||||
int value = 0;
|
||||
|
||||
s.consume_integer(value);
|
||||
|
||||
EXPECT_FALSE(s.empty());
|
||||
EXPECT_EQ(s.length(), 3u);
|
||||
EXPECT_TRUE(equals(s, "abc"));
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Iterators
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
TEST(kmp_str_ref_test, BeginEnd) {
|
||||
kmp_str_ref s("Hello");
|
||||
EXPECT_EQ(s.end() - s.begin(), 5);
|
||||
EXPECT_EQ(*s.begin(), 'H');
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, RangeBasedFor) {
|
||||
kmp_str_ref s("abc");
|
||||
std::string result;
|
||||
for (char c : s) {
|
||||
result += c;
|
||||
}
|
||||
EXPECT_EQ(result, "abc");
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Assignment
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
TEST(kmp_str_ref_test, Assignment) {
|
||||
kmp_str_ref s1("First");
|
||||
kmp_str_ref s2("Second");
|
||||
|
||||
s1 = s2;
|
||||
|
||||
EXPECT_TRUE(equals(s1, "Second"));
|
||||
EXPECT_EQ(s1.length(), 6u);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, SelfAssignment) {
|
||||
kmp_str_ref s("Test");
|
||||
kmp_str_ref &s_ref = s;
|
||||
s = s_ref; // Avoid self-assignment warning
|
||||
EXPECT_TRUE(equals(s, "Test"));
|
||||
EXPECT_EQ(s.length(), 4u);
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// consume_front
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
TEST(kmp_str_ref_test, ConsumeFrontSuccess) {
|
||||
kmp_str_ref s("Hello World");
|
||||
|
||||
EXPECT_TRUE(s.consume_front("Hello"));
|
||||
EXPECT_EQ(s.length(), 6u);
|
||||
EXPECT_TRUE(equals(s, " World"));
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, ConsumeFrontFailure) {
|
||||
kmp_str_ref s("Hello World");
|
||||
|
||||
EXPECT_FALSE(s.consume_front("World"));
|
||||
EXPECT_EQ(s.length(), 11u);
|
||||
EXPECT_TRUE(equals(s, "Hello World"));
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, ConsumeFrontEmpty) {
|
||||
kmp_str_ref s("Hello");
|
||||
|
||||
EXPECT_TRUE(s.consume_front(""));
|
||||
EXPECT_EQ(s.length(), 5u);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, ConsumeFrontTooLong) {
|
||||
kmp_str_ref s("Hi");
|
||||
|
||||
EXPECT_FALSE(s.consume_front("Hello"));
|
||||
EXPECT_EQ(s.length(), 2u);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, ConsumeFrontExact) {
|
||||
kmp_str_ref s("Hello");
|
||||
|
||||
EXPECT_TRUE(s.consume_front("Hello"));
|
||||
EXPECT_EQ(s.length(), 0u);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, ConsumeFrontMultiple) {
|
||||
kmp_str_ref s("prefix:middle:suffix");
|
||||
|
||||
EXPECT_TRUE(s.consume_front("prefix"));
|
||||
EXPECT_TRUE(s.consume_front(":"));
|
||||
EXPECT_TRUE(s.consume_front("middle"));
|
||||
EXPECT_TRUE(s.consume_front(":"));
|
||||
EXPECT_TRUE(equals(s, "suffix"));
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// consume_integer
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
TEST(kmp_str_ref_test, ConsumeIntegerSimple) {
|
||||
kmp_str_ref s("42");
|
||||
int value = 0;
|
||||
|
||||
EXPECT_TRUE(s.consume_integer(value));
|
||||
EXPECT_EQ(value, 42);
|
||||
EXPECT_EQ(s.length(), 0u);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, ConsumeIntegerWithTrailing) {
|
||||
kmp_str_ref s("123abc");
|
||||
int value = 0;
|
||||
|
||||
EXPECT_TRUE(s.consume_integer(value));
|
||||
EXPECT_EQ(value, 123);
|
||||
EXPECT_TRUE(equals(s, "abc"));
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, ConsumeIntegerZero) {
|
||||
kmp_str_ref s("0");
|
||||
int value = -1;
|
||||
|
||||
// allow_zero = true by default
|
||||
EXPECT_TRUE(s.consume_integer(value));
|
||||
EXPECT_EQ(value, 0);
|
||||
EXPECT_EQ(s.length(), 0u);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, ConsumeIntegerZeroNotAllowed) {
|
||||
kmp_str_ref s("0rest");
|
||||
int value = -1;
|
||||
|
||||
EXPECT_FALSE(s.consume_integer(value, /*allow_zero=*/false));
|
||||
// State should be restored on failure
|
||||
EXPECT_TRUE(equals(s, "0rest"));
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, ConsumeIntegerNoDigits) {
|
||||
kmp_str_ref s("abc");
|
||||
int value = -1;
|
||||
|
||||
// No digits to consume, should fail
|
||||
EXPECT_FALSE(s.consume_integer(value));
|
||||
// String should be unchanged
|
||||
EXPECT_TRUE(equals(s, "abc"));
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, ConsumeIntegerEmpty) {
|
||||
kmp_str_ref s("");
|
||||
int value = -1;
|
||||
|
||||
// Empty string has no digits, should fail
|
||||
EXPECT_FALSE(s.consume_integer(value));
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, ConsumeIntegerLeadingZero) {
|
||||
kmp_str_ref s("007");
|
||||
int value = -1;
|
||||
|
||||
EXPECT_TRUE(s.consume_integer(value));
|
||||
EXPECT_EQ(value, 7);
|
||||
EXPECT_EQ(s.length(), 0u);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, ConsumeIntegerNegativeAllowed) {
|
||||
kmp_str_ref s("-42rest");
|
||||
int value = 0;
|
||||
|
||||
EXPECT_TRUE(s.consume_integer(value, true, true));
|
||||
EXPECT_EQ(value, -42);
|
||||
EXPECT_TRUE(equals(s, "rest"));
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, ConsumeIntegerNegativeNotAllowed) {
|
||||
kmp_str_ref s("-42");
|
||||
int value = 0;
|
||||
|
||||
EXPECT_FALSE(s.consume_integer(value, true, false));
|
||||
// State should be restored on failure
|
||||
EXPECT_TRUE(equals(s, "-42"));
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, ConsumeIntegerMultipleDigits) {
|
||||
kmp_str_ref s("1234567890");
|
||||
int value = 0;
|
||||
|
||||
EXPECT_TRUE(s.consume_integer(value));
|
||||
EXPECT_EQ(value, 1234567890);
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// copy
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
TEST(kmp_str_ref_test, Copy) {
|
||||
kmp_str_ref s("Hello");
|
||||
char *copied = s.copy();
|
||||
|
||||
EXPECT_NE(copied, nullptr);
|
||||
EXPECT_STREQ(copied, "Hello");
|
||||
EXPECT_NE(copied, s.begin()); // Different pointer
|
||||
|
||||
KMP_INTERNAL_FREE(copied);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, CopyEmpty) {
|
||||
kmp_str_ref s("");
|
||||
char *copied = s.copy();
|
||||
|
||||
EXPECT_NE(copied, nullptr);
|
||||
EXPECT_STREQ(copied, "");
|
||||
|
||||
KMP_INTERNAL_FREE(copied);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, CopySubstring) {
|
||||
// Test copying a substring that doesn't have a null terminator at len
|
||||
kmp_str_ref full("device-0)rest");
|
||||
kmp_str_ref sub = full.take_while([](char c) { return c != ')'; });
|
||||
|
||||
EXPECT_EQ(sub.length(), 8u); // "device-0"
|
||||
|
||||
char *copied = sub.copy();
|
||||
|
||||
EXPECT_NE(copied, nullptr);
|
||||
EXPECT_STREQ(copied, "device-0"); // Should NOT include ")"
|
||||
EXPECT_EQ(strlen(copied), 8u);
|
||||
|
||||
KMP_INTERNAL_FREE(copied);
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// drop_front
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
TEST(kmp_str_ref_test, DropFront) {
|
||||
kmp_str_ref s("Hello World");
|
||||
|
||||
s.drop_front(6);
|
||||
|
||||
EXPECT_EQ(s.length(), 5u);
|
||||
EXPECT_TRUE(equals(s, "World"));
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, DropFrontZero) {
|
||||
kmp_str_ref s("Hello");
|
||||
|
||||
s.drop_front(0);
|
||||
|
||||
EXPECT_EQ(s.length(), 5u);
|
||||
EXPECT_TRUE(equals(s, "Hello"));
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, DropFrontAll) {
|
||||
kmp_str_ref s("Hello");
|
||||
|
||||
s.drop_front(5);
|
||||
|
||||
EXPECT_EQ(s.length(), 0u);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, DropFrontMoreThanLength) {
|
||||
kmp_str_ref s("Hi");
|
||||
|
||||
s.drop_front(100);
|
||||
|
||||
EXPECT_EQ(s.length(), 0u);
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// drop_while
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
TEST(kmp_str_ref_test, DropWhileDigits) {
|
||||
kmp_str_ref s("123abc");
|
||||
|
||||
s.drop_while([](char c) {
|
||||
return static_cast<bool>(isdigit(static_cast<unsigned char>(c)));
|
||||
});
|
||||
|
||||
EXPECT_TRUE(equals(s, "abc"));
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, DropWhileSpaces) {
|
||||
kmp_str_ref s(" hello");
|
||||
|
||||
s.drop_while([](char c) { return c == ' '; });
|
||||
|
||||
EXPECT_TRUE(equals(s, "hello"));
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, DropWhileNone) {
|
||||
kmp_str_ref s("hello");
|
||||
|
||||
s.drop_while([](char c) { return c == ' '; });
|
||||
|
||||
EXPECT_TRUE(equals(s, "hello"));
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, DropWhileAll) {
|
||||
kmp_str_ref s("12345");
|
||||
|
||||
s.drop_while([](char c) {
|
||||
return static_cast<bool>(isdigit(static_cast<unsigned char>(c)));
|
||||
});
|
||||
|
||||
EXPECT_EQ(s.length(), 0u);
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// count_while
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
TEST(kmp_str_ref_test, CountWhileDigits) {
|
||||
kmp_str_ref s("123abc");
|
||||
|
||||
size_t n = s.count_while(
|
||||
[](char c) { return isdigit(static_cast<unsigned char>(c)) != 0; });
|
||||
|
||||
EXPECT_EQ(n, 3u);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, CountWhileAll) {
|
||||
kmp_str_ref s("12345");
|
||||
|
||||
size_t n = s.count_while(
|
||||
[](char c) { return isdigit(static_cast<unsigned char>(c)) != 0; });
|
||||
|
||||
EXPECT_EQ(n, s.length());
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, CountWhileNone) {
|
||||
kmp_str_ref s("abc");
|
||||
|
||||
size_t n = s.count_while(
|
||||
[](char c) { return isdigit(static_cast<unsigned char>(c)) != 0; });
|
||||
|
||||
EXPECT_EQ(n, 0u);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, CountWhileEmpty) {
|
||||
kmp_str_ref s("");
|
||||
|
||||
size_t n = s.count_while([](char c) { return c == 'a'; });
|
||||
|
||||
EXPECT_EQ(n, 0u);
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// find_if
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
TEST(kmp_str_ref_test, FindIfDigit) {
|
||||
kmp_str_ref s("abc123");
|
||||
|
||||
size_t i = s.find_if(
|
||||
[](char c) { return isdigit(static_cast<unsigned char>(c)) != 0; });
|
||||
|
||||
EXPECT_EQ(i, 3u);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, FindIfFirstChar) {
|
||||
kmp_str_ref s("hello");
|
||||
|
||||
size_t i = s.find_if([](char c) { return c == 'h'; });
|
||||
|
||||
EXPECT_EQ(i, 0u);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, FindIfLastChar) {
|
||||
kmp_str_ref s("hello");
|
||||
|
||||
size_t i = s.find_if([](char c) { return c == 'o'; });
|
||||
|
||||
EXPECT_EQ(i, 4u);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, FindIfNoMatch) {
|
||||
kmp_str_ref s("hello");
|
||||
|
||||
size_t i = s.find_if(
|
||||
[](char c) { return isdigit(static_cast<unsigned char>(c)) != 0; });
|
||||
|
||||
EXPECT_EQ(i, kmp_str_ref::npos);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, FindIfEmpty) {
|
||||
kmp_str_ref s("");
|
||||
|
||||
size_t i = s.find_if([](char c) { return c == 'a'; });
|
||||
|
||||
EXPECT_EQ(i, kmp_str_ref::npos);
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// find_if_not
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
TEST(kmp_str_ref_test, FindIfNotDigit) {
|
||||
kmp_str_ref s("123abc");
|
||||
|
||||
size_t i = s.find_if_not(
|
||||
[](char c) { return isdigit(static_cast<unsigned char>(c)) != 0; });
|
||||
|
||||
EXPECT_EQ(i, 3u);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, FindIfNotAllMatch) {
|
||||
kmp_str_ref s("12345");
|
||||
|
||||
size_t i = s.find_if_not(
|
||||
[](char c) { return isdigit(static_cast<unsigned char>(c)) != 0; });
|
||||
|
||||
EXPECT_EQ(i, kmp_str_ref::npos);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, FindIfNotNoneMatch) {
|
||||
kmp_str_ref s("abc");
|
||||
|
||||
size_t i = s.find_if_not(
|
||||
[](char c) { return isdigit(static_cast<unsigned char>(c)) != 0; });
|
||||
|
||||
EXPECT_EQ(i, 0u);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, FindIfNotEmpty) {
|
||||
kmp_str_ref s("");
|
||||
|
||||
size_t i = s.find_if_not([](char c) { return c == 'a'; });
|
||||
|
||||
EXPECT_EQ(i, kmp_str_ref::npos);
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// skip_space
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
TEST(kmp_str_ref_test, SkipSpace) {
|
||||
kmp_str_ref s(" hello");
|
||||
|
||||
s.skip_space();
|
||||
|
||||
EXPECT_TRUE(equals(s, "hello"));
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, SkipSpaceNoSpaces) {
|
||||
kmp_str_ref s("hello");
|
||||
|
||||
s.skip_space();
|
||||
|
||||
EXPECT_TRUE(equals(s, "hello"));
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, SkipSpaceAllSpaces) {
|
||||
kmp_str_ref s(" ");
|
||||
|
||||
s.skip_space();
|
||||
|
||||
EXPECT_EQ(s.length(), 0u);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, SkipSpaceOnlyLeading) {
|
||||
kmp_str_ref s(" hello world ");
|
||||
|
||||
s.skip_space();
|
||||
|
||||
EXPECT_TRUE(equals(s, "hello world "));
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, SkipSpaceWithTabs) {
|
||||
kmp_str_ref s("\t\n hello");
|
||||
|
||||
s.skip_space();
|
||||
|
||||
EXPECT_TRUE(equals(s, "hello"));
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// take_while
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
TEST(kmp_str_ref_test, TakeWhileDigits) {
|
||||
kmp_str_ref s("123abc");
|
||||
|
||||
kmp_str_ref digits = s.take_while([](char c) {
|
||||
return static_cast<bool>(isdigit(static_cast<unsigned char>(c)));
|
||||
});
|
||||
|
||||
EXPECT_EQ(digits.length(), 3u);
|
||||
EXPECT_TRUE(equals(digits, "123"));
|
||||
// Original unchanged
|
||||
EXPECT_EQ(s.length(), 6u);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, TakeWhileAlpha) {
|
||||
kmp_str_ref s("hello123");
|
||||
|
||||
kmp_str_ref alpha = s.take_while([](char c) {
|
||||
return static_cast<bool>(isalpha(static_cast<unsigned char>(c)));
|
||||
});
|
||||
|
||||
EXPECT_EQ(alpha.length(), 5u);
|
||||
EXPECT_TRUE(equals(alpha, "hello"));
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, TakeWhileNone) {
|
||||
kmp_str_ref s("123abc");
|
||||
|
||||
kmp_str_ref result = s.take_while([](char c) {
|
||||
return static_cast<bool>(isalpha(static_cast<unsigned char>(c)));
|
||||
});
|
||||
|
||||
EXPECT_EQ(result.length(), 0u);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, TakeWhileAll) {
|
||||
kmp_str_ref s("hello");
|
||||
|
||||
kmp_str_ref result = s.take_while([](char c) {
|
||||
return static_cast<bool>(isalpha(static_cast<unsigned char>(c)));
|
||||
});
|
||||
|
||||
EXPECT_EQ(result.length(), 5u);
|
||||
EXPECT_TRUE(equals(result, "hello"));
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Integration / Complex Scenarios
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
TEST(kmp_str_ref_test, ParseKeyValuePair) {
|
||||
kmp_str_ref s("key=value");
|
||||
|
||||
kmp_str_ref key = s.take_while([](char c) { return c != '='; });
|
||||
s.drop_front(key.length());
|
||||
s.consume_front("=");
|
||||
|
||||
EXPECT_EQ(key.length(), 3u);
|
||||
EXPECT_TRUE(equals(key, "key"));
|
||||
EXPECT_TRUE(equals(s, "value"));
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, ParseCommaSeparated) {
|
||||
kmp_str_ref s("1,2,3");
|
||||
int values[3] = {0, 0, 0};
|
||||
int count = 0;
|
||||
|
||||
while (s.length() > 0 && count < 3) {
|
||||
s.consume_integer(values[count++]);
|
||||
s.consume_front(",");
|
||||
}
|
||||
|
||||
EXPECT_EQ(count, 3);
|
||||
EXPECT_EQ(values[0], 1);
|
||||
EXPECT_EQ(values[1], 2);
|
||||
EXPECT_EQ(values[2], 3);
|
||||
}
|
||||
|
||||
TEST(kmp_str_ref_test, ParseWithWhitespace) {
|
||||
kmp_str_ref s(" hello world ");
|
||||
|
||||
s.skip_space();
|
||||
kmp_str_ref word1 = s.take_while([](char c) { return c != ' '; });
|
||||
s.drop_front(word1.length());
|
||||
s.skip_space();
|
||||
kmp_str_ref word2 = s.take_while([](char c) { return c != ' '; });
|
||||
|
||||
EXPECT_EQ(word1.length(), 5u);
|
||||
EXPECT_TRUE(equals(word1, "hello"));
|
||||
EXPECT_EQ(word2.length(), 5u);
|
||||
EXPECT_TRUE(equals(word2, "world"));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
@ -67,4 +67,5 @@ add_openmp_testsuite(
|
|||
DEPENDS OpenMPUnitTests
|
||||
)
|
||||
|
||||
add_subdirectory(ADT)
|
||||
add_subdirectory(String)
|
||||
|
|
|
|||
|
|
@ -111,6 +111,33 @@ TEST(KmpStrTest, BasicStrToIntInvalid) {
|
|||
EXPECT_EQ(__kmp_basic_str_to_int("7.5"), 7);
|
||||
}
|
||||
|
||||
// Test basic string to int conversion with maxlen parameter
|
||||
TEST(KmpStrTest, BasicStrToIntMaxLen) {
|
||||
// maxlen limits how many characters are parsed
|
||||
EXPECT_EQ(__kmp_basic_str_to_int("12345", 3), 123);
|
||||
EXPECT_EQ(__kmp_basic_str_to_int("12345", 1), 1);
|
||||
EXPECT_EQ(__kmp_basic_str_to_int("12345", 5), 12345);
|
||||
|
||||
// maxlen larger than string length parses entire string
|
||||
EXPECT_EQ(__kmp_basic_str_to_int("42", 10), 42);
|
||||
EXPECT_EQ(__kmp_basic_str_to_int("123", 100), 123);
|
||||
|
||||
// maxlen of 0 returns 0 (no characters parsed)
|
||||
EXPECT_EQ(__kmp_basic_str_to_int("12345", 0), 0);
|
||||
|
||||
// maxlen with mixed content: stops at maxlen or non-digit, whichever first
|
||||
EXPECT_EQ(__kmp_basic_str_to_int("123abc", 2), 12);
|
||||
EXPECT_EQ(__kmp_basic_str_to_int("123abc", 5),
|
||||
123); // stops at 'a' before maxlen
|
||||
|
||||
// maxlen with leading zeros
|
||||
EXPECT_EQ(__kmp_basic_str_to_int("007", 2), 0);
|
||||
EXPECT_EQ(__kmp_basic_str_to_int("007", 3), 7);
|
||||
|
||||
// Default maxlen (INT_MAX) behaves like before
|
||||
EXPECT_EQ(__kmp_basic_str_to_int("999"), 999);
|
||||
}
|
||||
|
||||
// Test string match
|
||||
TEST(KmpStrTest, StrMatch) {
|
||||
const char *data = "Hello World";
|
||||
|
|
|
|||
Loading…
Reference in a new issue