#ifndef STR_H #define STR_H #include #include #include 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 #include 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; } return memcmp(s.data, prefix.data, prefix.len) == 0; } bool str_ends_with(str s, str suffix) { if (suffix.len > s.len) { return false; } return memcmp(s.data + s.len - suffix.len, suffix.data, suffix.len) == 0; } 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 */