1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
|
/// \file
/// \ingroup cgraph_utils
/// \brief Non-owning string references
///
/// This is similar to C++17’s `std::string_view`. Instances of `strview_t`
/// should generally be passed around by value rather than pointer as they are
/// small.
#pragma once
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include <util/alloc.h>
#include <util/startswith.h>
#include <util/strcasecmp.h>
/// a non-owning string reference
typedef struct {
const char *data; ///< start of the pointed to string
size_t size; ///< extent of the string in bytes
} strview_t;
/// create a string reference
static inline strview_t strview(const char *referent, char terminator) {
assert(referent != NULL);
// can we find the terminator before the end of the containing string?
const char *end = strchr(referent, terminator);
if (end != NULL) {
return (strview_t){.data = referent, .size = (size_t)(end - referent)};
}
// otherwise, span the entire string
return (strview_t){.data = referent, .size = strlen(referent)};
}
/// make a heap-allocated string from this string view
static inline char *strview_str(strview_t source) {
assert(source.data != NULL);
return gv_strndup(source.data, source.size);
}
/// compare two string references for case insensitive equality
static inline bool strview_case_eq(strview_t a, strview_t b) {
assert(a.data != NULL);
assert(b.data != NULL);
if (a.size != b.size) {
return false;
}
return strncasecmp(a.data, b.data, a.size) == 0;
}
/// compare a string reference to a string for case insensitive equality
static inline bool strview_case_str_eq(strview_t a, const char *b) {
assert(a.data != NULL);
assert(b != NULL);
return strview_case_eq(a, strview(b, '\0'));
}
/// compare two string references
static inline int strview_cmp(strview_t a, strview_t b) {
size_t min_size = a.size > b.size ? b.size : a.size;
int cmp = strncmp(a.data, b.data, min_size);
if (cmp != 0) {
return cmp;
}
if (a.size > b.size) {
return 1;
}
if (a.size < b.size) {
return -1;
}
return 0;
}
/// compare two string references for equality
static inline bool strview_eq(strview_t a, strview_t b) {
assert(a.data != NULL);
assert(b.data != NULL);
return strview_cmp(a, b) == 0;
}
/// compare a string reference to a string for equality
static inline bool strview_str_eq(strview_t a, const char *b) {
assert(a.data != NULL);
assert(b != NULL);
return strview_eq(a, strview(b, '\0'));
}
/// does the given string appear as a substring of the string view?
static inline bool strview_str_contains(strview_t haystack,
const char *needle) {
assert(haystack.data != NULL);
assert(needle != NULL);
// the empty string is a substring of everything
if (strcmp(needle, "") == 0) {
return true;
}
for (size_t offset = 0; offset < haystack.size;) {
// find the next possible starting point for the substring
const char *candidate = (const char *)memchr(
haystack.data + offset, needle[0], haystack.size - offset);
if (candidate == NULL) {
break;
}
// is it too close to the end of the containing string?
if ((size_t)(haystack.data + haystack.size - candidate) < strlen(needle)) {
return false;
}
// is it a match?
if (startswith(candidate, needle)) {
return true;
}
// advance to the position after this match for the next scan
offset = (size_t)(candidate - haystack.data) + 1;
}
return false;
}
|