diff --git a/nob.c b/nob.c index 872d219..1c92a7b 100644 --- a/nob.c +++ b/nob.c @@ -21,6 +21,7 @@ const char *test_names[] = { "da_append", "sb_appendf", "da_foreach", + "nob_sv_test", }; #define test_names_count ARRAY_LEN(test_names) diff --git a/nob.h b/nob.h index 4699e9f..189a111 100644 --- a/nob.h +++ b/nob.h @@ -184,6 +184,7 @@ #include #include #include +#include #include #include #include @@ -612,6 +613,25 @@ Nob_String_View nob_sv_from_parts(const char *data, size_t count); // nob_sb_to_sv() enables you to just view Nob_String_Builder as Nob_String_View #define nob_sb_to_sv(sb) nob_sv_from_parts((sb).items, (sb).count) +Nob_String_View nob_sv_take_left_while(Nob_String_View sv, bool (*predicate)(char x)); +Nob_String_View nob_sv_chop_by_sv(Nob_String_View *sv, Nob_String_View thicc_delim); +bool nob_sv_try_chop_by_delim(Nob_String_View *sv, char delim, Nob_String_View *chunk); +Nob_String_View nob_sv_chop_right(Nob_String_View *sv, size_t n); +Nob_String_View nob_sv_chop_left_while(Nob_String_View *sv, bool (*predicate)(char x)); +bool nob_sv_index_of(Nob_String_View sv, char c, size_t *index); +bool nob_sv_eq_ignorecase(Nob_String_View a, Nob_String_View b); +uint64_t nob_sv_to_u64(Nob_String_View sv); +uint64_t nob_sv_chop_u64(Nob_String_View *sv); + +#define SV(cstr_lit) nob_sv_from_parts(cstr_lit, sizeof(cstr_lit) - 1) +#define SV_STATIC(cstr_lit) \ + { \ + sizeof(cstr_lit) - 1, \ + (cstr_lit) \ + } + +#define SV_NULL nob_sv_from_parts(NULL, 0) + // printf macros for String_View #ifndef SV_Fmt #define SV_Fmt "%.*s" @@ -1762,6 +1782,109 @@ bool nob_sv_end_with(Nob_String_View sv, const char *cstr) return false; } +Nob_String_View nob_sv_take_left_while(Nob_String_View sv, bool (*predicate)(char x)) { + size_t i = 0; + while (i < sv.count && predicate(sv.data[i])) { + i += 1; + } + return nob_sv_from_parts(sv.data, i); +} + +Nob_String_View nob_sv_chop_by_sv(Nob_String_View *sv, Nob_String_View thicc_delim) { + Nob_String_View window = nob_sv_from_parts(sv->data, thicc_delim.count); + size_t i = 0; + while (i + thicc_delim.count < sv->count && !(nob_sv_eq(window, thicc_delim))) { + i++; + window.data++; + } + + Nob_String_View result = nob_sv_from_parts(sv->data, i); + + if (i + thicc_delim.count == sv->count) { + // include last characters if they aren't + // equal to thicc_delim + result.count += thicc_delim.count; + } + + // Chop! + sv->data += i + thicc_delim.count; + sv->count -= i + thicc_delim.count; + + return result; +} + +bool nob_sv_try_chop_by_delim(Nob_String_View *sv, char delim, Nob_String_View *chunk) { + size_t i = 0; + while (i < sv->count && sv->data[i] != delim) { + i += 1; + } + + Nob_String_View result = nob_sv_from_parts(sv->data, i); + + if (i < sv->count) { + sv->count -= i + 1; + sv->data += i + 1; + if (chunk) { + *chunk = result; + } + return true; + } + + return false; +} + +Nob_String_View nob_sv_chop_right(Nob_String_View *sv, size_t n) { + if (n > sv->count) { + n = sv->count; + } + + Nob_String_View result = nob_sv_from_parts(sv->data + sv->count - n, n); + + sv->count -= n; + + return result; +} + +Nob_String_View nob_sv_chop_left_while(Nob_String_View *sv, bool (*predicate)(char x)) { + size_t i = 0; + while (i < sv->count && predicate(sv->data[i])) { + i += 1; + } + return nob_sv_chop_left(sv, i); +} + +bool nob_sv_index_of(Nob_String_View sv, char c, size_t *index) { + size_t i = 0; + while (i < sv.count && sv.data[i] != c) { + i += 1; + } + + if (i < sv.count) { + if (index) { + *index = i; + } + return true; + } else { + return false; + } +} + +bool nob_sv_eq_ignorecase(Nob_String_View a, Nob_String_View b) { + if (a.count != b.count) { + return false; + } + + char x, y; + for (size_t i = 0; i < a.count; i++) { + x = 'A' <= a.data[i] && a.data[i] <= 'Z' ? a.data[i] + 32 : a.data[i]; + + y = 'A' <= b.data[i] && b.data[i] <= 'Z' ? b.data[i] + 32 : b.data[i]; + + if (x != y) + return false; + } + return true; +} bool nob_sv_starts_with(Nob_String_View sv, Nob_String_View expected_prefix) { @@ -1773,6 +1896,26 @@ bool nob_sv_starts_with(Nob_String_View sv, Nob_String_View expected_prefix) return false; } +uint64_t nob_sv_to_u64(Nob_String_View sv) { + uint64_t result = 0; + + for (size_t i = 0; i < sv.count && isdigit(sv.data[i]); ++i) { + result = result * 10 + (uint64_t)sv.data[i] - '0'; + } + + return result; +} + +uint64_t nob_sv_chop_u64(Nob_String_View *sv) { + uint64_t result = 0; + while (sv->count > 0 && isdigit(*sv->data)) { + result = result * 10 + *sv->data - '0'; + sv->count -= 1; + sv->data += 1; + } + return result; +} + // RETURNS: // 0 - file does not exists // 1 - file exists @@ -2033,6 +2176,15 @@ int closedir(DIR *dirp) #define sv_end_with nob_sv_end_with #define sv_from_cstr nob_sv_from_cstr #define sv_from_parts nob_sv_from_parts + #define sv_take_left_while nob_sv_take_left_while + #define sv_chop_by_sv nob_sv_chop_by_sv + #define sv_try_chop_by_delim nob_sv_try_chop_by_delim + #define sv_chop_right nob_sv_chop_right + #define sv_chop_left_while nob_sv_chop_left_while + #define sv_index_of nob_sv_index_of + #define sv_eq_ignorecase nob_sv_eq_ignorecase + #define sv_to_u64 nob_sv_to_u64 + #define sv_chop_u64 nob_sv_chop_u64 #define sb_to_sv nob_sb_to_sv #define win32_error_message nob_win32_error_message #endif // NOB_STRIP_PREFIX diff --git a/tests/nob_sv_test.c b/tests/nob_sv_test.c new file mode 100644 index 0000000..b2718da --- /dev/null +++ b/tests/nob_sv_test.c @@ -0,0 +1,262 @@ +#include +#include +#include + +#define NOB_IMPLEMENTATION +#include "nob.h" + +void sv_assert_eq_Nob_String_View(const char *file, size_t line, + const char *expected_expr, Nob_String_View expected, + const char *actual_expr, Nob_String_View actual) +{ + if (!nob_sv_eq(expected, actual)) { + fprintf(stderr, "%s:%zu: FAILED: %s == %s\n", + file, line, expected_expr, actual_expr); + fprintf(stderr, " EXPECTED: " SV_Fmt "\n", SV_Arg(expected)); + fprintf(stderr, " ACTUAL: " SV_Fmt "\n", SV_Arg(actual)); + exit(1); + } +} + +void sv_assert_eq_size_t(const char *file, size_t line, + const char *expected_expr, size_t expected, + const char *actual_expr, size_t actual) +{ + if (expected != actual) { + fprintf(stderr, "%s:%zu: FAILED: %s == %s\n", + file, line, expected_expr, actual_expr); + fprintf(stderr, " EXPECTED: %zu\n", expected); + fprintf(stderr, " ACTUAL: %zu\n", actual); + exit(1); + } +} + +void sv_assert_eq_uint64_t(const char *file, size_t line, + const char *expected_expr, uint64_t expected, + const char *actual_expr, uint64_t actual) +{ + if (expected != actual) { + fprintf(stderr, "%s:%zu: FAILED: %s == %s\n", + file, line, expected_expr, actual_expr); + fprintf(stderr, " EXPECTED: %" PRIu64 "\n", expected); + fprintf(stderr, " ACTUAL: %" PRIu64 "\n", actual); + exit(1); + } +} + +#define ASSERT_EQ(type, expected, actual) sv_assert_eq_##type(__FILE__, __LINE__, #expected, expected, #actual, actual) + +void sv_assert_true(const char *file, size_t line, + const char *expression_cstr, + bool expression) +{ + if (!expression) { + fprintf(stderr, "%s:%zu: FAILED: %s\n", file, line, expression_cstr); + exit(1); + } +} + +#define ASSERT_TRUE(expression) sv_assert_true(__FILE__, __LINE__, #expression, expression); + +bool is_alpha(char x) +{ + return isalpha(x); +} + +int main(void) +{ + // Construct + { + ASSERT_EQ(Nob_String_View, SV("Foo"), nob_sv_from_cstr("Foo")); + } + + // Trimming Whitespace + { + ASSERT_EQ(Nob_String_View, SV("hello "), nob_sv_trim_left(SV(" hello "))); + ASSERT_EQ(Nob_String_View, SV(" hello"), nob_sv_trim_right(SV(" hello "))); + ASSERT_EQ(Nob_String_View, SV("hello"), nob_sv_trim(SV(" hello "))); + } + + // Chop by delimiter + { + // Existing + { + Nob_String_View input = SV_STATIC("hello\nworld"); + Nob_String_View line = nob_sv_chop_by_delim(&input, '\n'); + ASSERT_EQ(Nob_String_View, SV("hello"), line); + ASSERT_EQ(Nob_String_View, SV("world"), input); + } + + // Non-Existing + { + Nob_String_View input = SV_STATIC("hello\nworld"); + Nob_String_View line = nob_sv_chop_by_delim(&input, ' '); + ASSERT_EQ(Nob_String_View, SV("hello\nworld"), line); + ASSERT_EQ(Nob_String_View, SV(""), input); + } + } + + // Chop by Nob_String_View (thicc delimiter) + { + // Existing + { + Nob_String_View input = SV_STATIC("hello\nworld\ngoodbye"); + Nob_String_View line = nob_sv_chop_by_sv(&input, SV("\nwor")); + ASSERT_EQ(Nob_String_View, SV("hello"), line); + ASSERT_EQ(Nob_String_View, SV("ld\ngoodbye"), input); + } + + // Non-Existing + { + Nob_String_View input = SV_STATIC("hello\nworld"); + Nob_String_View line = nob_sv_chop_by_sv(&input, SV("goodbye")); + ASSERT_EQ(Nob_String_View, SV("hello\nworld"), line); + ASSERT_EQ(Nob_String_View, SV(""), input); + } + } + + // Try to chop by delimiter + { + // Existing + { + Nob_String_View input = SV_STATIC("hello\nworld"); + Nob_String_View line = SV_NULL; + bool result = nob_sv_try_chop_by_delim(&input, '\n', &line); + ASSERT_TRUE(result); + ASSERT_EQ(Nob_String_View, SV("hello"), line); + ASSERT_EQ(Nob_String_View, SV("world"), input); + } + + // Non-Existing + { + Nob_String_View input = SV_STATIC("hello\nworld"); + Nob_String_View line = SV_NULL; + bool result = nob_sv_try_chop_by_delim(&input, ' ', &line); + ASSERT_TRUE(!result); + ASSERT_EQ(Nob_String_View, SV(""), line); + ASSERT_EQ(Nob_String_View, SV("hello\nworld"), input); + } + } + + // Chop N characters + { + // Chop left + { + Nob_String_View input = SV_STATIC("hello"); + Nob_String_View hell = nob_sv_chop_left(&input, 4); + ASSERT_EQ(Nob_String_View, SV("o"), input); + ASSERT_EQ(Nob_String_View, SV("hell"), hell); + } + + // Overchop left + { + Nob_String_View input = SV_STATIC("hello"); + Nob_String_View hell = nob_sv_chop_left(&input, 10); + ASSERT_EQ(Nob_String_View, SV(""), input); + ASSERT_EQ(Nob_String_View, SV("hello"), hell); + } + + // Chop right + { + Nob_String_View input = SV_STATIC("hello"); + Nob_String_View hell = nob_sv_chop_right(&input, 4); + ASSERT_EQ(Nob_String_View, SV("h"), input); + ASSERT_EQ(Nob_String_View, SV("ello"), hell); + } + + // Overchop right + { + Nob_String_View input = SV_STATIC("hello"); + Nob_String_View hell = nob_sv_chop_right(&input, 10); + ASSERT_EQ(Nob_String_View, SV(""), input); + ASSERT_EQ(Nob_String_View, SV("hello"), hell); + } + } + + // Take while + { + // Take while is_alpha + { + Nob_String_View input = SV_STATIC("hello1234"); + Nob_String_View hello = nob_sv_take_left_while(input, is_alpha); + ASSERT_EQ(Nob_String_View, SV("hello1234"), input); + ASSERT_EQ(Nob_String_View, SV("hello"), hello); + } + + // Overtake while + { + Nob_String_View input = SV_STATIC("helloworld"); + Nob_String_View hello = nob_sv_take_left_while(input, is_alpha); + ASSERT_EQ(Nob_String_View, SV("helloworld"), input); + ASSERT_EQ(Nob_String_View, SV("helloworld"), hello); + } + } + + // Chop while + { + // Chop while is_alpha + { + Nob_String_View input = SV_STATIC("hello1234"); + Nob_String_View hello = nob_sv_chop_left_while(&input, is_alpha); + ASSERT_EQ(Nob_String_View, SV("1234"), input); + ASSERT_EQ(Nob_String_View, SV("hello"), hello); + } + + // Overchop while + { + Nob_String_View input = SV_STATIC("helloworld"); + Nob_String_View hello = nob_sv_chop_left_while(&input, is_alpha); + ASSERT_EQ(Nob_String_View, SV(""), input); + ASSERT_EQ(Nob_String_View, SV("helloworld"), hello); + } + } + + // Equals, ignoring case + { + // exactly equal + { + Nob_String_View input = SV_STATIC("hello, world"); + ASSERT_TRUE(nob_sv_eq_ignorecase(input, SV("hello, world"))); + } + + // equal ignoring case + { + Nob_String_View input = SV_STATIC("Hello, World"); + ASSERT_TRUE(nob_sv_eq_ignorecase(input, SV("hello, world"))); + } + + // unequal + { + Nob_String_View input = SV_STATIC("Goodbye, World"); + ASSERT_TRUE(!(nob_sv_eq_ignorecase(input, SV("Hello, World")))); + } + } + + // Index of + { + size_t index = 0; + ASSERT_TRUE(nob_sv_index_of(SV("hello world"), ' ', &index)); + ASSERT_EQ(size_t, 5, index); + } + + // Prefix/suffix check + { + ASSERT_TRUE(nob_sv_starts_with(SV("Hello, World"), SV("Hello"))); + ASSERT_TRUE(nob_sv_end_with(SV("Hello, World"), "World")); + } + + // To Integer + { + Nob_String_View input = SV_STATIC("1234567890"); + + ASSERT_EQ(uint64_t, 1234567890, nob_sv_to_u64(input)); + ASSERT_EQ(Nob_String_View, input, SV("1234567890")); + + ASSERT_EQ(uint64_t, 1234567890, nob_sv_chop_u64(&input)); + ASSERT_TRUE(input.count == 0); + } + + printf("OK\n"); + + return 0; +}