first commit, initial layout and some ideas I want to implement, included my linked list impl

This commit is contained in:
2026-01-13 21:46:05 -05:00
commit 583975459f
30 changed files with 487 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
build/
*.o
*.a
*.d
*.log
*.tmp
.DS_Store

16
.vscode/c_cpp_properties.json vendored Normal file
View File

@@ -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
}

37
.vscode/tasks.json vendored Normal file
View File

@@ -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"
}

3
Makefile Normal file
View File

@@ -0,0 +1,3 @@
include make/config.mk
include make/toolchain.mk
include make/rules.mk

122
README.md Normal file
View File

@@ -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 (C11s “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 (youll 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)
13 (xalloc/arena/buf) → 710 (static assert, generic, cleanup, results) → 15 (argparse) → 19 (io) → 1618 (parsers) → 1114 (atomics/threads) → 56 (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)
---

0
include/nickel/arena.h Normal file
View File

View File

0
include/nickel/bitset.h Normal file
View File

0
include/nickel/buf.h Normal file
View File

View File

0
include/nickel/defer.h Normal file
View File

0
include/nickel/hashmap.h Normal file
View File

0
include/nickel/ini.h Normal file
View File

0
include/nickel/io.h Normal file
View File

View File

@@ -0,0 +1,62 @@
#include <stdlib.h>
#include <stdbool.h>
#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_ */

0
include/nickel/log.h Normal file
View File

0
include/nickel/refcnt.h Normal file
View File

0
include/nickel/result.h Normal file
View File

View File

0
include/nickel/span.h Normal file
View File

View File

View File

0
include/nickel/version.h Normal file
View File

0
include/nickel/xalloc.h Normal file
View File

7
make/config.mk Normal file
View File

@@ -0,0 +1,7 @@
# Project identity
PROJ := nickel
BUILD_DIR := build
INC_DIRS := include
# Default build mode: release | debug | asan | ubsan
MODE ?= debug

48
make/rules.mk Normal file
View File

@@ -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)

31
make/toolchain.mk Normal file
View File

@@ -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)

67
src/nickel/linkedlist.c Normal file
View File

@@ -0,0 +1,67 @@
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#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--;
}

BIN
tests/test_linkedlist Executable file

Binary file not shown.

87
tests/test_linkedlist.c Normal file
View File

@@ -0,0 +1,87 @@
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#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! */
}