aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbonmas14 <bonmas14@gmail.com>2025-08-02 23:20:37 +0000
committerbonmas14 <bonmas14@gmail.com>2025-08-02 23:20:37 +0000
commit1cf89852f951b59b89f2a8bd7b54a0b0b74d439c (patch)
tree884af08903beba5f0e1e8435df4a1c7015270487
downloadungrateful-1cf89852f951b59b89f2a8bd7b54a0b0b74d439c.tar.gz
ungrateful-1cf89852f951b59b89f2a8bd7b54a0b0b74d439c.zip
memory manipulation, strings, allocators list and logger.
-rw-r--r--.gitignore2
-rw-r--r--LICENCE19
-rw-r--r--README.md20
-rwxr-xr-xbuild.sh49
-rw-r--r--src/un_list.c83
-rw-r--r--src/un_log.c71
-rw-r--r--src/un_memory.c229
-rw-r--r--src/un_strings.c381
-rw-r--r--src/ungrateful.c6
-rw-r--r--src/ungrateful.h202
-rw-r--r--tests/allocs.c39
-rw-r--r--tests/hello_world.c7
-rw-r--r--tests/lists.c50
-rw-r--r--tests/memctl.c42
-rw-r--r--tests/strings.c103
15 files changed, 1303 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..85d0bbc
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+bin/
+lib/
diff --git a/LICENCE b/LICENCE
new file mode 100644
index 0000000..ed8f1f8
--- /dev/null
+++ b/LICENCE
@@ -0,0 +1,19 @@
+Copyright (C) 2025 Bogdan Masyutin (bonmas14)
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+Bogdan Masyutin - bonmas14@gmail.com
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3d40b34
--- /dev/null
+++ b/README.md
@@ -0,0 +1,20 @@
+# Ungrateful
+
+C99 standard library for game development.
+
+# Note:
+
+Only core features of C99 were used:
+
+- stdint;
+- normal comments;
+- no implicit function declarations and int;
+- long long;
+- restrict keyword;
+
+# TODO:
+
+- File IO.
+- Memory allocation.
+- Threading.
+- Strings.
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..4cf5e42
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,49 @@
+#!/bin/env bash
+
+cc="gcc"
+ld="gcc"
+
+rm -rf ./lib/
+rm -rf ./bin/
+mkdir ./lib/
+mkdir ./bin/
+
+echo "[BUILD] entry.c"
+
+cflags="-std=c99 -Wall -Wextra -g -Wno-error -pedantic"
+$cc $cflags \
+ -c -o lib/ungrateful.o \
+ -g src/ungrateful.c
+
+if [[ $? -ne 0 ]]; then
+ exit
+fi
+
+if [[ $1 == "no_tests" ]]; then
+ exit
+fi
+
+echo
+
+for test in tests/*.c; do
+ fname=$(basename -- "$test")
+ fname="${fname%.*}"
+
+ echo "[BUILD] $test"
+
+ $cc $cflags -o bin/$fname $test lib/ungrateful.o -Isrc/
+done
+
+for case in bin/*; do
+ if [[ $1 == "quiet" ]]; then
+ $case > /dev/null 2>&1
+ else
+ $case
+ fi
+
+ if [[ $? -eq 0 ]]; then
+ echo "[DONE] $case"
+ else
+ echo "[FAIL] $case"
+ fi
+done
diff --git a/src/un_list.c b/src/un_list.c
new file mode 100644
index 0000000..a670de0
--- /dev/null
+++ b/src/un_list.c
@@ -0,0 +1,83 @@
+List un_list_create(u64 start_capacity, u64 element_size, Allocator alloc) {
+ List list;
+
+ list.count = 0;
+ list.capacity = start_capacity;
+ list.element_size = element_size;
+ list.alloc = alloc;
+
+ list.data = un_memory_alloc(list.capacity * list.element_size, alloc);
+
+ assert(list.data != NULL);
+ return list;
+}
+
+void un_list_destroy(List *list) {
+ un_memory_destroy(&list->alloc);
+ UN_CLEAR(*list);
+}
+
+List un_list_clone(List *list, Allocator alloc) {
+ List result;
+
+ result = *list;
+ result.alloc = alloc;
+ result.data = un_memory_alloc(result.capacity * result.element_size, alloc);
+ assert(result.data != NULL);
+
+ un_memory_copy(result.data, list->data, result.count * result.element_size);
+
+ return result;
+}
+
+static b32 list_grow_if_needed(List *list) {
+ if ((list->count + 1) <= list->capacity) {
+ return true;
+ } else {
+ void *mem = un_memory_realloc(list->data, list->element_size * list->capacity * 2, list->alloc);
+
+ if (!mem) {
+ return false;
+ }
+
+ list->data = mem;
+ list->capacity *= 2;
+ }
+
+ return true;
+}
+
+void un_list_append(List *list, void *data) {
+ void *addr;
+
+ if (list_grow_if_needed(list)) {
+ addr = (u8*)list->data + list->count * list->element_size;
+ un_memory_copy(addr, data, list->element_size);
+ list->count++;
+ } else {
+ un_log_write_cstring(UN_LOG_ERROR, UN_CSTR "Failed to grow list");
+ }
+}
+
+void *un_list_get(List *list, u64 index) {
+ if (index >= list->count) return NULL;
+
+ return (u8*)list->data + index * list->element_size;
+}
+
+void un_list_remove(List *list, u64 index) {
+ void *addr;
+ u64 move_elements;
+
+ if (index >= list->count) return;
+
+ addr = (u8*)list->data + index * list->element_size;
+
+ move_elements = list->count - (index + 1);
+
+ if (move_elements) {
+ un_memory_move(addr, (u8*)addr + list->element_size, move_elements * list->element_size);
+ }
+
+ list->count--;
+}
diff --git a/src/un_log.c b/src/un_log.c
new file mode 100644
index 0000000..ef5bb02
--- /dev/null
+++ b/src/un_log.c
@@ -0,0 +1,71 @@
+#include <stdio.h>
+
+Log_Level un_current_log_level = UN_LOG_INFO;
+
+u8 log_buffer[UN_KB(4)];
+
+void un_log_write_internal(Log_Level level, String format, va_list vaptr) {
+ if (level < un_current_log_level) return;
+
+ switch (level) {
+ case UN_LOG_TRACE:
+ fprintf(stderr, "[TRACE] ");
+ break;
+ case UN_LOG_DEBUG:
+ fprintf(stderr, "[DEBUG] ");
+ break;
+ case UN_LOG_INFO:
+ fprintf(stderr, "[INFO] ");
+ break;
+ case UN_LOG_WARNING:
+ fprintf(stderr, "[WARNING] ");
+ break;
+ case UN_LOG_ERROR:
+ fprintf(stderr, "[ERROR] ");
+ break;
+ case UN_LOG_FATAL:
+ fprintf(stderr, "[FATAL] ");
+ break;
+ default:
+ break;
+ }
+
+ sprintf(CSTR log_buffer, "%.*s", (int)format.size, format.data);
+ vfprintf(stderr, CSTR log_buffer, vaptr);
+
+ switch (level) {
+ case UN_LOG_RAW:
+ break;
+ case UN_LOG_FATAL:
+ fprintf(stderr, "\n");
+ assert(false);
+ break;
+ default:
+ fprintf(stderr, "\n");
+ break;
+ }
+}
+
+extern void un_log_write_cstring(Log_Level level, u8 *format, ...) {
+ va_list vaptr;
+ String temp;
+
+ if (level < un_current_log_level) return;
+
+ temp.size = un_string_get_length(format);
+ temp.data = format;
+
+ va_start(vaptr, format);
+ un_log_write_internal(level, temp, vaptr);
+ va_end(vaptr);
+}
+
+void un_log_write(Log_Level level, String format, ...) {
+ va_list vaptr;
+
+ if (level < un_current_log_level) return;
+
+ va_start(vaptr, format);
+ un_log_write_internal(level, format, vaptr);
+ va_end(vaptr);
+}
diff --git a/src/un_memory.c b/src/un_memory.c
new file mode 100644
index 0000000..9ff316b
--- /dev/null
+++ b/src/un_memory.c
@@ -0,0 +1,229 @@
+#include <stdlib.h>
+
+/// -------- Basic functions
+
+void un_memory_set(u8 *buffer, u8 value, u64 size) {
+ if (size == 0) return;
+ assert(buffer != NULL);
+
+ while (size-- > 0) {
+ *buffer++ = value;
+ }
+}
+
+void un_memory_copy(u8 *dest, u8 *source, u64 size) {
+ if (size == 0) return;
+
+ assert(dest != NULL);
+ assert(source != NULL);
+
+#ifdef DEBUG
+
+ if (dest < source) {
+ assert(((ptrdiff_t)source - (ptrdiff_t)dest) >= (ptrdiff_t)size);
+ } else {
+ assert(((ptrdiff_t)dest - (ptrdiff_t)source) >= (ptrdiff_t)size);
+ }
+#endif // DEBUG
+
+ while (size-- > 0) {
+ *dest++ = *source++;
+ }
+}
+
+s32 un_memory_compare(u8 *left, u8 *right, u64 size) {
+ while (size-- > 0) {
+ if (*left++ == *right++)
+ continue;
+
+ return left[-1] > right[-1] ? 1 : -1;
+ }
+
+ return 0;
+}
+
+void un_memory_move(u8 *dest, u8 *src, u64 size) {
+ Allocator talloc = un_allocator_get_temporary();
+
+ void *temp = un_memory_alloc(size, talloc);
+
+ un_memory_copy(temp, src, size);
+ un_memory_copy(dest, temp, size);
+}
+
+void *un_memory_alloc(u64 size, Allocator alloc) {
+ assert(alloc.proc != NULL);
+ return alloc.proc(NULL, size, UN_ALLOC_MSG_ALLOCATE, alloc.data);
+}
+
+void *un_memory_realloc(void *ptr, u64 size, Allocator alloc) {
+ assert(alloc.proc != NULL);
+ return alloc.proc(ptr, size, UN_ALLOC_MSG_REALLOCATE, alloc.data);
+}
+
+void *un_memory_free(void *ptr, Allocator alloc) {
+ assert(alloc.proc != NULL);
+ return alloc.proc(ptr, 0, UN_ALLOC_MSG_FREE, alloc.data);
+}
+
+void un_memory_destroy(Allocator *alloc) {
+ assert(alloc != NULL);
+ assert(alloc->proc != NULL);
+
+ alloc->proc(NULL, 0, UN_ALLOC_MSG_SELF_DELETE, alloc->data);
+
+ UN_CLEAR(*alloc);
+}
+
+/// -------- Stdlib allocator
+
+static ALLOCATOR_PROC_SIGNATURE(un_std_alloc_proc) {
+ UNUSED(data);
+
+ switch (message) {
+ case UN_ALLOC_MSG_ALLOCATE:
+ return calloc(1, size);
+ case UN_ALLOC_MSG_REALLOCATE:
+ return realloc(p, size);
+ case UN_ALLOC_MSG_FREE:
+ free(p);
+ break;
+ case UN_ALLOC_MSG_SELF_DELETE: break;
+ }
+
+ return NULL;
+}
+
+Allocator un_allocator_get_standard(void) {
+ return CLITERAL(Allocator) { .proc = un_std_alloc_proc };
+}
+
+/// -------- Temp allocator
+
+static struct {
+ u64 index;
+ u8 data[UN_TEMP_SIZE];
+} temp;
+
+static void temp_reset(void) {
+ temp.index = 0;
+}
+
+static void *temp_alloc(u64 size) {
+ if ((temp.index + size) > UN_TEMP_SIZE) {
+ un_log_write_cstring(UN_LOG_ERROR, UN_CSTR "Temp allocator wrapped!");
+ temp_reset();
+ }
+
+ if ((temp.index + size) > UN_TEMP_SIZE) {
+ un_log_write_cstring(UN_LOG_ERROR, UN_CSTR "Too much space requested!");
+ return NULL;
+ }
+
+ void *ptr = (u8*)temp.data + temp.index;
+ temp.index += size;
+ un_memory_set(ptr, 0x00, size);
+
+ return ptr;
+}
+
+static ALLOCATOR_PROC_SIGNATURE(un_temp_alloc_proc) {
+ UNUSED(data);
+ UNUSED(p);
+
+ switch (message) {
+ case UN_ALLOC_MSG_ALLOCATE:
+ return temp_alloc(size);
+ case UN_ALLOC_MSG_REALLOCATE:
+ case UN_ALLOC_MSG_FREE:
+ break;
+ case UN_ALLOC_MSG_SELF_DELETE: break;
+ }
+
+ return NULL;
+}
+
+Allocator un_allocator_get_temporary(void) {
+ return CLITERAL(Allocator) { .proc = un_temp_alloc_proc };
+}
+
+/// Arena allocator
+
+typedef struct Arena {
+ u64 size;
+ u64 occupied;
+ struct Arena *next;
+ u8 data[1];
+} Arena;
+
+static Arena *arena_create(u64 size) {
+ Allocator alloc;
+ Arena *arena;
+
+ alloc = un_allocator_get_standard();
+ arena = CLITERAL(Arena*)un_memory_alloc(size, alloc);
+
+ if (!arena) {
+ un_log_write_cstring(UN_LOG_FATAL, UN_CSTR "Buy mem, failed to create arena!");
+ return NULL;
+ }
+
+ UN_CLEAR(*arena);
+ arena->size = size - sizeof(Arena);
+ return arena;
+}
+
+static void arena_delete(Arena *arena) {
+ if (arena->next != NULL) {
+ arena_delete(arena->next);
+ }
+
+ Allocator alloc = un_allocator_get_standard();
+ un_memory_free(arena, alloc);
+}
+
+static void *arena_allocate(u64 size, Arena *arena) {
+ u8* pos;
+
+ if (size <= (arena->size - arena->occupied)) {
+ pos = arena->data + arena->occupied;
+ arena->occupied += size;
+ return (void*)pos;
+ }
+
+ if (!arena->next) {
+ arena->next = arena_create((arena->size + sizeof(Arena)) * 2LL);
+ }
+
+ return arena_allocate(size, arena->next);
+}
+
+static ALLOCATOR_PROC_SIGNATURE(un_arena_alloc_proc) {
+ assert(data != NULL);
+ UNUSED(p);
+
+ switch (message) {
+ case UN_ALLOC_MSG_ALLOCATE:
+ return arena_allocate(size, (Arena*)data);
+ case UN_ALLOC_MSG_REALLOCATE:
+ un_log_write_cstring(UN_LOG_ERROR, UN_CSTR "Arena doesn't reallocate.");
+ break;
+ case UN_ALLOC_MSG_FREE:
+ un_log_write_cstring(UN_LOG_ERROR, UN_CSTR "Arena doesn't free it's memory, please destroy arena itself.");
+ break;
+ case UN_ALLOC_MSG_SELF_DELETE:
+ arena_delete((Arena*)data);
+ break;
+ }
+
+ return NULL;
+}
+
+Allocator un_allocator_create_arena(u64 size) {
+ Allocator alloc = { 0 };
+
+ alloc.proc = un_arena_alloc_proc;
+ alloc.data = arena_create(size);
+
+ return alloc;
+}
diff --git a/src/un_strings.c b/src/un_strings.c
new file mode 100644
index 0000000..1499726
--- /dev/null
+++ b/src/un_strings.c
@@ -0,0 +1,381 @@
+u64 un_string_get_length(u8 *cstring) {
+ u8* start = cstring;
+
+ while (*start != '\0') {
+ start++;
+ }
+
+ return start - cstring;
+}
+
+String un_string_from_cstring(u8* cstring) {
+ String result;
+ u64 length = un_string_get_length(cstring);
+ assert(length < LLONG_MAX);
+ result.size = length;
+ result.data = cstring;
+ return result;
+}
+
+u8* un_string_to_cstring(String string, Allocator alloc) {
+ u8 *mem;
+
+ assert(string.size > 0);
+
+ mem = (u8*)un_memory_alloc(string.size + 1, alloc);
+ un_memory_copy(mem, string.data, (u64)string.size);
+
+ return mem;
+}
+
+String un_string_copy(String source, Allocator alloc) {
+ u8 *mem;
+ String result;
+
+ assert(source.size >= 0);
+
+ mem = (u8*)un_memory_alloc(source.size, alloc);
+ un_memory_copy(mem, source.data, (u64)source.size);
+
+ result.size = source.size;
+ result.data = mem;
+
+ return result;
+}
+
+s32 un_string_compare(String left, String right) {
+ s32 result;
+
+ if (left.size == right.size && left.size == 0) return 0;
+ if (left.size == 0) return -1;
+ if (right.size == 0) return 1;
+
+ result = un_memory_compare(left.data, right.data, left.size);
+
+ if (result == 0) {
+ if (left.size > right.size) return 1;
+ if (left.size < right.size) return -1;
+ return 0;
+ } else {
+ return result;
+ }
+}
+
+String un_string_concat(String left, String right, Allocator alloc) {
+ String result = { 0 };
+
+ assert(left.size >= 0 && right.size >= 0);
+
+ if (left.size == right.size && left.size == 0) {
+ return result;
+ }
+
+ u8* data = (u8*)un_memory_alloc(left.size + right.size, alloc);
+
+ if (left.size > 0) {
+ assert(left.data != NULL);
+ un_memory_copy(data, left.data, left.size);
+ }
+
+ if (right.size > 0) {
+ assert(right.data != NULL);
+ un_memory_copy((data + left.size), right.data, right.size);
+ }
+
+ result.size = left.size + right.size;
+ result.data = data;
+
+ return result;
+}
+
+String un_string_swap(String input, u8 from, u8 to, Allocator alloc) {
+ s64 i;
+ String output;
+
+ output = un_string_copy(input, alloc);
+
+ for (i = 0; i < input.size; i++) {
+ if (output.data[i] != from)
+ continue;
+
+ output.data[i] = to;
+ }
+
+ return output;
+}
+
+List un_string_split(String input, String pattern, Allocator alloc) {
+ List splits;
+ String string;
+ s64 i, start;
+ u64 matches, size;
+ u8 *buffer;
+
+ UN_CLEAR(splits);
+
+ if (input.size <= pattern.size) {
+ return splits;
+ }
+
+ if (pattern.size == 0) {
+ return splits;
+ }
+
+ for (i = 0, matches = 1; i < (input.size - (pattern.size - 1)); i++) {
+ if (un_memory_compare(input.data + i, pattern.data, pattern.size) != 0) {
+ continue;
+ }
+
+ matches++;
+ }
+
+ splits = un_list_create(matches, sizeof(String), alloc);
+
+ start = 0;
+
+ for (i = 0; i < (input.size - (pattern.size - 1)); i++) {
+ if (un_memory_compare(input.data + i, pattern.data, pattern.size) != 0) {
+ continue;
+ }
+
+ size = i - start;
+
+ if (size == 0) {
+ start = i + pattern.size;
+ continue;
+ }
+
+ buffer = (u8*)un_memory_alloc(size, alloc);
+ un_memory_copy(buffer, input.data + start, size);
+ string = CLITERAL(String) { .size = size, .data = buffer };
+ un_list_append(&splits, &string);
+ start = i + pattern.size;
+ }
+
+ if (start != input.size - pattern.size) {
+ if (un_memory_compare(input.data + start, pattern.data, pattern.size) == 0) {
+ return splits;
+ }
+
+ size = input.size - start;
+ if (size == 0) {
+ return splits;
+ }
+
+ buffer = (u8*)un_memory_alloc(size, alloc);
+ un_memory_copy(buffer, input.data + start, size);
+ string = CLITERAL(String) { .size = size, .data = buffer };
+ un_list_append(&splits, &string);
+ }
+
+ return splits;
+}
+
+String un_string_join(List string_list, String separator, Allocator alloc) {
+ String cont, temp;
+ u64 i;
+ Allocator talloc;
+
+ UN_CLEAR(cont);
+
+ talloc = un_allocator_get_temporary();
+
+ assert(string_list.element_size == sizeof(String));
+
+
+ for (i = 0; i < string_list.count; i++) {
+ temp = *(String *)un_list_get(&string_list, i);
+
+ cont = un_string_concat(cont, temp, talloc);
+
+ if (i != (string_list.count - 1)) {
+ cont = un_string_concat(cont, separator, talloc);
+ }
+ }
+
+ return un_string_copy(cont, alloc);
+}
+
+String un_string_substring(String input, s64 start, s64 max_size) {
+ String slice;
+
+ assert(start >= 0);
+ assert(max_size > 0);
+
+ slice.size = UN_MIN(max_size, (input.size - (s64)max_size));
+ slice.data = input.data + start;
+
+
+ if (start > (input.size - max_size)) {
+ slice.size = UN_MIN(max_size, (input.size - (s64)max_size));
+ } else {
+ slice.size = max_size;
+ }
+
+ slice.data = input.data + start;
+ return slice;
+}
+
+s64 un_string_index_of(String input, u8 value, u64 skip_count) {
+ s64 i;
+
+ for (i = 0; i < input.size; i++) {
+ if (input.data[i] != value) {
+ continue;
+ }
+
+ if (skip_count) {
+ skip_count--;
+ } else {
+ return i;
+ }
+ }
+
+ return -1;
+}
+extern s64 un_string_index_of_last(String input, u8 value) {
+ s64 i, index;
+
+ index = -1;
+
+ for (i = 0; i < input.size; i++) {
+ if (input.data[i] == value) index = i;
+ }
+
+ return index;
+}
+
+static String format_u64(u64 value) {
+ String output;
+ u64 l, r, size;
+ u8 t;
+ u8 buffer[32];
+
+ size = 0;
+
+ if (value == 0) {
+ return UN_STR("0");
+ }
+
+ while (value) {
+ buffer[size++] = '0' + (value % 10);
+ value /= 10;
+ }
+
+ assert(size <= 20);
+
+ for (l = 0, r = size - 1; l < r; l++, r--) {
+ t = buffer[l];
+ buffer[l] = buffer[r];
+ buffer[r] = t;
+ }
+
+ output.size = size;
+ output.data = buffer;
+ return un_string_copy(output, un_allocator_get_temporary());
+}
+
+static String format_s64(s64 value) {
+ String output;
+ u64 l, r, size;
+ b32 negative;
+ u8 t;
+ u8 buffer[32];
+
+ size = 0;
+
+ if (value == 0) {
+ return UN_STR("0");
+ }
+
+ negative = false;
+
+ if (value < 0) {
+ value = -value;
+ negative = true;
+ }
+
+ while (value) {
+ buffer[size++] = '0' + (value % 10);
+ value /= 10;
+ }
+
+ if (negative) {
+ buffer[size++] = '-';
+ }
+
+ assert(size <= 20);
+
+ for (l = 0, r = size - 1; l < r; l++, r--) {
+ t = buffer[l];
+ buffer[l] = buffer[r];
+ buffer[r] = t;
+ }
+
+ output.size = size;
+ output.data = buffer;
+ return un_string_copy(output, un_allocator_get_temporary());
+}
+
+String un_string_format(Allocator alloc, String buffer, ...) {
+ va_list args;
+ String s;
+ List output;
+ s64 i, j;
+
+ Allocator talloc = un_allocator_get_temporary();
+ output = un_list_create(UN_KB(1), sizeof(u8), talloc);
+
+ va_start(args, buffer);
+
+ for (i = 0; i < buffer.size; i++) {
+ if (buffer.data[i] != '%') {
+ un_list_append(&output, buffer.data + i);
+ continue;
+ }
+
+ switch (buffer.data[++i]) {
+ case '%':
+ {
+ un_list_append(&output, buffer.data + i);
+ } break;
+ case 'c':
+ {
+ u32 b = va_arg(args, u32);
+ un_list_append(&output, &b);
+ } break;
+
+ case 'u':
+ {
+ s = format_u64(va_arg(args, u64));
+ for (j = 0; j < s.size; j++) {
+ un_list_append(&output, s.data + j);
+ }
+ } break;
+
+ case 'd':
+ {
+ s = format_s64(va_arg(args, u64));
+ for (j = 0; j < s.size; j++) {
+ un_list_append(&output, s.data + j);
+ }
+ } break;
+
+ case 's':
+ {
+ s = va_arg(args, String);
+ for (j = 0; j < s.size; j++) {
+ un_list_append(&output, s.data + j);
+ }
+ } break;
+ default: break;
+ }
+ }
+
+ va_end(args);
+
+ s.size = (s64)output.count;
+ s.data = output.data;
+
+ return un_string_copy(s, alloc);
+}
diff --git a/src/ungrateful.c b/src/ungrateful.c
new file mode 100644
index 0000000..1ceefd6
--- /dev/null
+++ b/src/ungrateful.c
@@ -0,0 +1,6 @@
+#include "ungrateful.h"
+
+#include "un_memory.c"
+#include "un_strings.c"
+#include "un_list.c"
+#include "un_log.c"
diff --git a/src/ungrateful.h b/src/ungrateful.h
new file mode 100644
index 0000000..8d1e70e
--- /dev/null
+++ b/src/ungrateful.h
@@ -0,0 +1,202 @@
+#if !defined(UNGRATEFUL_H)
+# define UNGRATEFUL_H
+
+/*
+ Ungrateful - standard library for game development.
+
+ LICENSE:
+
+ Copyright (C) 2025 Bogdan Masyutin (bonmas14)
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+ Bogdan Masyutin - bonmas14@gmail.com
+*/
+
+#if defined(__clang__) || defined(__GNUC__)
+# define CLANG_COMPILER
+# define __TRAP() __builtin_trap()
+#elif _MSC_VER >= 1939
+# define MSVC_COMPILER
+# include <intrin.h>
+# define __TRAP() __debugbreak()
+#else
+# error "Unknown compiler"
+#endif
+
+#if defined(__cplusplus)
+# define CLITERAL(type) type
+#else
+# define CLITERAL(type) (type)
+#endif
+
+#if defined(_WIN32)
+# define OS_WINDOWS
+#elif defined(__linux__)
+# define OS_LINUX
+#else
+# error "unknown platform!"
+#endif
+
+#define UN_TEMP_SIZE UN_MB(50)
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <assert.h>
+
+#define UNUSED(x) (void)(x)
+
+#define UN_KB(s) ((u64)(s) * 1024LL)
+#define UN_MB(s) (UN_KB(s) * 1024LL)
+#define UN_GB(s) (UN_MB(s) * 1024LL)
+
+#define UN_MAX(a, b) (a) > (b) ? (a) : (b)
+#define UN_MIN(a, b) (a) < (b) ? (a) : (b)
+
+#define UN_CSTR (u8*)
+#define UN_STR(cstr) un_string_from_cstring(UN_CSTR cstr)
+#define CSTR (char*)
+
+#define UN_CLEAR(var) un_memory_set((void*)&var, 0, sizeof(var))
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+
+typedef uint64_t u64;
+typedef uint32_t u32;
+typedef uint16_t u16;
+typedef uint8_t u8;
+
+typedef int64_t s64;
+typedef int32_t s32;
+typedef int16_t s16;
+typedef int8_t s8;
+
+typedef int8_t b32;
+typedef int8_t b8;
+
+typedef float f32;
+typedef double f64;
+
+/* ---- Memory Allocators API ---- */
+
+typedef enum {
+ UN_ALLOC_MSG_ALLOCATE,
+ UN_ALLOC_MSG_REALLOCATE,
+ UN_ALLOC_MSG_FREE,
+ UN_ALLOC_MSG_SELF_DELETE,
+} Allocator_Message;
+
+#define ALLOCATOR_PROC_SIGNATURE(name)\
+ void *name(void *p, u64 size, Allocator_Message message, void *data)
+
+typedef ALLOCATOR_PROC_SIGNATURE(Allocator_Proc);
+
+typedef struct {
+ Allocator_Proc *proc;
+ void *data;
+} Allocator;
+
+// extern Allocator un_allocator_create_heap(s64 chunk_size); /* ... for large things */
+extern Allocator un_allocator_create_arena(u64 initial_size); /* Grouping allocator, that will recursively grow */
+
+extern Allocator un_allocator_get_standard(void);
+extern Allocator un_allocator_get_temporary(void);
+
+extern void *un_memory_alloc(u64 size, Allocator alloc);
+extern void *un_memory_realloc(void *ptr, u64 size, Allocator alloc);
+extern void *un_memory_free(void *ptr, Allocator alloc);
+extern void un_memory_destroy(Allocator *alloc);
+
+extern void un_memory_set(u8 *dest, u8 value, u64 size);
+extern void un_memory_copy(u8 *dest, u8 *src, u64 size);
+extern void un_memory_move(u8 *dest, u8 *src, u64 size);
+extern s32 un_memory_compare(u8 *left, u8 *right, u64 size); /* checks for every byte in arrays for condition: if left is bigger that right. */
+
+/* ---- Generic list structure ---- */
+
+typedef struct {
+ u64 count;
+ u64 capacity;
+ u64 element_size;
+ Allocator alloc;
+ void *data;
+} List;
+
+extern List un_list_create(u64 start_capacity, u64 element_size, Allocator alloc);
+extern void un_list_destroy(List *list);
+
+extern List un_list_clone(List *list, Allocator alloc);
+extern void un_list_append(List *list, void *data);
+extern void *un_list_get(List *list, u64 index);
+extern void un_list_remove(List *list, u64 index);
+
+/* ---- no-wide string API ---- */
+
+typedef struct {
+ s64 size;
+ u8 *data;
+} String;
+
+extern u64 un_string_get_length(u8 *cstring);
+
+extern String un_string_from_cstring(u8* cstring);
+extern u8* un_string_to_cstring(String string, Allocator alloc);
+
+extern String un_string_copy(String source, Allocator alloc);
+extern s32 un_string_compare(String left, String right);
+extern String un_string_concat(String left, String right, Allocator alloc);
+
+extern String un_string_swap(String input, u8 from, u8 to, Allocator alloc);
+extern List un_string_split(String input, String pattern, Allocator alloc);
+extern String un_string_join(List string_list, String separator, Allocator alloc);
+
+extern String un_string_substring(String input, s64 start, s64 max_size);
+
+extern s64 un_string_index_of(String input, u8 value, u64 skip_count);
+extern s64 un_string_index_of_last(String input, u8 value);
+
+extern String un_string_format(Allocator alloc, String buffer, ...);
+extern String un_string_tformat(String buffer, ...);
+
+/* ---- Logging API ---- */
+
+typedef enum {
+ UN_LOG_TRACE,
+ UN_LOG_DEBUG,
+ UN_LOG_INFO,
+ UN_LOG_WARNING,
+ UN_LOG_ERROR,
+ UN_LOG_FATAL,
+ UN_LOG_RAW
+} Log_Level;
+
+extern Log_Level un_current_log_level;
+
+extern void un_log_write(Log_Level level, String format, ...);
+extern void un_log_write_cstring(Log_Level level, u8 *format, ...);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif // UNGRATEFUL_H
diff --git a/tests/allocs.c b/tests/allocs.c
new file mode 100644
index 0000000..608d9f4
--- /dev/null
+++ b/tests/allocs.c
@@ -0,0 +1,39 @@
+#include <ungrateful.h>
+
+int main(void) {
+ u64 i, size, *value;
+ size = UN_KB(1);
+
+ { // std
+ Allocator std = un_allocator_get_standard();
+ void *mem = un_memory_alloc(size, std);
+
+ if (mem != NULL) {
+ un_memory_free(mem, std);
+ }
+ }
+
+ { // temp
+ Allocator temp = un_allocator_get_temporary();
+
+ u8 *mem = (u8*) un_memory_alloc(size, temp);
+ assert(mem != NULL);
+
+ for (i = 0; i < size; i++) {
+ mem[i] = 0xAB;
+ }
+
+ un_memory_destroy(&temp);
+ }
+
+ { // arena
+ Allocator arena = un_allocator_create_arena(size);
+
+ for (i = 0; i < 1000; i++) {
+ value = un_memory_alloc(8, arena);
+ *value = 0xAC;
+ }
+
+ un_memory_destroy(&arena);
+ }
+}
diff --git a/tests/hello_world.c b/tests/hello_world.c
new file mode 100644
index 0000000..78f6c29
--- /dev/null
+++ b/tests/hello_world.c
@@ -0,0 +1,7 @@
+#include <ungrateful.h>
+
+int main(void) {
+ un_log_write(UN_LOG_INFO, un_string_from_cstring(UN_CSTR "Hello world!"));
+
+ return 0;
+}
diff --git a/tests/lists.c b/tests/lists.c
new file mode 100644
index 0000000..a8627ac
--- /dev/null
+++ b/tests/lists.c
@@ -0,0 +1,50 @@
+#include <ungrateful.h>
+
+int main(void) {
+ u64 i, v, times, size;
+ List list;
+ Allocator alloc;
+
+ size = 16;
+ times = 4;
+
+ {
+ alloc = un_allocator_get_standard();
+ list = un_list_create(size, sizeof(u64), alloc);
+
+ assert(un_list_get(&list, 0) == NULL);
+
+ for (i = 0; i < size; i++) {
+ v = i * times;
+
+ un_list_append(&list, (void *)(&v));
+ }
+
+ assert(*(u64*)un_list_get(&list, size - 1) == ((size - 1) * times));
+
+ for (i = 0; i < size; i++) {
+ v = *(u64*)un_list_get(&list, i);
+ assert(v == i * times);
+ }
+
+ un_list_remove(&list, 0);
+ assert(*(u64*)un_list_get(&list, 0) == times);
+
+ un_list_remove(&list, list.count); // will be ignored
+ un_list_remove(&list, list.count - 1);
+
+ assert(list.count == (size - 2));
+
+ for (i = 0; i < size; i++) {
+ v = i * times;
+ un_list_append(&list, (void *)(&v));
+ }
+
+ assert(list.capacity == (size * 2));
+
+ un_list_destroy(&list);
+
+ assert(list.data == NULL);
+ }
+}
+
diff --git a/tests/memctl.c b/tests/memctl.c
new file mode 100644
index 0000000..1614442
--- /dev/null
+++ b/tests/memctl.c
@@ -0,0 +1,42 @@
+#include <ungrateful.h>
+
+#define SIZE UN_KB(1)
+
+u8 buffer_a[SIZE];
+u8 buffer_b[SIZE];
+
+int main(void) {
+ u64 i;
+ for (i = 0; i < SIZE; i++) {
+ buffer_a[i] += i << 2;
+ }
+
+ {
+ un_memory_copy(buffer_b, buffer_a, SIZE);
+
+ for (i = 0; i < SIZE; i++) {
+ assert(buffer_b[i] == buffer_a[i]);
+ }
+
+ assert(un_memory_compare(buffer_a, buffer_b, SIZE) == 0);
+
+ buffer_b[SIZE - 10] = 0;
+ assert(un_memory_compare(buffer_a, buffer_b, SIZE) == 1);
+ }
+
+ {
+ un_memory_move(buffer_a + SIZE / 4, buffer_a, SIZE / 2);
+
+ assert(un_memory_compare(buffer_b, buffer_a + SIZE / 4, SIZE / 2) == 0);
+ }
+
+ {
+ un_memory_set(buffer_a, 0, SIZE);
+
+ for (i = 0; i < SIZE; i++) {
+ assert(buffer_a[i] == 0);
+ }
+ }
+
+ return 0;
+}
diff --git a/tests/strings.c b/tests/strings.c
new file mode 100644
index 0000000..d49d525
--- /dev/null
+++ b/tests/strings.c
@@ -0,0 +1,103 @@
+#include <ungrateful.h>
+
+int main(void) {
+ List splits;
+ Allocator talloc;
+ String result;
+
+ assert(un_string_get_length(UN_CSTR "Hello") == 5);
+ assert(un_string_get_length(UN_CSTR "") == 0);
+ assert(un_string_get_length(UN_CSTR "What") == 4);
+
+ talloc = un_allocator_get_temporary();
+
+ result = un_string_concat(UN_STR("Hello "), UN_STR("world!"), talloc);
+ assert(!un_string_compare(result, UN_STR("Hello world!")));
+
+ assert(!un_string_compare(un_string_copy(result, talloc), result));
+
+ splits = un_string_split(UN_STR("Eatin burger wit no honey mustard"), UN_STR(" "), talloc);
+
+ assert(splits.count == 6);
+
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 0), UN_STR("Eatin")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 1), UN_STR("burger")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 2), UN_STR("wit")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 3), UN_STR("no")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 4), UN_STR("honey")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 5), UN_STR("mustard")));
+
+ un_list_destroy(&splits);
+
+ splits = un_string_split(UN_STR("Eatin||burger||wit||no||honey||mustard"), UN_STR("||"), talloc);
+
+ assert(splits.count == 6);
+
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 0), UN_STR("Eatin")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 1), UN_STR("burger")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 2), UN_STR("wit")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 3), UN_STR("no")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 4), UN_STR("honey")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 5), UN_STR("mustard")));
+
+ un_list_destroy(&splits);
+
+ splits = un_string_split(UN_STR("Eatin||burger||wit||no||honey||mustard||a"), UN_STR("||"), talloc);
+
+ assert(splits.count == 7);
+
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 0), UN_STR("Eatin")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 1), UN_STR("burger")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 2), UN_STR("wit")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 3), UN_STR("no")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 4), UN_STR("honey")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 5), UN_STR("mustard")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 6), UN_STR("a")));
+
+ un_list_destroy(&splits);
+
+ splits = un_string_split(UN_STR("a||Eatin||burger||wit||no||honey||mustard||a"), UN_STR("||"), talloc);
+
+ assert(splits.count == 8);
+
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 0), UN_STR("a")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 1), UN_STR("Eatin")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 2), UN_STR("burger")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 3), UN_STR("wit")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 4), UN_STR("no")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 5), UN_STR("honey")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 6), UN_STR("mustard")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 7), UN_STR("a")));
+
+ un_list_destroy(&splits);
+
+ splits = un_string_split(UN_STR("||Eatin||burger||wit||no||honey||mustard||"), UN_STR("||"), talloc);
+
+ assert(splits.count == 6);
+
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 0), UN_STR("Eatin")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 1), UN_STR("burger")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 2), UN_STR("wit")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 3), UN_STR("no")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 4), UN_STR("honey")));
+ assert(!un_string_compare(*(String*)un_list_get(&splits, 5), UN_STR("mustard")));
+
+ assert(!un_string_compare(un_string_join(splits, UN_STR(" "), talloc), UN_STR("Eatin burger wit no honey mustard")));
+
+ un_list_destroy(&splits);
+
+ assert(!un_string_compare(un_string_substring(UN_STR("HelloWorld!"), 5, 6), UN_STR("World!")));
+ assert(!un_string_compare(un_string_substring(UN_STR("HelloWorld!"), 0, 11), UN_STR("HelloWorld!")));
+ assert(!un_string_compare(un_string_substring(UN_STR("HelloWorld!"), 10, 1), UN_STR("!")));
+
+ assert(un_string_index_of(UN_STR("CP/M"), (u8)'/', 0) == 2);
+ assert(un_string_index_of_last(UN_STR("https://github.com/bonmas14"), (u8)'/') == 18);
+
+ assert(!un_string_compare(un_string_swap(UN_STR("/path/from/unix/systems/"), (u8)'/', (u8) '\\', talloc), UN_STR("\\path\\from\\unix\\systems\\")));
+
+ assert(!un_string_compare(
+ un_string_format(talloc, UN_STR("/path/%s/unix/a %d %u %%"), UN_STR("test"), (s64)-100, (u64)404),
+ UN_STR("/path/test/unix/a -100 404 %")
+ )
+ );
+}