Files
FolderOpdsServer/src/noh.h

1346 lines
44 KiB
C

// A simple standard library full of functions reused a lot along my
// projects.
//
// Copyright 2024 ThoNohT <e.c.p.bataille@gmail.com>
//
// 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.
#ifndef NOH_H_
#define NOH_H_
#include <stdio.h>
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include <stdarg.h>
#include <time.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define _WINUSER_
#define _WINGDI_
#define _IMM_
#define _WINCON_
#include <windows.h>
#include <direct.h>
#else
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#endif // _WIN32
///////////////////////// Number definitions /////////////////////////
#ifndef int8
#define int8 signed char
#endif
#ifndef uint8
#define uint8 unsigned char
#endif
#ifndef int16
#define int16 short
#endif
#ifndef uint16
#define uint16 unsigned short
#endif
#ifndef uint
#define uint unsigned int
#endif
#ifndef _WIN32
#ifndef int64
#define int64 long
#endif
#endif
#ifndef uint64
#define uint64 unsigned long
#endif
#ifndef KB
#define KB << 10
#endif
#ifndef MB
#define MB << 20
#endif
#ifndef GB
#define GB << 30
#endif
///////////////////////// Core stuff /////////////////////////
#define max(a,b) \
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; })
#define min(a,b) \
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a < _b ? _a : _b; })
#define noh_array_len(array) (sizeof(array)/sizeof(array[0]))
#define noh_array_get(array, index) \
(noh_assert(index >= 0), noh_assert(index < noh_array_get(array)), array[index])
// Allows returning a file after performing some deferred code.
// Usage:
// Define a result variable before the first call of this macro.
// Place a defer label at the end of the function where the work is done.
// Return result at the end of the deferred work.
#define noh_return_defer(value) do { result = (value); goto defer; } while(0)
void* noh_realloc_check_(void *target, size_t size);
// Reallocates some memory and crashes if it failed.
#define noh_realloc_check(target, size) noh_realloc_check_((void*)(target), (size))
// Returns the next argument as a c-string, moves the argv pointer to the next argument and decreases argc.
char *noh_shift_args(int *argc, char ***argv);
///////////////////////// Time /////////////////////////
// Returns the result of subtracting the second timespec from the first timespec, in milliseconds.
// There is no absolute compare function, since it is assumed that a higher precision than milliseconds will not be
// needed, and cannot really be expected to be reliable.
long noh_diff_timespec_ms(const struct timespec *time1, const struct timespec *time2);
// Returns a timespec that represents the local time with the specified number of second and milliseconds added.
// Negative values will lead to a time in the past.
struct timespec noh_get_time_in(int seconds, long milliseconds);
// Adds the specified number of seconds and milliseconds to a timespec.
void noh_time_add(struct timespec *time, int seconds, long milliseconds);
///////////////////////// Logging /////////////////////////
// An assert macro that outputs a better format for use with vim's make command.
#define noh_assert(condition) { \
if (!(condition)) { \
printf("%s:%i: Assertion failed: %s. \n", __FILE__, __LINE__, #condition); \
exit(1); \
} \
} \
// Possible log levels.
typedef enum {
NOH_INFO,
NOH_WARNING,
NOH_ERROR,
} Noh_Log_Level;
// Writes a formatted log message to stderr with the provided log level.
void noh_log(Noh_Log_Level level, const char *fmt, ...);
///////////////////////// Dynamic array /////////////////////////
#define NOH_DA_INIT_CAP 256
// Appends an element to a dynamic array, allocates more memory and moves all elements to newly allocated memory
// if needed.
#define noh_da_append(da, elem) \
do { \
if ((da)->count >= (da)->capacity) { \
(da)->capacity = (da)->capacity == 0 ? NOH_DA_INIT_CAP : (da)->capacity * 2; \
(da)->elems = noh_realloc_check((da)->elems, (da)->capacity * sizeof(*(da)->elems)); \
} \
\
(da)->elems[(da)->count++] = (elem); \
} while(0)
// Appends multiple elements to a dynamic array. Allocates more memory and moves all elements to newly allocated memory
// if needed.
#define noh_da_append_multiple(da, new_elems, new_elems_count) \
do { \
if ((da)->count + new_elems_count > (da)->capacity) { \
if ((da)->capacity == 0) (da)->capacity = NOH_DA_INIT_CAP; \
while ((da)->count + new_elems_count > (da)->capacity) (da)->capacity *= 2; \
(da)->elems = noh_realloc_check((da)->elems, (da)->capacity * sizeof(*(da)->elems)); \
} \
\
memcpy((da)->elems + (da)->count, new_elems, new_elems_count * sizeof(*(da)->elems)); \
(da)->count += new_elems_count; \
} while (0)
// Removes the element at the specified location.
#define noh_da_remove_at(da, index) \
do { \
noh_assert((index) < (da)->count && "Index out of bounds."); \
(da)->count -= 1; \
if ((index) < (da)->count) { \
size_t elem_size = sizeof(*(da)->elems); \
memmove( \
(void*)(da)->elems + (index) * elem_size, \
(void*)(da)->elems + ((index) + 1) * elem_size, \
((da)->count - (index)) * elem_size); \
} \
} while (0)
// Frees the elements in a dynamic array, and resets the count and capacity.
#define noh_da_free(da) \
do { \
if ((da)->capacity > 0) { \
(da)->count = 0; \
(da)->capacity = 0; \
free((da)->elems); \
} \
} while (0)
// Resets the count of a dynamic array to 0.
#define noh_da_reset(da) \
do { \
(da)->count = 0; \
} while (0)
///////////////////////// Circular buffer /////////////////////////
// Initializes a circular buffer, similar to a dynamic array, but adding elements should be done with noh_cb_insert.
// This call should be the only one to allocate memory to hold the data and set the capacity.
#define noh_cb_initialize(da, size) { \
noh_assert((da)->capacity == 0 && "Cannot initialize an already initialized circular buffer."); \
noh_assert((size) > 0 && "Cannot initialize an empty circular buffer."); \
\
(da)->capacity = size; \
(da)->elems = noh_realloc_check((da)->elems, (da)->capacity * sizeof(*(da)->elems)); \
(da)->start = 0; \
(da)->count = 0; \
} \
// Inserts an element in a dynamic array as if it is a circular buffer, will not extend beyond the capacity of the
// dynamic array but instead overwrite the oldest element.
#define noh_cb_insert(da, elem) \
do { \
noh_assert((da)->capacity > 0 && "Circular buffer is not initialized."); \
\
if ((da)->count < (da)->capacity) { \
(da)->elems[(da)->count++] = (elem); \
} else { \
(da)->elems[(da)->start] = (elem); \
(da)->start = ((da)->start + 1) % (da)->count; \
} \
} while(0)
///////////////////////// Arena /////////////////////////
#define NOH_ARENA_INIT_CAP 1<<10
// Checkpoints in an arena.
typedef struct {
size_t block_id;
size_t offset_in_block;
} Noh_Arena_Checkpoint;
typedef struct {
Noh_Arena_Checkpoint *elems;
size_t count;
size_t capacity;
} Noh_Arena_Checkpoints;
// Data blocks in an arena.
typedef struct {
char *data;
size_t size;
size_t capacity;
} Noh_Arena_Data_Block;
typedef struct {
Noh_Arena_Data_Block *elems;
size_t count;
size_t capacity;
} Noh_Arena_Data_Blocks;
// An arena for storing temporary data.
typedef struct {
Noh_Arena_Data_Blocks blocks; // Blocks are always in order of increasing capacity.
Noh_Arena_Checkpoints checkpoints;
size_t active_block; // The index of the block up to which data has been allocated.
} Noh_Arena;
// Initialize an empty arena with the specified capacity. A checkpoint is also saved at the empty arena.
Noh_Arena noh_arena_init(size_t capacity);
// Resets the size of an arena to 0, keeping the data reserved. Any checkpoints are removed and one is saved at the
// start of the arena. Requires that the arena is initialized with noh_arena_init.
void noh_arena_reset(Noh_Arena *arena);
// Frees all data in an arena. Any checkpoints are removed. The arena is no longer initialized, and cannot be used
// anymore.
void noh_arena_free(Noh_Arena *arena);
// Ensures that there is room available for the requested size of data. Does not return a pointer to the data to the
// caller. Used if you want to pre-allocate a larger set of data that will later be filled by multiple allocations,
// keeping it in a single block.
void noh_arena_reserve(Noh_Arena *arena, size_t size);
// Allocates data in an arena of the requested size, returns the start of the data.
// Requires at least one checkpoint, either from noh_arena_init, noh_arena_reset or noh_arena_save.
void *noh_arena_alloc(Noh_Arena *arena, size_t size);
// Saves the current position in of the arena in a checkpoint. Requires that the arena is initialized with
// noh_arena_init.
void noh_arena_save(Noh_Arena *arena);
// Rewinds an arena to the last saved checkpoint. Requires at least one checkpoint.
void noh_arena_rewind(Noh_Arena *arena);
// Copies a c-string to the arena.
char *noh_arena_strdup(Noh_Arena *arena, const char *cstr);
// Prints the specified formatted string to the arena.
char *noh_arena_sprintf(Noh_Arena *arena, const char *format, ...);
///////////////////////// Strings /////////////////////////
// Defines a string that can be extended.
typedef struct {
char *elems;
size_t count;
size_t capacity;
} Noh_String;
// Copies a Noh_String to the arena, and frees the Noh_String
char *noh_arena_consume_string(Noh_Arena *arena, Noh_String *string);
// Creates a Noh_String from a c-string.
Noh_String noh_string_from_cstr(const char *cstr);
// Appends a null-terminated string into a Noh_String.
void noh_string_append_cstr(Noh_String *string, const char *cstr);
// Appends null into a Noh_String.
void noh_string_append_null(Noh_String *string);
// Frees a Noh_String, freeing the memory used and settings the count and capacity to 0.
#define noh_string_free(string) noh_da_free(string)
// Resets a Noh_String, setting the count to 0.
#define noh_string_reset(string) noh_da_reset(string)
// Returns true if a file or directory exists, false otherwise.
bool noh_file_exists(const char *filename);
// Reads the contents of a file into a Noh_String.
bool noh_string_read_file(Noh_String *string, const char *filename);
// Writes the contents of a Noh_String to a file.
bool noh_string_write_file(Noh_String *string, const char *filename);
///////////////////////// String view /////////////////////////
// A view of a string, that does not own the data.
typedef struct {
size_t count;
const char *elems;
} Noh_String_View;
// Copies a c-string to the arena and returns it as a Noh_String_View.
Noh_String_View noh_sv_copy_cstr(Noh_Arena *arena, const char *cstr);
// Appends a Noh_String_View view into a Noh_String.
void noh_string_append_sv(Noh_String *string, Noh_String_View sv);
// Increases the position of a string view by the specified amount, reducing its count by the same
// amount. If the end is reached, the string view will remain empty without moving further.
void noh_sv_increase_position(Noh_String_View *sv, size_t distance);
// Finds the first occurrence of the specified delimiter in a string view and returns the part of the string until
// that delimiter. The input string view itself is shrunk to start after the delimiter.
// If the delimiter is not found, the result is the entire string and the input string view is empty.
Noh_String_View noh_sv_chop_by_delim(Noh_String_View *sv, char delim);
// Finds the first occurrence of a line separator in a string view and returns the part of the string until
// that separator. The string view itself is shrunk to start after the separator.
// Supports '/r', '/n' and '/r/n'.
// If no line separator is found, the entire string is returned and the input string is empty.
Noh_String_View noh_sv_chop_line(Noh_String_View *sv);
// Chops a string while a predicate matches.
Noh_String_View noh_sv_chop_while(Noh_String_View *sv, bool (*do_chop)(char));
// Chops a string by the specified distance.
Noh_String_View noh_sv_chop(Noh_String_View *sv, size_t distance);
// Trims the left part of a string view, until the provided function no longer holds on the current character.
void noh_sv_trim_left(Noh_String_View *sv, bool (*do_trim)(char));
// Trims the right part of a string view, until the provided function no longer holds on the current character.
void noh_sv_trim_right(Noh_String_View *sv, bool (*do_trim)(char));
// Trims both sides of a string view, until the provided function no longer holds on the current character.
void noh_sv_trim(Noh_String_View *sv, bool (*do_trim)(char));
// Trims spaces from the left part of a string view.
inline void noh_sv_trim_space_left(Noh_String_View *sv);
// Trims spaces from the right part of a string view.
inline void noh_sv_trim_space_right(Noh_String_View *sv);
// Trims spaces from both sides of a string view.
inline void noh_sv_trim_space(Noh_String_View *sv);
// Creates a string view from a c-string.
Noh_String_View noh_sv_from_cstr(const char *cstr);
// Creates a string view from a string.
Noh_String_View noh_sv_from_string(const Noh_String string);
// Compares two string views.
// Returns:
// - 0 if the two strings are equal.
// - A negative value if a is smaller than b.
// - A positive value if a is greater than b.
int noh_sv_compare(Noh_String_View a, Noh_String_View b);
// Checks whether to string views contain the same string.
bool noh_sv_eq(Noh_String_View a, Noh_String_View b);
// Checks whether to string views contain the same string, ignoring the case.
bool noh_sv_eq_ci(Noh_String_View a, Noh_String_View b);
// Checks whether the first string view starts with the elements from second string view.
bool noh_sv_starts_with(Noh_String_View a, Noh_String_View b);
// Checks whether the first string view starts with the elements from second string view, ignoring the case.
bool noh_sv_starts_with_ci(Noh_String_View a, Noh_String_View b);
// Checks whether the first string view ends with the elements from second string view.
bool noh_sv_ends_with(Noh_String_View a, Noh_String_View b);
// Checks whether the first string view ends with the elements from second string view, ignoring the case.
bool noh_sv_ends_with_ci(Noh_String_View a, Noh_String_View b);
// Checks whether the first string view contains the elements from the second string view.
inline bool noh_sv_contains(Noh_String_View a, Noh_String_View b);
// Returns the first index of the second string view in the first string view.
// Returns -1 if it is not found.
int noh_sv_index_of(Noh_String_View a, Noh_String_View b);
// Checks whether the first string view contains the elements from the second string view, ignoring the case.
inline bool noh_sv_contains_ci(Noh_String_View a, Noh_String_View b);
// Returns the first index of the second string view in the first string view, ignoring the case.
// Returns -1 if it is not found.
int noh_sv_index_of_ci(Noh_String_View a, Noh_String_View b);
// Creates a cstring in an arena from a string view.
const char *noh_sv_to_arena_cstr(Noh_Arena *arena, Noh_String_View sv);
// Creates a substring from a string-view, where the start and end are capped to the bounds of the input string view.
// If a length of 0 is provided, the entire string after start is returned.
Noh_String_View noh_sv_substring(Noh_String_View sv, size_t start, size_t length);
// printf macros for Noh_String_View or Noh_String.
#define Nsv_Fmt "%.*s"
#define Nsv_Arg(sv) (int) (sv).count, (sv).elems
// USAGE:
// Noh_String_View name = ...;
// printf("Name: "Nsv_Fmt"\n", Nsv_Arg(name));
///////////////////////// Files and directories /////////////////////////
// File paths.
typedef struct {
char **elems;
size_t count;
size_t capacity;
} Noh_File_Paths;
// Creates the path at the specified directory if it does not exist.
// Does not create any missing parent directories.
bool noh_mkdir_if_needed(const char *path);
// Renames a file.
bool noh_rename(const char *path, const char *new_path);
// Removes a file.
bool noh_remove(const char *path);
///////////////////////// Processes /////////////////////////
// Process identifiers.
#ifdef _WIN32
typedef HANDLE Noh_Pid;
#define NOH_INVALID_PROC INVALID_HANDLE_VALUE
#else
typedef pid_t Noh_Pid;
#define NOH_INVALID_PROC (-1)
#endif // _WIN32
// A collection of processes.
typedef struct {
Noh_Pid *elems;
size_t count;
size_t capacity;
} Noh_Procs;
// Waits for a single process.
bool noh_proc_wait(Noh_Pid pid);
// Waits for a collection of processes.
bool noh_procs_wait(Noh_Procs procs);
// Frees the collection pocesses.
#define noh_procs_free(procs) noh_da_free(procs);
// Resets the collection of processes.
#define noh_procs_reset(procs) noh_da_reset(procs);
///////////////////////// Commands /////////////////////////
// Defines a command that can be run.
typedef struct {
const char **elems;
size_t count;
size_t capacity;
} Noh_Cmd;
// Appends one or more strings to a command.
#define noh_cmd_append(cmd, ...) \
noh_da_append_multiple( \
cmd, \
((const char*[]){__VA_ARGS__}), (sizeof((const char*[]){__VA_ARGS__}) / sizeof(const char*)))
// Frees a command, freeing the memory used for its elements and setting the count and capacity to 0.
#define noh_cmd_free(cmd) noh_da_free(cmd)
// Resets a command, setting the count to 0.
#define noh_cmd_reset(cmd) noh_da_reset(cmd)
// Runs a command asynchronously and returns the process id.
Noh_Pid noh_cmd_run_async(Noh_Cmd cmd);
// Runs a command synchronously.
bool noh_cmd_run_sync(Noh_Cmd cmd);
// Renders a textual representation of the command into the provided string.
void noh_cmd_render(Noh_Cmd cmd, Noh_String *string);
#endif // NOH_H_
#ifdef NOH_IMPLEMENTATION
///////////////////////// Core stuff /////////////////////////
void* noh_realloc_check_(void *target, size_t size) {
target = realloc(target, size);
noh_assert(target != NULL && "Could not allocate enough memory");
return target;
}
char *noh_shift_args(int *argc, char ***argv) {
noh_assert(*argc > 0 && "No more arguments");
char *result = **argv;
(*argv)++;
(*argc)--;
return result;
}
///////////////////////// Time /////////////////////////
long noh_diff_timespec_ms(const struct timespec *time1, const struct timespec *time2) {
noh_assert(time1);
noh_assert(time2);
long res = 0;
// Every second adds 1000 milliseconds difference.
res += (time1->tv_sec - time2->tv_sec) * 1000;
// Every 1000 * 1000 nanoseconds add 1 millisecond difference.
res += (time1->tv_nsec - time2->tv_nsec) / 1000 / 1000;
return res;
}
struct timespec noh_get_time_in(int seconds, long milliseconds) {
struct timespec time;
if (clock_gettime(CLOCK_REALTIME, &time) == -1)
{
noh_log(NOH_ERROR, "Unable to get the current time: %s", strerror(errno));
exit(1);
}
noh_time_add(&time, seconds, milliseconds);
return time;
}
void noh_time_add(struct timespec *time, int seconds, long milliseconds) {
static long ns_per_ms = 1000 * 1000;
time->tv_sec += seconds;
time->tv_nsec += milliseconds * ns_per_ms;
// Fix any overflow.
if (time->tv_nsec >= 1000 * ns_per_ms) {
time->tv_sec += time->tv_nsec / (1000 * ns_per_ms);
time->tv_nsec %= 1000 * ns_per_ms;
}
}
///////////////////////// Logging /////////////////////////
void noh_log(Noh_Log_Level level, const char *fmt, ...)
{
switch (level) {
case NOH_INFO:
fprintf(stderr, "[INFO] ");
break;
case NOH_WARNING:
fprintf(stderr, "[WARNING] ");
break;
case NOH_ERROR:
fprintf(stderr, "[ERROR] ");
break;
default:
noh_assert(false && "Invalid log level");
}
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fprintf(stderr, "\n");
}
///////////////////////// Arena /////////////////////////
// Alin a size such that it is a multiple of 8, keeping blocks of 64 bits.
size_t align_size(size_t size) {
return size + (size % 8);
}
Noh_Arena noh_arena_init(size_t size) {
Noh_Arena arena = {0};
Noh_Arena_Data_Blocks blocks = {0};
arena.blocks = blocks;
Noh_Arena_Checkpoints checkpoints = {0};
arena.checkpoints = checkpoints;
arena.active_block = 0;
Noh_Arena_Data_Block block = {0};
block.capacity = align_size(size);
block.data = noh_realloc_check(block.data, block.capacity);
block.size = 0;
noh_da_append(&arena.blocks, block);
// Nice to have a checkpoint at the start.
noh_arena_save(&arena);
return arena;
}
void noh_arena_reset(Noh_Arena *arena) {
// We need to load a block and save it in the checkpoint, so at least one block needs to be allocated.
noh_assert(arena->blocks.count > 0 && "Please ensure that the arena is inintialized.");
// Reset checkpoints.
noh_da_reset(&arena->checkpoints);
// Insert a checkpoint at the start so we can rewind to this checkpoint.
Noh_Arena_Checkpoint start_checkpoint = {0};
start_checkpoint.block_id = 0;
start_checkpoint.offset_in_block = 0;
noh_da_append(&arena->checkpoints, start_checkpoint);
// Rewind to the checkpoint, and place the checkpoint back in such that there is again a checkpoint at the start.
noh_arena_rewind(arena);
arena->checkpoints.count += 1;
}
void noh_arena_free(Noh_Arena *arena) {
// Remove checkpoints.
noh_da_free(&arena->checkpoints);
// Free all blocks.
for (size_t i = 0; i < arena->blocks.count; i++) {
Noh_Arena_Data_Block *block = &arena->blocks.elems[i];
free(block->data);
}
// Remove blocks.
noh_da_free(&arena->blocks);
arena->active_block = 0;
}
void *noh_arena_alloc(Noh_Arena *arena, size_t size) {
// This is technically not needed, but it is nice to be consistent and ensure that there is always a checkpoint
// at the beginning, either from noh_arena_init, noh_arena_reset or noh_arena_save.
noh_assert(arena->checkpoints.count > 0 && "Please ensure that there is at least one checkpoint before allocating.");
// Reserve will ensure that we have the required space available. Then we just need to find the block where we can
// allocate the requested space.
noh_arena_reserve(arena, size);
// Find the block that fits the requested size.
size_t current_block = arena->active_block;
Noh_Arena_Data_Block *block = &arena->blocks.elems[current_block];
while (block->capacity - block->size < size && current_block < arena->blocks.count) {
current_block += 1;
block = &arena->blocks.elems[current_block];
}
noh_assert(block->capacity - block->size >= size && "Reserve should have provided a large enough block.");
arena->active_block = current_block;
// Allocate data in the block and return a pointer to the start.
void *result = &block->data[block->size];
block->size += size;
return result;
}
void noh_arena_reserve(Noh_Arena *arena, size_t size) {
noh_assert(arena->blocks.count > 0 && "Please ensure that the arena is initialized.");
size_t requested_size = align_size(size);
while (arena->active_block < arena->blocks.count) {
Noh_Arena_Data_Block *block = &arena->blocks.elems[arena->active_block];
// If the requested size fits into the current block, use it and return.
if (block->capacity - block->size >= requested_size) {
return;
}
// If it doesn't, free the block if it was empty. Note that all but the current block will be empty, since
// rewinding sets the sizes of later blocks to 0. Current block may be empty.
if (block->size == 0) {
free(block->data);
// This reduces arena->blocks.count, thus ensuring termination of the loop.
noh_da_remove_at(&arena->blocks, arena->active_block);
} else {
// If we're not cleaning this block up, move the pointer.
arena->active_block += 1;
}
}
// If no block was found that fits, create a new one that is at least as big as the requested size, and twice the
// size of the current block.
// arena->active_block will now point to just beyond the last existing block. We can get the previous capacity
// only if we didn't just delete the first block.
size_t prev_cap = 0;
if (arena->active_block > 1) prev_cap = arena->blocks.elems[arena->active_block - 1].capacity;
size_t new_cap = NOH_ARENA_INIT_CAP;
// If not big enough to double the previous cap, set to double the previous capacity.
if (prev_cap * 2 > new_cap) new_cap = prev_cap * 2;
// Keep doubling until the requested size fits.
while (requested_size > new_cap) new_cap *= 2;
Noh_Arena_Data_Block new_block = {0};
new_block.data = noh_realloc_check(new_block.data, new_cap);
new_block.capacity = new_cap;
new_block.size = 0;
// After adding this block, arena->active_block will point to this new block.
noh_da_append(&(arena->blocks), new_block);
}
void noh_arena_save(Noh_Arena *arena) {
// We need to load a block and save it in the checkpoint, so at least one block needs to be allocated.
noh_assert(arena->blocks.count > 0 && "Please ensure that the arena is inintialized.");
Noh_Arena_Checkpoint checkpoint = {0};
checkpoint.block_id = arena->active_block;
Noh_Arena_Data_Block *block = &arena->blocks.elems[arena->active_block];
checkpoint.offset_in_block = block->size;
noh_da_append(&(arena->checkpoints), checkpoint);
}
void noh_arena_rewind(Noh_Arena *arena) {
noh_assert(arena->checkpoints.count > 0 && "No history to rewind");
// Restore to block from checkpoint.
Noh_Arena_Checkpoint *checkpoint = &arena->checkpoints.elems[arena->checkpoints.count - 1];
arena->active_block = checkpoint->block_id;
// Rewind all blocks from the active block to the end.
for (size_t i = arena->active_block; i < arena->blocks.count; i++) {
Noh_Arena_Data_Block *block = &arena->blocks.elems[i];
if (i == arena->active_block) block->size = checkpoint->offset_in_block;
else block->size = 0;
}
// Remove checkpoint.
arena->checkpoints.count -= 1;
}
char *noh_arena_strdup(Noh_Arena *arena, const char *cstr) {
size_t len = strlen(cstr);
char *result = noh_arena_alloc(arena, len + 1);
memcpy(result, cstr, len);
result[len] = '\0';
return result;
}
char *noh_arena_sprintf(Noh_Arena *arena, const char *format, ...) {
va_list args;
va_start(args, format);
int n = vsnprintf(NULL, 0, format, args);
va_end(args);
noh_assert(n >= 0);
char *result = noh_arena_alloc(arena, n + 1);
va_start(args, format);
vsnprintf(result, n + 1, format, args);
va_end(args);
return result;
}
///////////////////////// Strings /////////////////////////
char *noh_arena_consume_string(Noh_Arena *arena, Noh_String *string) {
char *result = noh_arena_alloc(arena, string->count + 1);
memcpy(result, string->elems, string->count);
result[string->count] = '\0';
noh_string_free(string);
return result;
}
Noh_String noh_string_from_cstr(const char *cstr) {
Noh_String str = {0};
noh_string_append_cstr(&str, cstr);
return str;
}
void noh_string_append_cstr(Noh_String *string, const char *cstr) {
size_t len = strlen(cstr);
noh_da_append_multiple(string, cstr, len);
}
void noh_string_append_null(Noh_String *string) {
noh_da_append(string, '\0');
}
bool noh_file_exists(const char *filename) {
struct stat st;
return (stat(filename, &st)) == 0;
}
bool noh_string_read_file(Noh_String *string, const char *filename) {
bool result = true;
size_t buf_size = 32*1024;
char *buf = NULL;
buf = noh_realloc_check(buf, buf_size);
FILE *f = fopen(filename, "rb");
if (f == NULL) {
noh_log(NOH_ERROR, "Could not open file %s: %s.\n", filename, strerror(errno));
noh_return_defer(false);
}
size_t n = fread(buf, 1, buf_size, f);
while (n > 0) {
noh_da_append_multiple(string, buf, n);
n = fread(buf, 1, buf_size, f);
}
if (ferror(f)) {
noh_log(NOH_ERROR, "Could not read file %s: %s.\n", filename, strerror(errno));
noh_return_defer(false);
}
defer:
free(buf);
if (f) fclose(f);
return result;
}
bool noh_string_write_file(Noh_String *string, const char *filename) {
bool result = true;
FILE *f = fopen(filename, "wb");
if (f == NULL) {
noh_log(NOH_ERROR, "Could not open file %s: %s.\n", filename, strerror(errno));
noh_return_defer(false);
}
int written = fprintf(f, Nsv_Fmt, Nsv_Arg(*string));
if (written < 0) {
noh_log(NOH_ERROR, "Could not write to file %s.\n", filename);
noh_return_defer(false);
}
defer:
if (f) fclose(f);
return result;
}
///////////////////////// String view /////////////////////////
Noh_String_View noh_sv_copy_cstr(Noh_Arena *arena, const char *cstr) {
size_t len = strlen(cstr);
char *buffer = noh_arena_alloc(arena, len);
memcpy(buffer, cstr, len);
return (Noh_String_View){ .elems = buffer, .count = len };
}
void noh_string_append_sv(Noh_String *string, Noh_String_View sv) {
noh_da_append_multiple(string, sv.elems, sv.count);
}
void noh_sv_increase_position(Noh_String_View *sv, size_t distance) {
if (distance < sv->count) {
sv->count -= distance;
sv->elems += distance;
} else {
sv->count = 0;
sv->elems += sv->count;
}
}
Noh_String_View noh_sv_chop_by_delim(Noh_String_View *sv, char delim) {
size_t i = 0;
// Find the character, or the end of the string view.
while (i < sv->count && sv->elems[i] != delim) i++;
// The data until the delimiter is returned.
Noh_String_View result = { .count = i, .elems = sv->elems };
// Update the current string view beyond the delimiter.
noh_sv_increase_position(sv, i + 1);
return result;
}
Noh_String_View noh_sv_chop_while(Noh_String_View *sv, bool (*do_chop)(char)) {
size_t i = 0;
// Keep going until the end or the function no longer matches.
while (i < sv->count && ((*do_chop)(sv->elems[i]))) i++;
// The data until this point is returned.
Noh_String_View result = { .count = i, .elems = sv->elems };
// Update the current string view to after this point.
noh_sv_increase_position(sv, i);
return result;
}
Noh_String_View noh_sv_chop(Noh_String_View *sv, size_t distance) {
Noh_String_View result = noh_sv_substring(*sv, 0, distance);
noh_sv_increase_position(sv, distance);
return result;
}
Noh_String_View noh_sv_chop_line(Noh_String_View *sv) {
size_t i = 0;
// Find a newline or carriage return character.
while (i < sv->count && sv->elems[i] != '\r' && sv->elems[i] != '\n') i++;
// The data until the line separator(s) is returned.
Noh_String_View result = { .count = i, .elems = sv->elems };
// Update the current string view beyond the line separator(s).
if (i + 1 < sv->count && sv->elems[i] == '\r' && sv->elems[i + 1] == '\n') {
// Skip carriage return and newline.
noh_sv_increase_position(sv, i + 2);
} else {
// Skip single newline, carriage return or to the end
// (noh_sv_increase_position allows too high increases).
noh_sv_increase_position(sv, i + 1);
}
return result;
}
void noh_sv_trim_left(Noh_String_View *sv, bool (*do_trim)(char)) {
size_t i = 0;
while (i < sv->count && (*do_trim)(sv->elems[i])) i++;
noh_sv_increase_position(sv, i);
}
void noh_sv_trim_right(Noh_String_View *sv, bool (*do_trim)(char)) {
size_t i = sv->count;
while (i > 0 && (*do_trim)(sv->elems[i-1])) i--;
sv->count = i;
}
void noh_sv_trim(Noh_String_View *sv, bool (*do_trim)(char)) {
noh_sv_trim_left(sv, do_trim);
noh_sv_trim_right(sv, do_trim);
}
bool is_space(char c) {
return isspace(c) > 0;
}
inline void noh_sv_trim_space_left(Noh_String_View *sv) {
noh_sv_trim_left(sv, &is_space);
}
inline void noh_sv_trim_space_right(Noh_String_View *sv) {
noh_sv_trim_right(sv, &is_space);
}
inline void noh_sv_trim_space(Noh_String_View *sv) {
noh_sv_trim(sv, &is_space);
}
Noh_String_View noh_sv_from_cstr(const char *cstr) {
Noh_String_View result = {0};
result.elems = cstr;
result.count = strlen(cstr);
return result;
}
Noh_String_View noh_sv_from_string(const Noh_String string) {
Noh_String_View result = {0};
result.elems = string.elems;
result.count = string.count;
return result;
}
int noh_sv_compare(Noh_String_View a, Noh_String_View b) {
size_t count = a.count;
if (b.count < a.count) count = b.count;
for (size_t i = 0; i < count; i++) {
if (a.elems[i] == b.elems[i]) continue;
return a.elems[i] - b.elems[i];
}
return a.count - b.count;
}
bool noh_sv_eq(Noh_String_View a, Noh_String_View b) {
if (a.count != b.count) return false;
for (size_t i = 0; i < a.count; i++) {
if (a.elems[i] != b.elems[i]) return false;
}
return true;
}
// Check two characters case insensitively.
bool char_eq_ci(char a, char b) {
// FUTURE: Unicode support?
// A=65, Z=90 - a=97, z=122
if (a >= 65 && a <= 90) a +=32;
if (b >= 65 && b <= 90) b +=32;
return a == b;
}
bool noh_sv_eq_ci(Noh_String_View a, Noh_String_View b) {
if (a.count != b.count) return false;
for (size_t i = 0; i < a.count; i++) {
if (!char_eq_ci(a.elems[i], b.elems[i])) return false;
}
return true;
}
bool noh_sv_starts_with(Noh_String_View a, Noh_String_View b) {
if (a.count < b.count) return false;
a.count = b.count;
return noh_sv_eq(a, b);
}
bool noh_sv_starts_with_ci(Noh_String_View a, Noh_String_View b) {
if (a.count < b.count) return false;
a.count = b.count;
return noh_sv_eq_ci(a, b);
}
bool noh_sv_ends_with(Noh_String_View a, Noh_String_View b) {
if (a.count < b.count) return false;
a.elems += (a.count - b.count);
a.count = b.count;
return noh_sv_eq(a, b);
}
bool noh_sv_ends_with_ci(Noh_String_View a, Noh_String_View b) {
if (a.count < b.count) return false;
a.elems += (a.count - b.count);
a.count = b.count;
return noh_sv_eq_ci(a, b);
}
inline bool noh_sv_contains(Noh_String_View a, Noh_String_View b) {
return noh_sv_index_of(a, b) >= 0;
}
int noh_sv_index_of(Noh_String_View a, Noh_String_View b) {
int i = 0;
while (a.count >= b.count) {
if (noh_sv_starts_with(a, b)) return i;
noh_sv_increase_position(&a, 1);
i++;
}
return -1;
}
inline bool noh_sv_contains_ci(Noh_String_View a, Noh_String_View b) {
return noh_sv_index_of_ci(a, b) >= 0;
}
int noh_sv_index_of_ci(Noh_String_View a, Noh_String_View b) {
int i = 0;
while (a.count >= b.count) {
if (noh_sv_starts_with_ci(a, b)) return i;
noh_sv_increase_position(&a, 1);
i++;
}
return -1;
}
const char *noh_sv_to_arena_cstr(Noh_Arena *arena, Noh_String_View sv)
{
char *result = noh_arena_alloc(arena, sv.count + 1);
memcpy(result, sv.elems, sv.count);
result[sv.count] = '\0';
return result;
}
Noh_String_View noh_sv_substring(Noh_String_View sv, size_t start, size_t length) {
Noh_String_View result = (Noh_String_View) { sv.count, sv.elems };
noh_sv_increase_position(&result, start);
if (result.count > length && length > 0) result.count = length;
return result;
}
///////////////////////// Files and directories /////////////////////////
bool noh_mkdir_if_needed(const char *path) {
#ifdef _WIN32
int result = mkdir(path);
#else
int result = mkdir(path, 0755);
#endif // _WIN32
if (result == 0) {
noh_log(NOH_INFO, "Created directory '%s'.", path);
return true;
}
if (errno == EEXIST) {
noh_log(NOH_INFO, "Directory '%s' already exists.", path);
return true;
}
noh_log(NOH_ERROR, "Could not create directory '%s': %s", path, strerror(errno));
return false;
}
bool noh_rename(const char *path, const char *new_path)
{
noh_log(NOH_INFO, "Renaming '%s' to '%s'.", path, new_path);
if (rename(path, new_path) < 0) {
noh_log(NOH_ERROR, "Rename failed: %s", strerror(errno));
return false;
}
return true;
}
bool noh_remove(const char *path) {
noh_log(NOH_INFO, "Removing '%s'.", path);
if (remove(path) < 0) {
noh_log(NOH_ERROR, "Remove failed: %s", strerror(errno));
return false;
}
return true;
}
///////////////////////// Processes /////////////////////////
bool noh_proc_wait(Noh_Pid pid)
{
if (pid == NOH_INVALID_PROC) return false;
#ifdef _WIN32
DWORD result = WaitForSingleObject(pid, INFINITE);
if (result == WAIT_FAILED) {
noh_log(NOH_ERROR, "Could not wait for command: %lu", GetLastError());
return false;
}
DWORD exit_status;
if (!GetExitCodeProcess(pid, &exit_status)) {
noh_log(NOH_ERROR, "Could not get command exit code: %lu", GetLastError());
return false;
}
if (exit_status != 0) {
noh_log(NOH_ERROR, "Command exited with exit code %lu", exit_status);
return false;
}
CloseHandle(pid);
#else
for (;;) {
int wstatus = 0;
if (waitpid(pid, &wstatus, 0) < 0) {
noh_log(NOH_ERROR, "Could not wait for command (pid %d): %s", pid, strerror(errno));
return false;
}
if (WIFEXITED(wstatus)) {
int exit_status = WEXITSTATUS(wstatus);
if (exit_status != 0) {
noh_log(NOH_ERROR, "Command exited with exit code %d", exit_status);
return false;
}
break;
}
if (WIFSIGNALED(wstatus)) {
noh_log(NOH_ERROR, "Command process was terminated by %s", strsignal(WTERMSIG(wstatus)));
return false;
}
}
#endif // _WIN32
return true;
}
bool noh_procs_wait(Noh_Procs procs) {
bool success = true;
for (size_t i = 0; i < procs.count; i++) {
success = noh_proc_wait(procs.elems[i]) && success;
}
return success;
}
///////////////////////// Commands /////////////////////////
// Adds a c string to a string, surrounding it with single quotes if it contains any spaces.
void noh_quote_if_needed(const char *value, Noh_String *string) {
if (!strchr(value, ' ')) {
noh_string_append_cstr(string, value);
} else {
noh_da_append(string, '\'');
noh_string_append_cstr(string, value);
noh_da_append(string, '\'');
}
}
void noh_cmd_render(Noh_Cmd cmd, Noh_String *string) {
for (size_t i = 0; i < cmd.count; ++i) {
const char *arg = cmd.elems[i];
if (arg == NULL) break;
if (i > 0) noh_string_append_cstr(string, " ");
noh_quote_if_needed(arg, string);
}
}
Noh_Pid noh_cmd_run_async(Noh_Cmd cmd) {
if (cmd.count < 1) {
noh_log(NOH_ERROR, "Cannot run an empty command.");
return NOH_INVALID_PROC;
}
// Log the command.
Noh_String sb = {0};
noh_cmd_render(cmd, &sb);
noh_da_append(&sb, '\0');
noh_log(NOH_INFO, "CMD: %s", sb.elems);
#ifdef _WIN32
noh_string_reset(&sb);
// https://learn.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output
STARTUPINFO suInfo;
ZeroMemory(&suInfo, sizeof(STARTUPINFO));
suInfo.cb = sizeof(STARTUPINFO);
suInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
suInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
suInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
suInfo.dwFlags |= STARTF_USESTDHANDLES;
PROCESS_INFORMATION procInfo;
ZeroMemory(&procInfo, sizeof(PROCESS_INFORMATION));
noh_cmd_render(cmd, &sb);
noh_string_append_null(&sb);
bool success = CreateProcessA(NULL, sb.elems, NULL, NULL, true, 0, NULL, NULL, &suInfo, &procInfo);
noh_string_free(&sb);
if (!success) {
noh_log(NOH_ERROR, "Could not create child process: %lu", GetLastError());
return NOH_INVALID_PROC;
}
CloseHandle(procInfo.hThread);
return procInfo.hProcess;
#else
noh_string_free(&sb);
Noh_Pid cpid = fork();
if (cpid < 0) {
noh_log(NOH_ERROR, "Could not fork child process: %s", strerror(errno));
return NOH_INVALID_PROC;
}
if (cpid == 0) {
// NOTE: This leaks a bit of memory in the child process.
// But do we actually care? It's a one off leak anyway...
// Create a command that is null terminated.
Noh_Cmd cmd_null = {0};
noh_da_append_multiple(&cmd_null, cmd.elems, cmd.count);
noh_cmd_append(&cmd_null, NULL);
if (execvp(cmd.elems[0], (char * const*) cmd_null.elems) < 0) {
noh_log(NOH_ERROR, "Could not execute child process: %s", strerror(errno));
exit(1);
}
noh_assert(0 && "unreachable");
}
return cpid;
#endif // _WIN32
}
bool noh_cmd_run_sync(Noh_Cmd cmd) {
Noh_Pid pid = noh_cmd_run_async(cmd);
if (pid == NOH_INVALID_PROC) return false;
return noh_proc_wait(pid);
}
#endif // NOH_IMPLEMENTATION