208 lines
4.9 KiB
C
208 lines
4.9 KiB
C
#ifndef STR_H
|
|
#define STR_H
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <stdbool.h>
|
|
|
|
typedef struct {
|
|
uint8_t *data;
|
|
size_t len;
|
|
} str;
|
|
|
|
typedef struct {
|
|
bool found;
|
|
size_t offset;
|
|
} str_find_result;
|
|
|
|
#define STR(s) (str) { .data = (uint8_t *) s, .len = sizeof(s) - 1 }
|
|
|
|
str make_str(uint8_t *data, size_t len);
|
|
str cstr(const char *cstr);
|
|
str str_next_token(str *source, str delim);
|
|
str str_sub(str s, size_t start, size_t end);
|
|
str str_trim_left(str s);
|
|
str str_trim_right(str s);
|
|
str str_trim(str s);
|
|
bool str_eq(str a, str b);
|
|
bool str_starts_with(str s, str prefix);
|
|
bool str_ends_with(str s, str suffix);
|
|
bool str_empty(str s);
|
|
void str_split(str s, str delim, str *first, str *second);
|
|
|
|
str_find_result str_find_left(str haystack, str needle);
|
|
str_find_result str_find_right(str haystack, str needle);
|
|
|
|
void str_print(str s);
|
|
|
|
#define STR_FMT "%.*s"
|
|
#define STR_ARG(s) (int) (s).len, (const char *) (s).data
|
|
|
|
#ifdef STR_IMPLEMENTATION
|
|
|
|
#include <stdio.h>
|
|
|
|
str make_str(uint8_t *data, size_t len) {
|
|
return (str) { .data = data, .len = len };
|
|
}
|
|
|
|
str cstr(const char *cstr) {
|
|
size_t len = 0;
|
|
for (const char *it = cstr; *it; ++it) {
|
|
len++;
|
|
}
|
|
return (str) {
|
|
.data = (uint8_t *) cstr,
|
|
.len = len
|
|
};
|
|
}
|
|
|
|
str str_sub(str s, size_t start, size_t end) {
|
|
return (str) {
|
|
.data = s.data + start,
|
|
.len = end - start
|
|
};
|
|
}
|
|
|
|
static inline bool is_whitespace(int ch) {
|
|
return ch == ' ' || ch == '\r' || ch == '\n'
|
|
|| ch == '\t' || ch == '\v' || ch == '\f';
|
|
}
|
|
|
|
str str_trim_left(str s) {
|
|
// TODO make more efficient!
|
|
while (s.len > 0 && is_whitespace(s.data[0])) {
|
|
s.data++;
|
|
s.len--;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
str str_trim_right(str s) {
|
|
// TODO make more efficient!
|
|
while (s.len > 0 && is_whitespace(s.data[s.len - 1])) {
|
|
s.len--;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
str str_trim(str s) {
|
|
return str_trim_right(str_trim_left(s));
|
|
}
|
|
|
|
bool str_eq(str a, str b) {
|
|
if (a.len != b.len) {
|
|
return false;
|
|
}
|
|
else {
|
|
for (size_t i = 0; i < a.len; ++i) {
|
|
if (a.data[i] != b.data[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool str_starts_with(str s, str prefix) {
|
|
if (prefix.len > s.len) {
|
|
return false;
|
|
}
|
|
for (size_t index = 0; index < prefix.len; ++index) {
|
|
if (s.data[index] != prefix.data[index]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool str_ends_with(str s, str suffix) {
|
|
if (suffix.len > s.len) {
|
|
return false;
|
|
}
|
|
size_t offset = s.len - suffix.len;
|
|
for (size_t index = 0; index < suffix.len; ++index) {
|
|
if (s.data[offset + index] != suffix.data[index]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool str_empty(str s) {
|
|
return s.len == 0;
|
|
}
|
|
|
|
void str_print(str s) {
|
|
printf("%.*s", (int) s.len, s.data);
|
|
}
|
|
|
|
str_find_result str_find_left(str haystack, str needle) {
|
|
if (needle.len > haystack.len) {
|
|
return (str_find_result) { .found = false };
|
|
}
|
|
for (size_t start = 0; start < haystack.len - needle.len; ++start) {
|
|
bool found = true;
|
|
for (size_t offset = 0; offset < needle.len; ++offset) {
|
|
if (haystack.data[start + offset] != needle.data[offset]) {
|
|
found = false;
|
|
break;
|
|
}
|
|
}
|
|
if (found) {
|
|
return (str_find_result) { .found = true, .offset = start };
|
|
}
|
|
}
|
|
return (str_find_result) { .found = false };
|
|
}
|
|
|
|
str_find_result str_find_right(str haystack, str needle) {
|
|
if (needle.len > haystack.len) {
|
|
return (str_find_result) { .found = false };
|
|
}
|
|
// TODO is intmax_t a good idea?
|
|
for (intmax_t start = haystack.len - needle.len;
|
|
start >= 0;
|
|
++start)
|
|
{
|
|
bool found = true;
|
|
for (size_t offset = 0; offset < needle.len; ++offset) {
|
|
if (haystack.data[start + offset] != needle.data[offset]) {
|
|
found = false;
|
|
break;
|
|
}
|
|
}
|
|
if (found) {
|
|
return (str_find_result) { .found = true, .offset = start };
|
|
}
|
|
}
|
|
return (str_find_result) { .found = false };
|
|
}
|
|
|
|
str str_next_token(str *source, str delim) {
|
|
str_find_result match = str_find_left(*source, delim);
|
|
if (match.found) {
|
|
size_t skip_len = match.offset + delim.len;
|
|
str result = make_str(source->data, match.offset);
|
|
source->data += skip_len;
|
|
source->len -= skip_len;
|
|
return result;
|
|
}
|
|
else {
|
|
// if we reach here, we have reached the end of `source` without
|
|
// encountering the delimiter -> return the remainder of the string.
|
|
str result = *source;
|
|
source->data += source->len;
|
|
source->len = 0;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
void str_split(str s, str delim, str *first, str *second) {
|
|
*first = str_next_token(&s, delim);
|
|
*second = s;
|
|
}
|
|
|
|
#endif /* STR_IMPLEMENTATION */
|
|
#endif /* STR_H */
|