From a2dbda162fc0f8f994661aed8061858eb58d6b73 Mon Sep 17 00:00:00 2001 From: Randy Jordan Date: Sat, 17 Jan 2026 12:19:24 -0600 Subject: [PATCH] Initial commit --- LICENSE | 21 +++++ Makefile | 76 ++++++++++++++++++ README.md | 28 +++++++ include/sv.h | 34 ++++++++ src/sv.c | 152 ++++++++++++++++++++++++++++++++++++ tests/01_sv_str.c | 15 ++++ tests/02_sv_strn.c | 15 ++++ tests/03_trim_left.c | 24 ++++++ tests/04_trim_right.c | 21 +++++ tests/05_trim.c | 22 ++++++ tests/06_sv_eq.c | 25 ++++++ tests/07_case_cmp.c | 29 +++++++ tests/08_sv_before_delims.c | 18 +++++ tests/09_after_delims.c | 18 +++++ tests/10_strstr.c | 20 +++++ tests/11_sv_pack.c | 24 ++++++ tests/12_sv_pack2.c | 21 +++++ 17 files changed, 563 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 include/sv.h create mode 100644 src/sv.c create mode 100644 tests/01_sv_str.c create mode 100644 tests/02_sv_strn.c create mode 100644 tests/03_trim_left.c create mode 100644 tests/04_trim_right.c create mode 100644 tests/05_trim.c create mode 100644 tests/06_sv_eq.c create mode 100644 tests/07_case_cmp.c create mode 100644 tests/08_sv_before_delims.c create mode 100644 tests/09_after_delims.c create mode 100644 tests/10_strstr.c create mode 100644 tests/11_sv_pack.c create mode 100644 tests/12_sv_pack2.c diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8aa2645 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [year] [fullname] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..95a2a59 --- /dev/null +++ b/Makefile @@ -0,0 +1,76 @@ +# Compiler Flags +CC := gcc +CFLAGS := -g -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined -fno-omit-frame-pointer + +# Directory variables +LIBDIR := lib +OBJ := obj +INC := include +SRC := src +TEST := tests + +# Filepath Pattern Matching +LIB := $(LIBDIR)/lib.a +SRCS := $(wildcard $(SRC)/*.c) +OBJS := $(patsubst $(SRC)/%.c, $(OBJ)/%.o, $(SRCS)) +TESTS := $(wildcard $(TEST)/*.c) +TESTBINS := $(patsubst $(TEST)/%.c, $(TEST)/bin/%, $(TESTS)) + +# Commands must be labeled PHONY +.PHONY: all release clean test + +# Compiler Release Flags +release: CFLAGS := -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined -fno-omit-frame-pointer -O2 -DNDEBUG +release: clean $(LIB) + +# Target for compilation. +all: $(LIB) + +# Target / Dependencies +$(LIB): $(OBJS) | $(LIBDIR) + $(RM) $(LIB) + ar -cvrs $@ $^ + +$(OBJ)/%.o: $(SRC)/%.c $(SRC)/%.h | $(OBJ) + $(CC) $(CFLAGS) -c $< -o $@ + +$(OBJ)/%.o: $(SRC)/%.c | $(OBJ) + $(CC) $(CFLAGS) -c $< -o $@ + +$(TEST)/bin/%: $(TEST)/%.c $(LIB) | $(TEST)/bin + $(CC) $(CFLAGS) $< $(LIB) -o $@ + +# Make directories if none. +$(LIBDIR): + mkdir $@ + +$(INC): + mkdir $@ + +$(OBJ): + mkdir $@ + +$(TEST)/bin: + mkdir $@ + +# Run the tests in the bin folder and track results +test: $(LIB) $(TEST)/bin $(TESTBINS) + @SUCCESS_COUNT=0; FAILURE_COUNT=0; \ + for test in $(TESTBINS); do \ + ./$$test; \ + EXIT_CODE=$$?; \ + TEST_NAME=$(notdir $$test); \ + if [ $$EXIT_CODE -eq 0 ]; then \ + echo "\033[0;32m$$TEST_NAME: EXIT CODE: $$EXIT_CODE (SUCCESS)\033[0m"; \ + SUCCESS_COUNT=$$((SUCCESS_COUNT + 1)); \ + else \ + echo "\033[0;31m$$TEST_NAME: EXIT CODE: $$EXIT_CODE (FAILURE)\033[0m"; \ + FAILURE_COUNT=$$((FAILURE_COUNT + 1)); \ + fi; \ + done; \ + echo "\n\nTests completed"; \ + echo "SUCCESS: $$SUCCESS_COUNT"; \ + echo "FAILURE: $$FAILURE_COUNT"; + +clean: + $(RM) -r $(LIBDIR) $(OBJ) $(TEST)/bin/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..bbd23ec --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# sv + +## Description +String View implementation written in c. + +## Table of Contents + +- [Description](#description) +- [Features](#features) +- [Usage](#usage) +- [Credits / Resources](#credits--resources) +- [License](#license) + +## Features / TODOS ++ Easy strings. ++ Safe access. ++ Clear semantics + + +## Usage + +## Credits / Resources +[Mongoose Webserver](https://mongoose.ws/)
+[Tsoding SV](https://github.com/tsoding/sv)
+[C Interfaces and Implementations - David Hanson](https://github.com/drh/cii)
+ +## License +This project is licensed under MIT - see the [LICENSE](LICENSE) file for details. diff --git a/include/sv.h b/include/sv.h new file mode 100644 index 0000000..613c5b8 --- /dev/null +++ b/include/sv.h @@ -0,0 +1,34 @@ +#ifndef SV_INCLUDED +#define SV_INCLUDED + +#include +struct String_View { + size_t len; + const char *buf; +}; +typedef struct String_View SV; + +// Macros for String_View printf +#define SV_FMT "%.*s" +#define SV_ARG(sv) (int) (sv).len, (sv).buf + +// Null String +#define SV_NULL sv_strn(NULL, 0) + +extern struct String_View sv_str(const char *s); +extern struct String_View sv_strn(const char *s, size_t n); +extern struct String_View sv_trim_left(struct String_View sv); +extern struct String_View sv_trim_right(struct String_View sv); +extern struct String_View sv_trim(struct String_View sv); +extern int sv_eq(const struct String_View sv1, const struct String_View sv2); +extern int sv_casecmp( const struct String_View sv1, const struct String_View sv2); +extern struct String_View sv_before_delim(const struct String_View sv, + const char *delims); +extern struct String_View sv_after_delim(const struct String_View sv, + const char *delims); +extern struct String_View sv_str_str(const struct String_View haystack, + const struct String_View needle); +extern size_t sv_pack_size(const struct String_View sv); +extern void sv_pack(unsigned char *buf, const struct String_View sv); +extern struct String_View sv_unpack(unsigned char *buf); +#endif diff --git a/src/sv.c b/src/sv.c new file mode 100644 index 0000000..f18abe6 --- /dev/null +++ b/src/sv.c @@ -0,0 +1,152 @@ +#include "../include/sv.h" +#include +#include +#include +#include + +static bool sv_is_space(int c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\v' || c == '\f'; +} +static int sv_tolc(char c) { + return (c >= 'A' && c <= 'Z') ? c + 'a' - 'A' : c; +} +static void sv_pack_u64(uint64_t i, unsigned char *buf){ + *buf++ = i>>56; + *buf++ = i>>48; + *buf++ = i>>40; + *buf++ = i>>32; + *buf++ = i>>24; + *buf++ = i>>16; + *buf++ = i>>8; + *buf++ = i; +} +static uint64_t sv_unpack_u64(unsigned char *buf){ +return ((unsigned long long int)buf[0]<<56) | + ((unsigned long long int)buf[1]<<48) | + ((unsigned long long int)buf[2]<<40) | + ((unsigned long long int)buf[3]<<32) | + ((unsigned long long int)buf[4]<<24) | + ((unsigned long long int)buf[5]<<16) | + ((unsigned long long int)buf[6]<<8) | + buf[7]; +} +struct String_View sv_str(const char *s) { + struct String_View str = {s == NULL ? 0 : strlen(s), (char *) s }; + return str; +} +struct String_View sv_strn(const char *s, size_t n) { + struct String_View str = {n, (char *) s}; + return str; +} + +struct String_View sv_trim(struct String_View s){ + while (s.len > 0 && sv_is_space((int) *s.buf)) s.buf++, s.len--; + while (s.len > 0 && sv_is_space((int) *(s.buf + s.len - 1))) s.len--; + return s; +} +struct String_View sv_trim_left(struct String_View s){ + while (s.len > 0 && sv_is_space((int) *s.buf)) s.buf++, s.len--; + return s; +} +struct String_View sv_trim_right(struct String_View s){ + + while (s.len > 0 && sv_is_space((int) *(s.buf + s.len - 1))) s.len--; + + return s; +} + +int sv_eq( const struct String_View sv1, const struct String_View sv2){ + if (sv1.len != sv2.len) { + return 0; + } else { + return memcmp(sv1.buf, sv2.buf, sv1.len) == 0; + } + return 0; +} +int sv_casecmp(const struct String_View sv1, const struct String_View sv2){ + size_t i = 0; + while (i < sv1.len && i < sv2.len) { + int c1 = sv_tolc(sv1.buf[i]); + int c2 = sv_tolc(sv2.buf[i]); + if (c1 < c2) return -1; + if (c1 > c2) return 1; + i++; + } + if (i < sv1.len) return 1; + if (i < sv2.len) return -1; + return 0; +} + +struct String_View sv_before_delim(const struct String_View sv,const char *delims){ + // Handle NULL pointer or empty string view + if (sv.buf == NULL || delims == NULL) { + return sv_strn(NULL, 0); + } + + size_t i = 0; + while (i < sv.len) { + // Check if the current character is a delimiter + for (size_t j = 0; delims[j] != '\0'; j++) { + if (sv.buf[i] == delims[j]) { + struct String_View result = sv_strn(sv.buf, i); + return result; + } + } + i++; + } + + // Return the whole string if no delimiter was found + struct String_View result = sv_strn(sv.buf, i); + return result; + +} +struct String_View sv_after_delim(const struct String_View sv, const char *delims){ + // Handle NULL pointer or empty string view + if (sv.buf == NULL || delims == NULL) { + return sv_strn(NULL, 0); + } + + size_t i = 0; + while (i < sv.len) { + // Check if the current character is a delimiter + for (size_t j = 0; delims[j] != '\0'; j++) { + if (sv.buf[i] == delims[j]) { + struct String_View result = sv_strn(sv.buf+i+1, sv.len-i-1); + return result; + } + } + i++; + } + + // Return the whole string if no delimiter was found + struct String_View result = sv_strn(sv.buf, i); + return result; +} + +struct String_View sv_str_str(const struct String_View haystack, const struct String_View needle){ + size_t i; + if (needle.len > haystack.len) return sv_strn(NULL,0); + if (needle.len == 0) return haystack; + for (i = 0; i <= haystack.len - needle.len; i++) { + if (memcmp(haystack.buf + i, needle.buf, needle.len) == 0) { + return sv_strn(haystack.buf + i, haystack.len -i); + } + } + return sv_strn(NULL,0); +} + +size_t sv_pack_size(const struct String_View sv){ + size_t result = 0; + result += sv.len; + result += sizeof(uint64_t); + return result; +} +void sv_pack(unsigned char *buf, const struct String_View sv){ + sv_pack_u64(sv.len, buf); + size_t offset = sizeof(uint64_t); + memcpy(buf+offset, sv.buf, sv.len); +} +struct String_View sv_unpack(unsigned char *buf){ + size_t len = sv_unpack_u64(buf); + return sv_strn((const char *)buf+sizeof(uint64_t),len); +} diff --git a/tests/01_sv_str.c b/tests/01_sv_str.c new file mode 100644 index 0000000..8344621 --- /dev/null +++ b/tests/01_sv_str.c @@ -0,0 +1,15 @@ +#define DEBUG +#include +#include +#include +#include "../include/sv.h" + +int main(void){ + const char *s = "Hello World!"; + SV sv = sv_str(s); + + assert(sv.buf != NULL); + assert(strlen(s) == sv.len); + assert( memcmp(sv.buf, s, strlen(s) ) == 0); + return EXIT_SUCCESS; +} diff --git a/tests/02_sv_strn.c b/tests/02_sv_strn.c new file mode 100644 index 0000000..85bc9a9 --- /dev/null +++ b/tests/02_sv_strn.c @@ -0,0 +1,15 @@ +#define DEBUG +#include +#include +#include +#include "../include/sv.h" + +int main(void){ + const char *s = "Hello World!"; + SV sv = sv_strn(s, strlen(s)); + + assert(sv.buf != NULL); + assert(strlen(s) == sv.len); + assert( memcmp(sv.buf, s, strlen(s) ) == 0); + return EXIT_SUCCESS; +} diff --git a/tests/03_trim_left.c b/tests/03_trim_left.c new file mode 100644 index 0000000..8183eca --- /dev/null +++ b/tests/03_trim_left.c @@ -0,0 +1,24 @@ +#define DEBUG +#include +#include +#include +#include +#include "../include/sv.h" + +int main(void){ + const char *s = " Hello World!"; + const char *s2 = "Hello World!"; + + SV sv = sv_str(s); + + assert(sv.buf != NULL); + assert(strlen(s) == sv.len); + assert( memcmp(sv.buf, s, strlen(s)) == 0); + SV sv2 = sv_trim_left(sv); + assert(sv2.len != strlen(s)); + assert( memcmp(sv2.buf, s, sv2.len) != 0); + assert( memcmp(sv2.buf, s2, sv2.len) == 0); + assert( sv2.len == strlen(s2)); + + return EXIT_SUCCESS; +} diff --git a/tests/04_trim_right.c b/tests/04_trim_right.c new file mode 100644 index 0000000..023ed04 --- /dev/null +++ b/tests/04_trim_right.c @@ -0,0 +1,21 @@ +#define DEBUG +#include +#include +#include +#include +#include "../include/sv.h" + +int main(void){ + const char *s = "Hello World! "; + const char *s2 = "Hello World!"; + + SV sv = sv_str(s); + assert(sv.buf != NULL); + assert(strlen(s) == sv.len); + assert( memcmp(sv.buf, s, strlen(s)) == 0); + SV sv2 = sv_trim_right(sv); + assert(sv2.len == strlen(s2)); + assert(memcmp(s2, sv2.buf, sv2.len) == 0); + assert(sv2.len != strlen(s)); + return EXIT_SUCCESS; +} diff --git a/tests/05_trim.c b/tests/05_trim.c new file mode 100644 index 0000000..d67f4d5 --- /dev/null +++ b/tests/05_trim.c @@ -0,0 +1,22 @@ +#define DEBUG +#include +#include +#include +#include +#include "../include/sv.h" + +int main(void){ + const char *s = " Hello World! "; + const char *s2 = "Hello World!"; + + SV sv = sv_str(s); + assert(sv.buf != NULL); + assert(strlen(s) == sv.len); + assert( memcmp(sv.buf, s, strlen(s)) == 0); + SV sv2 = sv_trim(sv); + assert(sv2.len == strlen(s2)); + assert(memcmp(s2, sv2.buf, sv2.len) == 0); + assert(sv2.len != strlen(s)); + assert( memcmp(s, sv2.buf, sv2.len) != 0); + return EXIT_SUCCESS; +} diff --git a/tests/06_sv_eq.c b/tests/06_sv_eq.c new file mode 100644 index 0000000..d4767ed --- /dev/null +++ b/tests/06_sv_eq.c @@ -0,0 +1,25 @@ +#define DEBUG +#include +#include +#include +#include +#include "../include/sv.h" + +int main(void){ + const char *s = " Hello World! "; + const char *s2 = "Hello World!"; + + SV sv = sv_str(s); + SV sv3 = sv_str(s2); + assert(sv.buf != NULL); + assert(strlen(s) == sv.len); + assert( memcmp(sv.buf, s, strlen(s)) == 0); + SV sv2 = sv_trim(sv); + assert(sv2.len == strlen(s2)); + assert(memcmp(s2, sv2.buf, sv2.len) == 0); + assert(sv2.len != strlen(s)); + assert( memcmp(s, sv2.buf, sv2.len) != 0); + assert( !sv_eq(sv, sv2)); + assert( sv_eq(sv2,sv3)); + return EXIT_SUCCESS; +} diff --git a/tests/07_case_cmp.c b/tests/07_case_cmp.c new file mode 100644 index 0000000..2ad46b5 --- /dev/null +++ b/tests/07_case_cmp.c @@ -0,0 +1,29 @@ +#define DEBUG +#include +#include +#include +#include +#include "../include/sv.h" + +int main(void){ + const char *s = " Hello World! "; + const char *s2 = "Hello World!"; + const char *s3 = "hElLo wOrLD!"; + const char *s4 = "Goodbye World!"; + + SV sv = sv_str(s); + SV sv3 = sv_str(s3); + SV sv4 = sv_str(s4); + assert(sv.buf != NULL); + assert(strlen(s) == sv.len); + assert( memcmp(sv.buf, s, strlen(s)) == 0); + SV sv2 = sv_trim(sv); + assert(sv2.len == strlen(s2)); + assert(memcmp(s2, sv2.buf, sv2.len) == 0); + assert(sv2.len != strlen(s)); + assert( memcmp(s, sv2.buf, sv2.len) != 0); + assert( !sv_eq(sv, sv2)); + assert( sv_casecmp(sv2,sv3) == 0); + assert( sv_casecmp(sv3,sv4) == 1); + return EXIT_SUCCESS; +} diff --git a/tests/08_sv_before_delims.c b/tests/08_sv_before_delims.c new file mode 100644 index 0000000..17bed74 --- /dev/null +++ b/tests/08_sv_before_delims.c @@ -0,0 +1,18 @@ +#define DEBUG +#include +#include +#include +#include +#include "../include/sv.h" + +int main(void){ + const char *s = "Hello World!,Goodbye World!"; + const char *s3 = "Hello World!"; + + struct String_View sv = sv_strn(s, strlen(s)); + struct String_View sv2 = sv_before_delim(sv,","); + assert(sv2.len == strlen(s3)); + assert( memcmp(sv2.buf, s3, sv2.len) == 0); + + return EXIT_SUCCESS; +} diff --git a/tests/09_after_delims.c b/tests/09_after_delims.c new file mode 100644 index 0000000..59ae50a --- /dev/null +++ b/tests/09_after_delims.c @@ -0,0 +1,18 @@ +#define DEBUG +#include +#include +#include +#include +#include "../include/sv.h" + +int main(void){ + const char *s = "Hello World!,Goodbye World!"; + const char *s3 = "Goodbye World!"; + + struct String_View sv = sv_strn(s, strlen(s)); + struct String_View sv2 = sv_after_delim(sv,","); + assert(sv2.len == strlen(s3)); + assert( memcmp(sv2.buf, s3, sv2.len) == 0); + + return EXIT_SUCCESS; +} diff --git a/tests/10_strstr.c b/tests/10_strstr.c new file mode 100644 index 0000000..75943fe --- /dev/null +++ b/tests/10_strstr.c @@ -0,0 +1,20 @@ +#define DEBUG +#include +#include +#include +#include +#include "../include/sv.h" + +int main(void){ + const char *s = "Hello World!,Goodbye World!"; + const char *n = "Goodbye"; + const char *test = "Goodbye World!"; + struct String_View sv = sv_strn(s, strlen(s)); + struct String_View sv2 = sv_strn(n, strlen(n)); + + struct String_View found = sv_str_str(sv, sv2); + assert(found.len == strlen(test)); + assert( memcmp(found.buf, test, found.len) == 0); + + return EXIT_SUCCESS; +} diff --git a/tests/11_sv_pack.c b/tests/11_sv_pack.c new file mode 100644 index 0000000..feae921 --- /dev/null +++ b/tests/11_sv_pack.c @@ -0,0 +1,24 @@ +#define DEBUG +#include +#include +#include +#include +#include +#include "../include/sv.h" + +int main(void){ + const char *s = "Hello"; + struct String_View sv = sv_strn(s, strlen(s)); + size_t offset = sv_pack_size(sv); + unsigned char *buf = malloc(offset); + + sv_pack(buf, sv); + + struct String_View out = sv_unpack(buf); + assert(out.len == strlen(s)); + assert( memcmp(out.buf, s, out.len) == 0); + assert( sv_eq(sv, out)); + assert( sv_casecmp(sv, out ) == 0); + free(buf); + return EXIT_SUCCESS; +} diff --git a/tests/12_sv_pack2.c b/tests/12_sv_pack2.c new file mode 100644 index 0000000..d8ede0e --- /dev/null +++ b/tests/12_sv_pack2.c @@ -0,0 +1,21 @@ +#define DEBUG +#include +#include +#include +#include +#include +#include "../include/sv.h" + +int main(void){ + const char *s = "Hello"; + size_t offset = strlen(s) + sizeof(uint64_t); + unsigned char *buf = malloc(offset); + struct String_View sv = sv_strn(s, strlen(s)); + sv_pack(buf, sv); + + struct String_View out = sv_unpack(buf); + assert(out.len == strlen(s)); + assert( memcmp(out.buf, s, out.len) == 0); + free(buf); + return EXIT_SUCCESS; +}