commit 583975459fac6b641c2d4d5a2e1ce477c0ac0ff1 Author: Elaina Claus Date: Tue Jan 13 21:46:05 2026 -0500 first commit, initial layout and some ideas I want to implement, included my linked list impl diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..950b750 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +build/ +*.o +*.a +*.d +*.log +*.tmp +.DS_Store \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..d06787a --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,16 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [], + "compilerPath": "/usr/bin/gcc-14", + "cStandard": "c11", + "cppStandard": "c++11", + "intelliSenseMode": "linux-gcc-x64" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..5137304 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,37 @@ +{ + "tasks": [ + { + "type": "cppbuild", + "label": "C/C++: gcc-14 build active file", + "command": "/usr/bin/gcc-14", + "args": [ + "-fdiagnostics-color=always", + "-g3", + "-O0", + "-std=c11", + "-Wall", + "-Wextra", + "-Wpedantic", + "-Wshadow", + "-Wconversion", + "-Wstrict-prototypes", + "-Wmissing-prototypes", + "-D_POSIX_C_SOURCE=200809L", + "-Iinclude", + "${file}", + "-o", + "${fileDirname}/${fileBasenameNoExtension}", + "./build/lib/libnickel.a" + ], + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "Task generated by Debugger." + } + ], + "version": "2.0.0" +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..20c68e6 --- /dev/null +++ b/Makefile @@ -0,0 +1,3 @@ +include make/config.mk +include make/toolchain.mk +include make/rules.mk \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1f008e4 --- /dev/null +++ b/README.md @@ -0,0 +1,122 @@ +### Core “make C nicer” utilities + +1. **`xmalloc/xcalloc/xrealloc/xstrdup` (header: `xalloc.h`)** + + * Teaches: error handling patterns, `errno`, consistent OOM behavior. + +2. **Arena allocator (header: `arena.h`)** + + * `arena_init`, `arena_alloc`, `arena_reset`, `arena_free` + * Teaches: alignment, bump allocators, lifetime-based allocation. + +3. **Dynamic buffer / string builder (header: `buf.h`)** + + * `buf_reserve`, `buf_append`, `buf_appendf` + * Teaches: amortized growth, `vsnprintf`, safe formatting. + +4. **Slice + spans (header: `span.h`)** + + * `typedef struct { uint8_t* p; size_t n; } span_u8;` etc. + * Teaches: safer APIs than raw pointers, length-carrying. + +5. **Hash map (header: `hashmap.h`)** (string→void* or string→u64) + + * Teaches: hashing, open addressing, tombstones, resizing. + +6. **Bitset (header: `bitset.h`)** + + * `bitset_set/clear/test`, `bitset_find_first_zero` + * Teaches: word operations, popcount/ctz if you want. + +### C11 correctness features + +7. **Compile-time assertions & type checks (header: `ctassert.h`)** + + * Wrap `_Static_assert`, plus some `_Generic` helpers. + * Teaches: compile-time constraints, “making invalid states unrepresentable”. + +8. **`_Generic` logging macros (header: `log.h`)** + + * `LOG_INFO("x=%d", x)` with file/line, optional type-based formatting helpers. + * Teaches: variadic macros, `_Generic`, ergonomics. + +9. **Defer/cleanup pattern (header: `defer.h`)** + + * Macro that runs cleanup at scope end (via `goto` pattern). + * Teaches: structured cleanup in C, error paths. + +10. **Optional result type (header: `result.h`)** + +* `typedef struct { int ok; int err; T value; } result_T;` pattern. +* Teaches: explicit error handling, no hidden global state. + +### Concurrency & atomics (C11’s “real” new power) + +11. **Atomic reference counting (header: `refcnt.h`)** + +* `ref_inc/ref_dec` with destructor callback. +* Teaches: `stdatomic.h`, memory ordering (start with seq_cst, then learn relax/acq_rel). + +12. **Lock-free SPSC ring buffer (header: `ring_spsc.h`)** + +* Single-producer/single-consumer queue. +* Teaches: atomics, cache friendliness, correctness reasoning. + +13. **Thread pool (header: `threadpool.h`)** using `threads.h` + +* `tp_init`, `tp_submit`, `tp_join`, `tp_destroy` +* Teaches: `thrd_t`, `mtx_t`, `cnd_t`, work queues. + +14. **Once-init & singletons (header: `once.h`)** + +* Use `once_flag` / `call_once` (or implement if platform lacks). +* Teaches: init races, safe global setup. + +### Parsing / CLI tools (you’ll actually use these) + +15. **Argument parser (header: `argparse.h`)** + +* `--long`, `-s`, combined short flags, `--key=value` +* Teaches: string parsing, API design, test cases. + +16. **INI parser (header: `ini.h`)** + +* Minimal: sections, key=value, comments. +* Teaches: parsing state machines, callbacks. + +17. **CSV reader/writer (header: `csv.h`)** + +* Correct quoting/escaping. +* Teaches: edge cases, streaming parsing. + +18. **Path utilities (header: `path.h`)** + +* `path_join`, `path_dirname`, `path_basename`, normalize `..` and `.` +* Teaches: portability pitfalls, careful string ops. + +### Systems-ish building blocks + +19. **File mapping / buffered reader (header: `io.h`)** + +* Portable-ish wrapper for “read whole file”, “iter lines”, “atomic write via temp+rename”. +* Teaches: robust file I/O patterns, error handling. + +20. **Timing + profiling helpers (header: `timeutil.h`)** + +* `now_ns()`, `stopwatch`, `scope_timer` macro +* Teaches: `timespec_get`, measurement pitfalls, microbenchmark hygiene. + +--- + +## A suggested “crash course” order (so it builds) + +1–3 (xalloc/arena/buf) → 7–10 (static assert, generic, cleanup, results) → 15 (argparse) → 19 (io) → 16–18 (parsers) → 11–14 (atomics/threads) → 5–6 (hashmap/bitset) → 20 (timing) → 12/13 (ring + threadpool, as capstone) + +--- + +## Two capstone tool ideas that use a lot of the above + +* **`logscan`**: fast log parser + filter + histogram (buf + argparse + csv + hashmap + timeutil) +* **`dedupe`**: file deduplicator by hashing chunks (io + hashmap + arena + threadpool) + +--- diff --git a/include/nickel/arena.h b/include/nickel/arena.h new file mode 100644 index 0000000..e69de29 diff --git a/include/nickel/argparse.h b/include/nickel/argparse.h new file mode 100644 index 0000000..e69de29 diff --git a/include/nickel/bitset.h b/include/nickel/bitset.h new file mode 100644 index 0000000..e69de29 diff --git a/include/nickel/buf.h b/include/nickel/buf.h new file mode 100644 index 0000000..e69de29 diff --git a/include/nickel/ctassert.h b/include/nickel/ctassert.h new file mode 100644 index 0000000..e69de29 diff --git a/include/nickel/defer.h b/include/nickel/defer.h new file mode 100644 index 0000000..e69de29 diff --git a/include/nickel/hashmap.h b/include/nickel/hashmap.h new file mode 100644 index 0000000..e69de29 diff --git a/include/nickel/ini.h b/include/nickel/ini.h new file mode 100644 index 0000000..e69de29 diff --git a/include/nickel/io.h b/include/nickel/io.h new file mode 100644 index 0000000..e69de29 diff --git a/include/nickel/linkedlist.h b/include/nickel/linkedlist.h new file mode 100644 index 0000000..5256d0c --- /dev/null +++ b/include/nickel/linkedlist.h @@ -0,0 +1,62 @@ +#include +#include + +#ifndef __NICKEL_LINKEDLIST_H_ +#define __NICKEL_LINKEDLIST_H_ + +typedef struct ni_list_node { + struct ni_list_node* next; + struct ni_list_node* prev; +} ni_list_node; + +// sentinel type structure for head + count +typedef struct ni_list { + ni_list_node head; + size_t count; +} ni_list; + +// convenience ptr wrapper struct for a 'generic' list of pointers to things +typedef struct ni_list_ptr { + ni_list_node link; + void* ptr; +} ni_list_ptr; + +// List functions +void ni_list__init(ni_list* l); +bool ni_list__is_empty(ni_list* l); + +void ni_list__insert_after(ni_list* l, ni_list_node* entry, ni_list_node* n); +void ni_list__insert_before(ni_list* l, ni_list_node* entry, ni_list_node* n); + +void ni_list__push_front(ni_list* l, ni_list_node* n); +void ni_list__push_back(ni_list* l, ni_list_node* n); + +void ni_list__remove(ni_list* l, ni_list_node* n); + +static inline ni_list_node* ni_list__get_front(ni_list* l) { + // if l is_empty() front == NULL else return the first actual data baring node + return ni_list__is_empty(l) ? NULL : l->head.next; +} + +static inline ni_list_node* ni_list__get_back(ni_list* l) { + // if l is_empty() front == NULL else return the last node in the list + return ni_list__is_empty(l) ? NULL : l->head.prev; +} + +/* Iter Helpers */ + +#define NI_LIST__FOREACH(lptr) \ + for (ni_list_node* it = ni_list__get_front(lptr); it != &(lptr)->head; it = it->next) + +#define NI_LIST_FOREACH_SAFE(tmp, lptr) \ + for (ni_list_node* it = ni_list__get_front(lptr), *tmp = it->next; \ + it != &(lptr)->head; \ + it = tmp, tmp = it-> next) + +// create a pointer to struct that contains a ni_list_node member +// since we know the structure size at compile time we can do some math +// to retreive the base address of the structure that contains our list node. +#define NI_LIST_CONTAINER_OF(ptr, type, member) \ + ((type*)((uint8_t*)(ptr) - offsetof(type, member))) + +#endif /* __NICKEL_LINKEDLIST_H_ */ \ No newline at end of file diff --git a/include/nickel/log.h b/include/nickel/log.h new file mode 100644 index 0000000..e69de29 diff --git a/include/nickel/refcnt.h b/include/nickel/refcnt.h new file mode 100644 index 0000000..e69de29 diff --git a/include/nickel/result.h b/include/nickel/result.h new file mode 100644 index 0000000..e69de29 diff --git a/include/nickel/ring_spsc.h b/include/nickel/ring_spsc.h new file mode 100644 index 0000000..e69de29 diff --git a/include/nickel/span.h b/include/nickel/span.h new file mode 100644 index 0000000..e69de29 diff --git a/include/nickel/threadpool.h b/include/nickel/threadpool.h new file mode 100644 index 0000000..e69de29 diff --git a/include/nickel/timeutil.h b/include/nickel/timeutil.h new file mode 100644 index 0000000..e69de29 diff --git a/include/nickel/version.h b/include/nickel/version.h new file mode 100644 index 0000000..e69de29 diff --git a/include/nickel/xalloc.h b/include/nickel/xalloc.h new file mode 100644 index 0000000..e69de29 diff --git a/make/config.mk b/make/config.mk new file mode 100644 index 0000000..3ec0ec8 --- /dev/null +++ b/make/config.mk @@ -0,0 +1,7 @@ +# Project identity +PROJ := nickel +BUILD_DIR := build +INC_DIRS := include + +# Default build mode: release | debug | asan | ubsan +MODE ?= debug diff --git a/make/rules.mk b/make/rules.mk new file mode 100644 index 0000000..fcc2429 --- /dev/null +++ b/make/rules.mk @@ -0,0 +1,48 @@ +LIB := $(BUILD_DIR)/lib/libnickel.a +BIN_DIR := $(BUILD_DIR)/bin +OBJ_DIR := $(BUILD_DIR)/obj + +NICKEL_SRCS := $(wildcard src/nickel/*.c) +NICKEL_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,$(NICKEL_SRCS)) + +TEST_SRCS := $(wildcard tests/*.c) +TEST_BIN := $(BIN_DIR)/tests + +TOOL_BINS := \ + #$(BIN_DIR)/logscan \ + #$(BIN_DIR)/dedupe + +.PHONY: all clean test tools examples + +all: $(LIB) tools $(TEST_BIN) + +$(LIB): $(NICKEL_OBJS) + @mkdir -p $(dir $@) + $(AR) rcs $@ $^ + $(RANLIB) $@ + +$(OBJ_DIR)/%.o: src/%.c + @mkdir -p $(dir $@) + $(CC) $(CFLAGS) -c $< -o $@ + +# Tests link against libnickel.a +$(TEST_BIN): $(TEST_SRCS) $(LIB) + @mkdir -p $(BIN_DIR) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(TEST_SRCS) $(LIB) $(LDLIBS) + +# Tools (one main.c each; expand as needed) +#$(BIN_DIR)/logscan: tools/logscan/main.c $(LIB) +# @mkdir -p $(BIN_DIR) +# $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIB) $(LDLIBS) + +#$(BIN_DIR)/dedupe: tools/dedupe/main.c $(LIB) +# @mkdir -p $(BIN_DIR) +# $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIB) $(LDLIBS) + +test: $(TEST_BIN) + $(TEST_BIN) + +tools: $(TOOL_BINS) + +clean: + rm -rf $(BUILD_DIR) diff --git a/make/toolchain.mk b/make/toolchain.mk new file mode 100644 index 0000000..e2f014e --- /dev/null +++ b/make/toolchain.mk @@ -0,0 +1,31 @@ +CC ?= cc +AR ?= ar +RANLIB ?= ranlib + +CSTD := -std=c11 +WARN := -Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wstrict-prototypes -Wmissing-prototypes +DEFS := -D_POSIX_C_SOURCE=200809L + +INCS := $(addprefix -I,$(INC_DIRS)) + +CFLAGS_COMMON := $(CSTD) $(WARN) $(DEFS) $(INCS) +LDFLAGS_COMMON := +LDLIBS_COMMON := + +ifeq ($(MODE),release) + CFLAGS_MODE := -O2 -DNDEBUG +else ifeq ($(MODE),debug) + CFLAGS_MODE := -O0 -g3 +else ifeq ($(MODE),asan) + CFLAGS_MODE := -O1 -g3 -fsanitize=address -fno-omit-frame-pointer + LDFLAGS_MODE := -fsanitize=address +else ifeq ($(MODE),ubsan) + CFLAGS_MODE := -O1 -g3 -fsanitize=undefined -fno-omit-frame-pointer + LDFLAGS_MODE := -fsanitize=undefined +else + $(error Unknown MODE=$(MODE)) +endif + +CFLAGS := $(CFLAGS_COMMON) $(CFLAGS_MODE) +LDFLAGS := $(LDFLAGS_COMMON) $(LDFLAGS_MODE) +LDLIBS := $(LDLIBS_COMMON) \ No newline at end of file diff --git a/src/nickel/linkedlist.c b/src/nickel/linkedlist.c new file mode 100644 index 0000000..bd35c92 --- /dev/null +++ b/src/nickel/linkedlist.c @@ -0,0 +1,67 @@ +#include +#include +#include +#include + +#include "nickel/linkedlist.h" + +void ni_list__init(ni_list* l) +{ + l->head.next = &l->head; + l->head.prev = &l->head; + l->count = 0; +} + +bool ni_list__is_empty(ni_list* l) { + // only accept if head.next is head (circular head) + // as the indicator of empty-ness. + return l->head.next == &l->head; +} + +void ni_list__insert_after(ni_list* l, ni_list_node* entry, ni_list_node* n) { + // setup our incomming node as the next node after the insert point + n->next = entry->next; + n->prev = entry; + + // update the following node to point at our new next node + entry->next->prev = n; + + // update the insert point to point at the new node + entry->next = n; + l->count++; +} + +void ni_list__insert_before(ni_list* l, ni_list_node* entry, ni_list_node* n) { + // setup our incomming node as the next node before the insert point + n->prev = entry->prev; + n->next = entry; + + // update the previous node to point at our new next node + entry->prev->next = n; + + // update our insert point with the new prev + entry->prev = n; + l->count++; +} + +void ni_list__push_front(ni_list* l, ni_list_node* n) { + ni_list__insert_after(l, &l->head, n); +} + +void ni_list__push_back(ni_list* l, ni_list_node* n) { + ni_list__insert_before(l, &l->head, n); +} + +void ni_list__remove(ni_list* l, ni_list_node* n) { + // set node before our node to point back to the node after n. + n->prev->next = n->next; + + // set node after our node to point back to the node before n + n->next->prev = n->prev; + + // poison our removed node + n->next = NULL; + n->prev = NULL; + + l->count--; +} \ No newline at end of file diff --git a/tests/test_linkedlist b/tests/test_linkedlist new file mode 100755 index 0000000..710b6bb Binary files /dev/null and b/tests/test_linkedlist differ diff --git a/tests/test_linkedlist.c b/tests/test_linkedlist.c new file mode 100644 index 0000000..a6fbb02 --- /dev/null +++ b/tests/test_linkedlist.c @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include + +#include "nickel/linkedlist.h" + +// helper function to format bytes out. +char* get_bytes_f(long bytes, char* str) { + if(str != NULL) { + if (bytes > 0) + { + if (bytes < 1024) { + sprintf(str, "%ld bytes", bytes); + } + if ((bytes >= 1024) & (bytes < 1024*1024)) { + sprintf(str, "%.3f Kbytes", ((double)bytes/(double)1024.00)); + } + if (bytes >= 1024*1024) { + sprintf(str, "%.3f Mbytes",(double)bytes/(double)(1024*1024)); + } + } + } + else { + return NULL; + } + return str; +} + +typedef struct test_data { + uint64_t data1; + uint64_t data2; + uint64_t data3; + uint64_t data4; + + ni_list_node link; +} test_data; + +int main (void) +{ + time_t time_now; + srand(time(&time_now)); + + ni_list data_instances; + ni_list__init(&data_instances); + + printf("list is allocated at %p\n", (void*)&data_instances); + size_t test_size = 10; + for(size_t i = 0; i < test_size; i++) { + test_data* t = malloc(sizeof(test_data)); + printf("n = %d allocated data...link at %p...\n", i, &t->link); + + t->data1 = rand() % UINT64_MAX; + t->data2 = rand() % UINT64_MAX; + t->data3 = rand() % UINT64_MAX; + t->data4 = rand() % UINT64_MAX; + + ni_list__push_back(&data_instances, &(t->link)); + + printf("write: n = %d: %ld %ld %ld %ld \n\tnext: %p\n\tprev: %p\n", + i, t->data1, t->data2, t->data3, t->data4, t->link.next, t->link.prev); + } + + printf("Inserted %d count entries into test list...\n", data_instances.count); + + { // scope for access to iter and list + size_t i = 0; + NI_LIST__FOREACH(&data_instances) { + test_data* t = NI_LIST_CONTAINER_OF(it, test_data, link); + printf("read: %d: %ld %ld %ld %ld \n\tnext: %p\n\tprev: %p\n", + i, t->data1, t->data2, t->data3, t->data4, (void*)it->next, (void*)it->prev); + i++; + } + } + + while (!ni_list__is_empty(&data_instances)) { + ni_list_node* tail = ni_list__get_back(&data_instances); + ni_list__remove(&data_instances, tail); + + test_data* t = NI_LIST_CONTAINER_OF(tail, test_data, link); + free(t); + } + + /* all done! */ +} \ No newline at end of file