init
This commit is contained in:
commit
ab779b8506
7
LICENSE
Normal file
7
LICENSE
Normal file
@ -0,0 +1,7 @@
|
||||
Copyright 2022 Darwin Schuppan <darwin@nobrain.org>
|
||||
|
||||
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.
|
89
Makefile
Normal file
89
Makefile
Normal file
@ -0,0 +1,89 @@
|
||||
################################
|
||||
# Config #
|
||||
################################
|
||||
CFLAGS := -O2 -Wall -pedantic -Wno-gnu-zero-variadic-macro-arguments
|
||||
#CFLAGS := -ggdb -Wall -pedantic -Wno-gnu-zero-variadic-macro-arguments
|
||||
ifeq ($(OS),Windows_NT)
|
||||
EXE_EXT := .exe
|
||||
else
|
||||
EXE_EXT :=
|
||||
endif
|
||||
|
||||
################################
|
||||
# Library #
|
||||
################################
|
||||
HDR := internal/generic/begin.h internal/generic/end.h generic/map.h generic/smap.h generic/vec.h error.h fmt.h types.h string.h
|
||||
SRC := error.c fmt.c string.c
|
||||
|
||||
_HDR := $(addprefix include/ds/,$(HDR))
|
||||
_SRC := $(addprefix src/ds/,$(SRC))
|
||||
_OBJ := $(_SRC:.c=.o)
|
||||
|
||||
all: ds.a
|
||||
|
||||
ds.a: $(_OBJ)
|
||||
rm -f $@
|
||||
$(AR) rc $@ $^
|
||||
|
||||
src/%.o: src/%.c $(_HDR)
|
||||
$(CC) -c -o $@ $< -I./include $(CFLAGS)
|
||||
|
||||
################################
|
||||
# Testing #
|
||||
################################
|
||||
define test_single
|
||||
if ./$(1); then \
|
||||
echo "Passed test $(1)."; \
|
||||
else \
|
||||
echo "Failed test $(1)!"; \
|
||||
exit 1; \
|
||||
fi
|
||||
endef
|
||||
|
||||
define test_single_leaks
|
||||
printf "Checking for leaks in $(1)..."; \
|
||||
if valgrind -q --error-exitcode=1 --leak-check=full --show-leak-kinds=all --track-origins=yes ./$(1); then \
|
||||
echo "OK"; \
|
||||
else \
|
||||
echo "Valgrind reported one or more errors!"; \
|
||||
exit 1; \
|
||||
fi
|
||||
endef
|
||||
|
||||
TESTS := generic/map generic/smap generic/vec error fmt
|
||||
|
||||
_TESTS := $(addsuffix $(EXE_EXT),$(addprefix tests/,$(TESTS)))
|
||||
|
||||
.PHONY: test test_outputs test_leaks test_full test_debug
|
||||
|
||||
test: $(_TESTS)
|
||||
@for i in $(_TESTS); do $(call test_single,$$i); done
|
||||
@echo "All tests passed."
|
||||
|
||||
test_leaks: $(_TESTS)
|
||||
@for i in $(_TESTS); do $(call test_single_leaks,$$i); done
|
||||
@echo "All tests passed."
|
||||
|
||||
test_full: clean $(_TESTS)
|
||||
@for i in $(_TESTS); do $(call test_single,$$i); done
|
||||
@for i in $(_TESTS); do $(call test_single_leaks,$$i); done
|
||||
@echo "All tests passed."
|
||||
|
||||
test_debug: tests/$(TEST_TO_DEBUG)
|
||||
ifdef TEST_TO_DEBUG
|
||||
gdb -q --args ./tests/$(TEST_TO_DEBUG)
|
||||
else
|
||||
@echo "Please run with TEST_TO_DEBUG=<test>."
|
||||
@echo "Options are: $(TESTS)"
|
||||
endif
|
||||
|
||||
tests/%: tests/%.c ds.a $(_HDR)
|
||||
$(CC) -o $@ $< ds.a -I./include $(CFLAGS) $(LDFLAGS)
|
||||
|
||||
################################
|
||||
# General #
|
||||
################################
|
||||
.PHONY: clean
|
||||
|
||||
clean:
|
||||
rm -f ds.a $(_OBJ) $(_TESTS)
|
100
include/ds/error.h
Normal file
100
include/ds/error.h
Normal file
@ -0,0 +1,100 @@
|
||||
// Copyright 2022 Darwin Schuppan <darwin@nobrain.org>
|
||||
// SPDX license identifier: MIT
|
||||
|
||||
#ifndef __DS_ERROR_H__
|
||||
#define __DS_ERROR_H__
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* It's pretty important that error messages get through, so we reserve 32kiB
|
||||
* which we can free if malloc fails. Then we should hopefully at least have
|
||||
* enough memory to allocate for the error message. */
|
||||
#define ERROR_RESERVATION_SIZE 32768
|
||||
extern void *error_reserved_for_error;
|
||||
|
||||
/* Some useful shortcuts for constructing errors. */
|
||||
|
||||
/* Essentially just a "no error" error. */
|
||||
#define OK() (Error){ .kind = ErrorNone }
|
||||
/* Use when malloc fails, for example. */
|
||||
#define ERROR_OUT_OF_MEMORY() (Error){ .kind = ErrorOutOfMemory }
|
||||
/* Use for string constants ("this is a string constant"). */
|
||||
#define ERROR_STRING(_str) (Error){ .kind = ErrorString, .str = (char *)_str }
|
||||
/* Use for heap-allocated strings (e.g. by malloc(), strdup() etc.). */
|
||||
#define ERROR_HEAP_STRING(_str) (Error){ .kind = ErrorString, .str = _str, .str_on_heap = true }
|
||||
|
||||
/* Embed the current file and line information in the error message. */
|
||||
#define ERROR_OUT_OF_MEMORY_HERE() (Error){ .kind = ErrorOutOfMemory, .has_location = true, .file = __FILE__, .line = __LINE__ }
|
||||
#define ERROR_STRING_HERE(_str) (Error){ .kind = ErrorString, .str = (char *)_str, .has_location = true, .file = __FILE__, .line = __LINE__ }
|
||||
#define ERROR_HEAP_STRING_HERE(_str) (Error){ .kind = ErrorString, .str = _str, .str_on_heap = true, .has_location = true, .file = __FILE__, .line = __LINE__ }
|
||||
|
||||
/* Embed custom file and line information in the error message. */
|
||||
#define ERROR_OUT_OF_MEMORY_LOCATION(_file, _line) (Error){ .kind = ErrorOutOfMemory, .has_location = true, .file = _file, .line = _line }
|
||||
#define ERROR_STRING_LOCATION(_file, _line, _str) (Error){ .kind = ErrorString, .str = (char *)_str, .has_location = true, .file = _file, .line = _line }
|
||||
#define ERROR_HEAP_STRING_LOCATION(_file, _line, _str) (Error){ .kind = ErrorString, .str = _str, .str_on_heap = true, .has_location = true, .file = _file, .line = _line }
|
||||
|
||||
/* Append one error to another (will show as "error1: error2" in the error message). */
|
||||
#define ERROR_NESTED(base, _annex) (Error){ .kind = base.kind, .str = base.str, .has_annex = true, .annex = _error_heapify(_annex) }
|
||||
|
||||
/* Write the current file and line to an error and return it. */
|
||||
#define ERROR_HEREIFY(_err) _error_hereify(__FILE__, __LINE__, _err)
|
||||
|
||||
/* Propagates any potential errors. Use the otherwise field for any uninitialization code. */
|
||||
#define TRY(expr, otherwise) { Error _err = expr; if (_err.kind != ErrorNone) { otherwise; return _err; } }
|
||||
|
||||
/* If x is an error, this prints a nice error message and then exits. */
|
||||
#define ERROR_ASSERT(x) /* use with moderation */ { \
|
||||
Error _err = x; \
|
||||
if (_err.kind != ErrorNone) { \
|
||||
if (!_err.has_location) { \
|
||||
_err.has_location = true; \
|
||||
_err.file = __FILE__; \
|
||||
_err.line = __LINE__; \
|
||||
} \
|
||||
char _buf[512]; error_to_string(_buf, 512, _err, true); \
|
||||
fprintf(stderr, "Fatal error: %s\n", _buf); \
|
||||
exit(1); \
|
||||
} \
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
ErrorNone = 0,
|
||||
ErrorOutOfMemory,
|
||||
ErrorString,
|
||||
} ErrorKind;
|
||||
|
||||
typedef struct Error {
|
||||
ErrorKind kind;
|
||||
char *str;
|
||||
struct Error *annex;
|
||||
const char *file;
|
||||
size_t line;
|
||||
bool str_on_heap;
|
||||
bool has_annex;
|
||||
bool has_location;
|
||||
} Error;
|
||||
|
||||
/* Returns true if the given error structure actually contains a real error. */
|
||||
static inline bool error_is(Error e) { return e.kind != ErrorNone; }
|
||||
/* Same as error_is but takes a pointer and does NULL checking. */
|
||||
static inline bool error_ptr_is(Error *e) { return e != NULL && e->kind != ErrorNone; }
|
||||
|
||||
/* Call this at the beginning of any program that makes use of any of the more
|
||||
* advanced error features (those which involve heap allocation). */
|
||||
void error_init();
|
||||
/* Call this at the end of any program that used error_init() to clean up. */
|
||||
void error_term();
|
||||
|
||||
/* Writes an error to a string. It is recommended to use ds/fmt.h in most cases, e.g.:
|
||||
#include <ds/fmt.h>;
|
||||
fmt("%{Error:destroy}", err);
|
||||
*/
|
||||
size_t error_to_string(char *buf, size_t size, Error e, bool destroy);
|
||||
|
||||
Error *_error_heapify(Error e);
|
||||
Error _error_hereify(const char *file, size_t line, Error e);
|
||||
|
||||
#endif
|
92
include/ds/fmt.h
Normal file
92
include/ds/fmt.h
Normal file
@ -0,0 +1,92 @@
|
||||
// Copyright 2022 Darwin Schuppan <darwin@nobrain.org>
|
||||
// SPDX license identifier: MIT
|
||||
|
||||
#ifndef __DS_FMT_H__
|
||||
#define __DS_FMT_H__
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <ds/error.h>
|
||||
|
||||
/* Limit definitions. */
|
||||
#define FMT_MAX_ATTRS 8
|
||||
#define FMT_MAX_ATTR_LEN 32 /* including null termination */
|
||||
|
||||
/* An FmtContext defines how and where a print function should write its output.
|
||||
*
|
||||
* putc_func is the fundamental function which "writes" a char in whatever
|
||||
* way the context requires.
|
||||
* ctx_data serves as input, output or both to putc_func.
|
||||
*
|
||||
* For example, in the fmt functions which write to string buffers, ctx_data
|
||||
* holds the buffer, its size and how many bytes were written so far. */
|
||||
typedef struct FmtContext {
|
||||
void *ctx_data;
|
||||
void (*putc_func)(struct FmtContext *restrict ctx, char c);
|
||||
} FmtContext;
|
||||
|
||||
/* FmtAttrs holds attributes that are passed like additional parameters to a
|
||||
* print function. Which attributes are valid is entirely dependent on the
|
||||
* print function and the type being printed.
|
||||
*
|
||||
* len is the number of attributes, names contains the attribute names and
|
||||
* vals contains the attribute values (default is -1 if no value is given).
|
||||
*
|
||||
* For example, when printing an integer, you could use: %{int:p=4,c='0'}, where
|
||||
* p is the width, the rest of which is filled in by c. Resulting in an integer
|
||||
* with a width of at least 4 characters padded by zeroes. Attributes can have
|
||||
* integer or char values, or no value at all (e.g. %{int:X} just sets the "X"
|
||||
* attribute with a default value of -1).
|
||||
*
|
||||
* When creating a custom print function, you have to iterate over the FmtAttrs
|
||||
* manually. See src/ds/fmt.c for reference examples.
|
||||
* */
|
||||
typedef struct FmtAttrs {
|
||||
size_t len;
|
||||
char names[FMT_MAX_ATTRS][FMT_MAX_ATTR_LEN];
|
||||
int vals[FMT_MAX_ATTRS]; /* number/char value */
|
||||
} FmtAttrs;
|
||||
|
||||
/* FmtPrintFuncRet is the return value of a print function. It can either
|
||||
* return no error or specify an unrecognized attribute name. You should
|
||||
* always use the macros FMT_PRINT_FUNC_RET_OK() and
|
||||
* FMT_PRINT_FUNC_RET_INVALID_ATTR() to construct this class in a return
|
||||
* statement. */
|
||||
typedef struct FmtPrintFuncRet {
|
||||
bool invalid_attr;
|
||||
size_t invalid_attr_idx;
|
||||
} FmtPrintFuncRet;
|
||||
|
||||
#define FMT_PRINT_FUNC_RET_OK() (FmtPrintFuncRet){0}
|
||||
#define FMT_PRINT_FUNC_RET_INVALID_ATTR(_idx) (FmtPrintFuncRet){ .invalid_attr = true, .invalid_attr_idx = _idx }
|
||||
|
||||
/* An FmtPrintFunc describes any function that is designed to print a specific
|
||||
* type so fmt functions can output it. */
|
||||
typedef FmtPrintFuncRet (*FmtPrintFunc)(FmtContext *restrict ctx, FmtAttrs *restrict attrs, va_list v);
|
||||
|
||||
/* Add a formatter for your custom types. See src/ds/fmt.c for some examples. */
|
||||
void fmt_register(const char *keyword, FmtPrintFunc print_func);
|
||||
|
||||
void _fmtv(const char *restrict file, size_t line, const char *restrict format, va_list args);
|
||||
void _fmt(const char *restrict file, size_t line, const char *restrict format, ...);
|
||||
size_t _fmtsv(const char *restrict file, size_t line, char *restrict buf, size_t size, const char *restrict format, va_list args);
|
||||
size_t _fmts(const char *restrict file, size_t line, char *restrict buf, size_t size, const char *restrict format, ...);
|
||||
void _fmtcv(const char *restrict file, size_t line, FmtContext *ctx, const char *restrict format, va_list args);
|
||||
void _fmtc(const char *restrict file, size_t line, FmtContext *ctx, const char *restrict format, ...);
|
||||
|
||||
/* Format to stdout. */
|
||||
#define fmtv(format, ...) _fmtv(__FILE__, __LINE__, format, ##__VA_ARGS__)
|
||||
#define fmt(format, ...) _fmt(__FILE__, __LINE__, format, ##__VA_ARGS__)
|
||||
/* Format to char buffer. */
|
||||
#define fmtsv(buf, size, format, ...) _fmtsv(__FILE__, __LINE__, buf, size, format, ##__VA_ARGS__)
|
||||
#define fmts(buf, size, format, ...) _fmts(__FILE__, __LINE__, buf, size, format, ##__VA_ARGS__)
|
||||
/* Format with custom context (e.g. for custom output functions). */
|
||||
#define fmtcv(ctx, format, ...) _fmtcv(__FILE__, __LINE__, ctx, format, ##__VA_ARGS__)
|
||||
#define fmtc(ctx, format, ...) _fmtc(__FILE__, __LINE__, ctx, format, ##__VA_ARGS__)
|
||||
|
||||
void fmt_init();
|
||||
void fmt_term();
|
||||
|
||||
#endif
|
214
include/ds/generic/map.h
Normal file
214
include/ds/generic/map.h
Normal file
@ -0,0 +1,214 @@
|
||||
// Copyright 2022 Darwin Schuppan <darwin@nobrain.org>
|
||||
// SPDX license identifier: MIT
|
||||
|
||||
/* Example Usage:
|
||||
|
||||
// something.h:
|
||||
#define GENERIC_KEY_TYPE int // Key type
|
||||
#define GENERIC_VALUE_TYPE int // Value type
|
||||
#define GENERIC_NAME IntIntMap // Name of the resulting map type
|
||||
#define GENERIC_PREFIX int_int_map // Prefix for functions
|
||||
#include "map.h"
|
||||
|
||||
// something.c:
|
||||
#define GENERIC_IMPL // We want something.c to define the actual function implementations
|
||||
#include "something.h"
|
||||
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <ds/error.h>
|
||||
#include <ds/fmt.h>
|
||||
|
||||
#define GENERIC_REQUIRE_VALUE_TYPE
|
||||
#define GENERIC_REQUIRE_KEY_TYPE
|
||||
#include "../internal/generic/begin.h"
|
||||
|
||||
#define ITEM_TYPE GENERIC_CONCAT(NAME, Item)
|
||||
#define EMPTY 0
|
||||
#define TOMBSTONE 1
|
||||
#define OCCUPIED 2
|
||||
|
||||
typedef struct ITEM_TYPE {
|
||||
unsigned char state;
|
||||
KTYPE key;
|
||||
VTYPE val;
|
||||
} ITEM_TYPE;
|
||||
|
||||
typedef struct NAME {
|
||||
ITEM_TYPE *data;
|
||||
size_t cap, len;
|
||||
} NAME;
|
||||
|
||||
VARDECL(const char *, __val_fmt);
|
||||
VARDECL(const char *, __key_fmt);
|
||||
|
||||
FUNCDECL(NAME, )();
|
||||
FUNCDECL(void, _term)(NAME m);
|
||||
FUNCDECL(void, _fmt_register)(const char *key_fmt, const char *val_fmt);
|
||||
FUNCDECL(VTYPE *, _get)(NAME m, KTYPE key);
|
||||
FUNCDECL(Error, _set)(NAME *m, KTYPE key, VTYPE val);
|
||||
FUNCDECL(bool, _del)(NAME m, KTYPE key);
|
||||
FUNCDECL(Error, _rehash)(NAME *m, size_t new_minimum_cap);
|
||||
FUNCDECL(bool, _it_next)(NAME m, ITEM_TYPE **it);
|
||||
|
||||
#ifdef GENERIC_IMPL
|
||||
VARDEF(const char *, __val_fmt) = NULL;
|
||||
VARDEF(const char *, __key_fmt) = NULL;
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef _GENERIC_MAP_IMPL_ONCE
|
||||
#define _GENERIC_MAP_IMPL_ONCE
|
||||
static size_t _pow_of_2_from_minimum(size_t n) {
|
||||
n--; /* we want to handle the case of n already being a power of 2 */
|
||||
/* We use the leftmost bit set to 1 to also set any bits to its right
|
||||
* to 1. Then we just increment to carry and get our desired power of 2. */
|
||||
n |= n >> 1;
|
||||
n |= n >> 2;
|
||||
n |= n >> 4;
|
||||
n |= n >> 8;
|
||||
n |= n >> 16;
|
||||
#if INTPTR_MAX == INT64_MAX /* only do the last shift on 64-bit systems */
|
||||
n |= n >> 32;
|
||||
#endif
|
||||
return n + 1;
|
||||
}
|
||||
|
||||
static uint32_t _fnv1a32(const void *data, size_t n) {
|
||||
uint32_t res = 2166136261u;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
res ^= ((uint8_t*)data)[i];
|
||||
res *= 16777619u;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
static FUNCDEF(FmtPrintFuncRet, __print_func)(FmtContext *restrict ctx, FmtAttrs *restrict attrs, va_list v) {
|
||||
if (attrs != NULL) {
|
||||
if (attrs->len != 0)
|
||||
return FMT_PRINT_FUNC_RET_INVALID_ATTR(0);
|
||||
}
|
||||
NAME m = va_arg(v, NAME);
|
||||
ctx->putc_func(ctx, '{');
|
||||
ITEM_TYPE *it = NULL;
|
||||
bool first = true;
|
||||
while (FUNC(_it_next)(m, &it)) {
|
||||
if (!first)
|
||||
fmtc(ctx, ", ");
|
||||
fmtc(ctx, VAR(__key_fmt), it->key);
|
||||
fmtc(ctx, ": ");
|
||||
fmtc(ctx, VAR(__val_fmt), it->val);
|
||||
first = false;
|
||||
}
|
||||
ctx->putc_func(ctx, '}');
|
||||
return FMT_PRINT_FUNC_RET_OK();
|
||||
}
|
||||
|
||||
FUNCDEF(NAME, )() {
|
||||
return (NAME){0};
|
||||
}
|
||||
|
||||
FUNCDEF(void, _term)(NAME m) {
|
||||
free(m.data);
|
||||
}
|
||||
|
||||
FUNCDEF(void, _fmt_register)(const char *key_fmt, const char *val_fmt) {
|
||||
VAR(__key_fmt) = key_fmt;
|
||||
VAR(__val_fmt) = val_fmt;
|
||||
fmt_register(NAME_STR, FUNC(__print_func));
|
||||
}
|
||||
|
||||
FUNCDEF(VTYPE *, _get)(NAME m, KTYPE key) {
|
||||
size_t i = _fnv1a32(&key, sizeof(KTYPE)) & (m.cap - 1);
|
||||
while (m.data[i].state != EMPTY) {
|
||||
if (m.data[i].state != TOMBSTONE && memcmp(&m.data[i].key, &key, sizeof(KTYPE)) == 0)
|
||||
return &m.data[i].val;
|
||||
i = (i + 1) % m.cap;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
FUNCDEF(Error, _set)(NAME *m, KTYPE key, VTYPE val) {
|
||||
if (m->cap == 0 || (float)m->len / (float)m->cap > 0.7f)
|
||||
TRY(FUNC(_rehash)(m, m->cap == 0 ? 8 : m->cap * 2), );
|
||||
|
||||
size_t i = _fnv1a32(&key, sizeof(KTYPE)) & (m->cap - 1);
|
||||
while (m->data[i].state != EMPTY) {
|
||||
if (m->data[i].state == TOMBSTONE) {
|
||||
m->data[i].state = OCCUPIED;
|
||||
m->data[i].key = key;
|
||||
m->data[i].val = val;
|
||||
return OK();
|
||||
} else if (memcmp(&m->data[i].key, &key, sizeof(KTYPE)) == 0) {
|
||||
m->data[i].val = val;
|
||||
return OK();
|
||||
}
|
||||
i = (i + 1) % m->cap;
|
||||
}
|
||||
m->data[i].state = OCCUPIED;
|
||||
m->data[i].key = key;
|
||||
m->data[i].val = val;
|
||||
m->len++;
|
||||
return OK();
|
||||
}
|
||||
|
||||
FUNCDEF(bool, _del)(NAME m, KTYPE key) {
|
||||
size_t i = _fnv1a32(&key, sizeof(KTYPE)) & (m.cap - 1);
|
||||
while (m.data[i].state != EMPTY) {
|
||||
if (m.data[i].state != TOMBSTONE && memcmp(&m.data[i].key, &key, sizeof(KTYPE)) == 0) {
|
||||
m.data[i].state = TOMBSTONE;
|
||||
return true;
|
||||
}
|
||||
i = (i + 1) % m.cap;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
FUNCDEF(Error, _rehash)(NAME *m, size_t new_minimum_cap) {
|
||||
size_t new_cap = _pow_of_2_from_minimum(new_minimum_cap > m->len ? new_minimum_cap : m->len);
|
||||
NAME new_m = {
|
||||
.data = malloc(sizeof(ITEM_TYPE) * new_cap),
|
||||
.cap = new_cap,
|
||||
.len = 0,
|
||||
};
|
||||
if (new_m.data == NULL)
|
||||
return ERROR_OUT_OF_MEMORY();
|
||||
for (size_t i = 0; i < new_m.cap; i++)
|
||||
new_m.data[i].state = EMPTY;
|
||||
|
||||
for (size_t i = 0; i < m->cap; i++) {
|
||||
if (m->data[i].state == OCCUPIED) {
|
||||
size_t j = _fnv1a32(&m->data[i].key, sizeof(KTYPE)) & (new_m.cap - 1);
|
||||
while (new_m.data[j].state != EMPTY) { j = (j + 1) % new_m.cap; }
|
||||
new_m.data[j].state = OCCUPIED;
|
||||
new_m.data[j].key = m->data[i].key;
|
||||
new_m.data[j].val = m->data[i].val;
|
||||
new_m.len++;
|
||||
}
|
||||
}
|
||||
|
||||
free(m->data);
|
||||
*m = new_m;
|
||||
return OK();
|
||||
}
|
||||
|
||||
FUNCDEF(bool, _it_next)(NAME m, ITEM_TYPE **it) {
|
||||
*it == NULL ? *it = m.data : (*it)++;
|
||||
while (*it < m.data + m.cap && (*it)->state != OCCUPIED) { (*it)++; }
|
||||
return *it < m.data + m.cap;
|
||||
}
|
||||
#endif
|
||||
|
||||
#undef ITEM_TYPE
|
||||
#undef EMPTY
|
||||
#undef TOMBSTONE
|
||||
#undef OCCUPIED
|
||||
|
||||
#include "../internal/generic/end.h"
|
211
include/ds/generic/smap.h
Normal file
211
include/ds/generic/smap.h
Normal file
@ -0,0 +1,211 @@
|
||||
// Copyright 2022 Darwin Schuppan <darwin@nobrain.org>
|
||||
// SPDX license identifier: MIT
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <ds/error.h>
|
||||
#include <ds/fmt.h>
|
||||
#include <ds/string.h>
|
||||
|
||||
/* Example Usage:
|
||||
|
||||
// something.h:
|
||||
#define GENERIC_TYPE int // Value type
|
||||
#define GENERIC_NAME StringIntMap // Name of the resulting map type
|
||||
#define GENERIC_PREFIX string_int_map // Prefix for functions
|
||||
#include "smap.h"
|
||||
|
||||
// something.c:
|
||||
#define GENERIC_IMPL // We want something.c to define the actual function implementations
|
||||
#include "something.h"
|
||||
|
||||
*/
|
||||
|
||||
#define GENERIC_REQUIRE_TYPE
|
||||
#include "../internal/generic/begin.h"
|
||||
#define ITEM_TYPE GENERIC_CONCAT(NAME, Item)
|
||||
#define TOMBSTONE ((char*)UINTPTR_MAX)
|
||||
|
||||
typedef struct ITEM_TYPE {
|
||||
char *key;
|
||||
TYPE val;
|
||||
} ITEM_TYPE;
|
||||
|
||||
typedef struct NAME {
|
||||
ITEM_TYPE *data;
|
||||
size_t cap, len;
|
||||
} NAME;
|
||||
|
||||
VARDECL(const char *, __val_fmt);
|
||||
|
||||
FUNCDECL(NAME, )();
|
||||
FUNCDECL(void, _term)(NAME m);
|
||||
FUNCDECL(void, _fmt_register)(const char *val_fmt);
|
||||
FUNCDECL(TYPE *, _get)(NAME m, const char *key);
|
||||
FUNCDECL(Error, _set)(NAME *m, const char *key, TYPE val);
|
||||
FUNCDECL(bool, _del)(NAME m, const char *key);
|
||||
FUNCDECL(Error, _rehash)(NAME *m, size_t new_minimum_cap);
|
||||
FUNCDECL(bool, _it_next)(NAME m, ITEM_TYPE **it);
|
||||
|
||||
#ifdef GENERIC_IMPL
|
||||
VARDEF(const char *, __val_fmt) = NULL;
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef _GENERIC_MAP_IMPL_ONCE
|
||||
#define _GENERIC_MAP_IMPL_ONCE
|
||||
static size_t _pow_of_2_from_minimum(size_t n) {
|
||||
n--; /* we want to handle the case of n already being a power of 2 */
|
||||
/* We use the leftmost bit set to 1 to also set any bits to its right
|
||||
* to 1. Then we just increment to carry and get our desired power of 2. */
|
||||
n |= n >> 1;
|
||||
n |= n >> 2;
|
||||
n |= n >> 4;
|
||||
n |= n >> 8;
|
||||
n |= n >> 16;
|
||||
#if INTPTR_MAX == INT64_MAX /* only do the last shift on 64-bit systems */
|
||||
n |= n >> 32;
|
||||
#endif
|
||||
return n + 1;
|
||||
}
|
||||
|
||||
static uint32_t _fnv1a32(const void *data, size_t n) {
|
||||
uint32_t res = 2166136261u;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
res ^= ((uint8_t*)data)[i];
|
||||
res *= 16777619u;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
static FUNCDEF(FmtPrintFuncRet, __print_func)(FmtContext *restrict ctx, FmtAttrs *restrict attrs, va_list v) {
|
||||
if (attrs != NULL) {
|
||||
if (attrs->len != 0)
|
||||
return FMT_PRINT_FUNC_RET_INVALID_ATTR(0);
|
||||
}
|
||||
NAME m = va_arg(v, NAME);
|
||||
ctx->putc_func(ctx, '{');
|
||||
ITEM_TYPE *it = NULL;
|
||||
bool first = true;
|
||||
while (FUNC(_it_next)(m, &it)) {
|
||||
if (!first)
|
||||
fmtc(ctx, ", ");
|
||||
fmtc(ctx, "\"%s\": ", it->key);
|
||||
fmtc(ctx, VAR(__val_fmt), it->val);
|
||||
first = false;
|
||||
}
|
||||
ctx->putc_func(ctx, '}');
|
||||
return FMT_PRINT_FUNC_RET_OK();
|
||||
}
|
||||
|
||||
FUNCDEF(NAME, )() {
|
||||
return (NAME){0};
|
||||
}
|
||||
|
||||
FUNCDEF(void, _term)(NAME m) {
|
||||
for (size_t i = 0; i < m.cap; i++) {
|
||||
if (m.data[i].key && m.data[i].key != TOMBSTONE)
|
||||
free(m.data[i].key);
|
||||
}
|
||||
free(m.data);
|
||||
}
|
||||
|
||||
FUNCDEF(void, _fmt_register)(const char *val_fmt) {
|
||||
VAR(__val_fmt) = val_fmt;
|
||||
fmt_register(NAME_STR, FUNC(__print_func));
|
||||
}
|
||||
|
||||
FUNCDEF(TYPE *, _get)(NAME m, const char *key) {
|
||||
size_t i = _fnv1a32(key, strlen(key)) & (m.cap - 1);
|
||||
while (m.data[i].key) {
|
||||
if (m.data[i].key != TOMBSTONE && strcmp(m.data[i].key, key) == 0)
|
||||
return &m.data[i].val;
|
||||
i = (i + 1) % m.cap;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
FUNCDEF(Error, _set)(NAME *m, const char *key, TYPE val) {
|
||||
if (m->cap == 0 || (float)m->len / (float)m->cap > 0.7f)
|
||||
TRY(FUNC(_rehash)(m, m->cap == 0 ? 8 : m->cap * 2), );
|
||||
|
||||
size_t i = _fnv1a32(key, strlen(key)) & (m->cap - 1);
|
||||
while (m->data[i].key) {
|
||||
if (m->data[i].key == TOMBSTONE) {
|
||||
char *new_key = strdup(key);
|
||||
if (new_key == NULL)
|
||||
return ERROR_OUT_OF_MEMORY();
|
||||
m->data[i].key = new_key;
|
||||
m->data[i].val = val;
|
||||
return OK();
|
||||
} else if (strcmp(m->data[i].key, key) == 0) {
|
||||
m->data[i].val = val;
|
||||
return OK();
|
||||
}
|
||||
i = (i + 1) % m->cap;
|
||||
}
|
||||
char *new_key = strdup(key);
|
||||
if (new_key == NULL)
|
||||
return ERROR_OUT_OF_MEMORY();
|
||||
m->data[i].key = new_key;
|
||||
m->data[i].val = val;
|
||||
m->len++;
|
||||
return OK();
|
||||
}
|
||||
|
||||
FUNCDEF(bool, _del)(NAME m, const char *key) {
|
||||
size_t i = _fnv1a32(key, strlen(key)) & (m.cap - 1);
|
||||
while (m.data[i].key) {
|
||||
if (m.data[i].key != TOMBSTONE && strcmp(m.data[i].key, key) == 0) {
|
||||
free(m.data[i].key);
|
||||
m.data[i].key = TOMBSTONE;
|
||||
return true;
|
||||
}
|
||||
i = (i + 1) % m.cap;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
FUNCDEF(Error, _rehash)(NAME *m, size_t new_minimum_cap) {
|
||||
size_t new_cap = _pow_of_2_from_minimum(new_minimum_cap > m->len ? new_minimum_cap : m->len);
|
||||
NAME new_m = {
|
||||
.data = malloc(sizeof(ITEM_TYPE) * new_cap),
|
||||
.cap = new_cap,
|
||||
.len = 0,
|
||||
};
|
||||
if (new_m.data == NULL)
|
||||
return ERROR_OUT_OF_MEMORY();
|
||||
for (size_t i = 0; i < new_m.cap; i++)
|
||||
new_m.data[i].key = NULL;
|
||||
|
||||
for (size_t i = 0; i < m->cap; i++) {
|
||||
if (m->data[i].key && m->data[i].key != TOMBSTONE) {
|
||||
size_t j = _fnv1a32(m->data[i].key, strlen(m->data[i].key)) & (new_m.cap - 1);
|
||||
while (new_m.data[j].key) { j = (j + 1) % new_m.cap; }
|
||||
new_m.data[j].key = m->data[i].key;
|
||||
new_m.data[j].val = m->data[i].val;
|
||||
new_m.len++;
|
||||
}
|
||||
}
|
||||
|
||||
free(m->data);
|
||||
*m = new_m;
|
||||
return OK();
|
||||
}
|
||||
|
||||
FUNCDEF(bool, _it_next)(NAME m, ITEM_TYPE **it) {
|
||||
*it == NULL ? *it = m.data : (*it)++;
|
||||
while (*it < m.data + m.cap && (!(*it)->key || (*it)->key == TOMBSTONE)) { (*it)++; }
|
||||
return *it < m.data + m.cap;
|
||||
}
|
||||
#endif
|
||||
|
||||
#undef ITEM_TYPE
|
||||
#undef TOMBSTONE
|
||||
|
||||
#include "../internal/generic/end.h"
|
156
include/ds/generic/vec.h
Normal file
156
include/ds/generic/vec.h
Normal file
@ -0,0 +1,156 @@
|
||||
// Copyright 2022 Darwin Schuppan <darwin@nobrain.org>
|
||||
// SPDX license identifier: MIT
|
||||
|
||||
/* Example Usage:
|
||||
|
||||
// something.h:
|
||||
#define GENERIC_TYPE int // Key type
|
||||
#define GENERIC_NAME IntVec // Name of the resulting vector type
|
||||
#define GENERIC_PREFIX int_vec // Prefix for functions
|
||||
#include "vec.h"
|
||||
|
||||
// something.c:
|
||||
#define GENERIC_IMPL // We want something.c to define the actual function implementations
|
||||
#include "something.h"
|
||||
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <ds/error.h>
|
||||
#include <ds/fmt.h>
|
||||
|
||||
#define GENERIC_REQUIRE_TYPE
|
||||
#include "../internal/generic/begin.h"
|
||||
|
||||
#ifndef _GENERIC_VEC_ONCE
|
||||
#define _GENERIC_VEC_ONCE
|
||||
size_t vec_len(void *v);
|
||||
size_t vec_cap(void *v);
|
||||
#endif
|
||||
|
||||
typedef TYPE *NAME;
|
||||
|
||||
VARDECL(const char *, __val_fmt);
|
||||
|
||||
FUNCDECL(NAME, )();
|
||||
FUNCDECL(void, _term)(NAME v);
|
||||
FUNCDECL(void, _fmt_register)(const char *val_fmt);
|
||||
static inline FUNCDEF(size_t, _len)(NAME v) { return vec_len((void*)v); }
|
||||
static inline FUNCDEF(size_t, _cap)(NAME v) { return vec_cap((void*)v); }
|
||||
FUNCDECL(size_t, _len)(NAME v);
|
||||
FUNCDECL(size_t, _cap)(NAME v);
|
||||
FUNCDECL(Error, _fit)(NAME *v, size_t new_minimum_cap);
|
||||
FUNCDECL(Error, _push)(NAME *v, TYPE val);
|
||||
FUNCDECL(TYPE, _pop)(NAME v);
|
||||
FUNCDECL(TYPE *, _back)(NAME v);
|
||||
FUNCDECL(TYPE, _del)(NAME v, size_t idx);
|
||||
FUNCDECL(Error, _insert)(NAME *v, size_t idx, TYPE val);
|
||||
|
||||
#ifdef GENERIC_IMPL
|
||||
VARDEF(const char *, __val_fmt) = NULL;
|
||||
|
||||
#define _VEC_HEADER(vec) ((_VecHeader*)(vec) - 1)
|
||||
|
||||
#ifndef _GENERIC_VEC_IMPL_ONCE
|
||||
#define _GENERIC_VEC_IMPL_ONCE
|
||||
typedef struct _VecHeader {
|
||||
size_t cap, len;
|
||||
} _VecHeader;
|
||||
|
||||
size_t vec_len(void *v) {
|
||||
return v == NULL ? 0 : _VEC_HEADER(v)->len;
|
||||
}
|
||||
|
||||
size_t vec_cap(void *v) {
|
||||
return v == NULL ? 0 : _VEC_HEADER(v)->cap;
|
||||
}
|
||||
#endif
|
||||
|
||||
static FUNCDEF(FmtPrintFuncRet, __print_func)(FmtContext *restrict ctx, FmtAttrs *restrict attrs, va_list v) {
|
||||
if (attrs != NULL) {
|
||||
if (attrs->len != 0)
|
||||
return FMT_PRINT_FUNC_RET_INVALID_ATTR(0);
|
||||
}
|
||||
NAME vec = va_arg(v, NAME);
|
||||
ctx->putc_func(ctx, '{');
|
||||
for (size_t i = 0; i < vec_len(vec); i++) {
|
||||
if (i != 0)
|
||||
fmtc(ctx, ", ");
|
||||
fmtc(ctx, VAR(__val_fmt), vec[i]);
|
||||
}
|
||||
ctx->putc_func(ctx, '}');
|
||||
return FMT_PRINT_FUNC_RET_OK();
|
||||
}
|
||||
|
||||
FUNCDEF(NAME, )() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
FUNCDEF(void, _term)(NAME v) {
|
||||
free(_VEC_HEADER(v));
|
||||
}
|
||||
|
||||
FUNCDEF(void, _fmt_register)(const char *val_fmt) {
|
||||
VAR(__val_fmt) = val_fmt;
|
||||
fmt_register(NAME_STR, FUNC(__print_func));
|
||||
}
|
||||
|
||||
FUNCDEF(Error, _fit)(NAME *v, size_t new_minimum_cap) {
|
||||
size_t new_cap;
|
||||
size_t len;
|
||||
_VecHeader *h;
|
||||
if (*v == NULL) {
|
||||
h = NULL;
|
||||
new_cap = new_minimum_cap;
|
||||
len = 0;
|
||||
} else {
|
||||
h = _VEC_HEADER(*v);
|
||||
new_cap = new_minimum_cap < h->len ? h->len : new_minimum_cap;
|
||||
len = h->len;
|
||||
}
|
||||
_VecHeader *new_h = realloc(h, sizeof(_VecHeader) + sizeof(TYPE) * new_cap);
|
||||
if (new_h == NULL)
|
||||
return ERROR_OUT_OF_MEMORY();
|
||||
new_h->len = len;
|
||||
new_h->cap = new_minimum_cap;
|
||||
*v = (NAME)(new_h + 1);
|
||||
return OK();
|
||||
}
|
||||
|
||||
FUNCDEF(Error, _push)(NAME *v, TYPE val) {
|
||||
if (vec_len(*v) + 1 > vec_cap(*v))
|
||||
TRY(FUNC(_fit)(v, vec_cap(*v) == 0 ? 8 : vec_cap(*v) * 2), );
|
||||
(*v)[_VEC_HEADER(*v)->len++] = val;
|
||||
return OK();
|
||||
}
|
||||
|
||||
FUNCDEF(TYPE, _pop)(NAME v) {
|
||||
return v[--_VEC_HEADER(v)->len];
|
||||
}
|
||||
|
||||
FUNCDEF(TYPE *, _back)(NAME v) {
|
||||
return &v[vec_len(v) - 1];
|
||||
}
|
||||
|
||||
FUNCDEF(TYPE, _del)(NAME v, size_t idx) {
|
||||
TYPE val = v[idx];
|
||||
memmove(v + idx, v + idx + 1, sizeof(TYPE) * (--_VEC_HEADER(v)->len - idx));
|
||||
return val;
|
||||
}
|
||||
|
||||
FUNCDEF(Error, _insert)(NAME *v, size_t idx, TYPE val) {
|
||||
if (vec_len(*v) + 1 > vec_cap(*v))
|
||||
TRY(FUNC(_fit)(v, vec_cap(*v) == 0 ? 8 : vec_cap(*v) * 2), );
|
||||
memmove(*v + idx + 1, *v + idx, sizeof(TYPE) * (_VEC_HEADER(*v)->len++ - idx));
|
||||
(*v)[idx] = val;
|
||||
return OK();
|
||||
}
|
||||
|
||||
#undef _VEC_HEADER
|
||||
|
||||
#endif
|
||||
|
||||
#include "../internal/generic/end.h"
|
70
include/ds/internal/generic/begin.h
Normal file
70
include/ds/internal/generic/begin.h
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright 2022 Darwin Schuppan <darwin@nobrain.org>
|
||||
// SPDX license identifier: MIT
|
||||
|
||||
#if !defined(GENERIC_NAME) || !defined(GENERIC_PREFIX)
|
||||
#error GENERIC_NAME and GENERIC_PREFIX must be defined before including a header for defining a generic class
|
||||
#endif
|
||||
|
||||
#define _GENERIC_CONCAT(pfx, x) pfx##x
|
||||
#define GENERIC_CONCAT(pfx, x) _GENERIC_CONCAT(pfx, x)
|
||||
|
||||
#define _GENERIC_STRINGIZE(s) #s
|
||||
#define GENERIC_STRINGIZE(s) _GENERIC_STRINGIZE(s)
|
||||
|
||||
#define NAME GENERIC_NAME
|
||||
#define NAME_STR GENERIC_STRINGIZE(NAME)
|
||||
|
||||
#define FUNC(name) GENERIC_CONCAT(GENERIC_PREFIX, name)
|
||||
#define VAR(name) GENERIC_CONCAT(GENERIC_PREFIX, name)
|
||||
#ifdef GENERIC_IMPL_STATIC
|
||||
#define GENERIC_IMPL
|
||||
#define FUNCDECL(ret_type, name) __attribute__((unused)) static ret_type FUNC(name)
|
||||
#define FUNCDEF(ret_type, name) __attribute__((unused)) ret_type FUNC(name)
|
||||
#define VARDECL(type, name) static type VAR(name)
|
||||
#define VARDEF(type, name) static type VAR(name)
|
||||
#else
|
||||
#define FUNCDECL(ret_type, name) ret_type FUNC(name)
|
||||
#define FUNCDEF(ret_type, name) ret_type FUNC(name)
|
||||
#define VARDECL(type, name) extern type VAR(name)
|
||||
#define VARDEF(type, name) type VAR(name)
|
||||
#endif
|
||||
|
||||
#ifdef GENERIC_REQUIRE_TYPE
|
||||
#define GENERIC_ALLOW_TYPE
|
||||
#endif
|
||||
#ifdef GENERIC_REQUIRE_VALUE_TYPE
|
||||
#define GENERIC_ALLOW_VALUE_TYPE
|
||||
#endif
|
||||
#ifdef GENERIC_REQUIRE_KEY_TYPE
|
||||
#define GENERIC_ALLOW_KEY_TYPE
|
||||
#endif
|
||||
|
||||
#if defined(GENERIC_REQUIRE_TYPE) && !defined(GENERIC_TYPE)
|
||||
#error Defining GENERIC_TYPE is required for defining this generic class
|
||||
#endif
|
||||
#if defined(GENERIC_REQUIRE_VALUE_TYPE) && !defined(GENERIC_VALUE_TYPE)
|
||||
#error Defining GENERIC_VALUE_TYPE is required for defining this generic class
|
||||
#endif
|
||||
#if defined(GENERIC_REQUIRE_KEY_TYPE) && !defined(GENERIC_KEY_TYPE)
|
||||
#error Defining GENERIC_KEY_TYPE is required for defining this generic class
|
||||
#endif
|
||||
|
||||
#if !defined(GENERIC_ALLOW_TYPE) && defined(GENERIC_TYPE)
|
||||
#error Defining GENERIC_TYPE is not allowed for defining this generic class
|
||||
#endif
|
||||
#if !defined(GENERIC_ALLOW_VALUE_TYPE) && defined(GENERIC_VALUE_TYPE)
|
||||
#error Defining GENERIC_VALUE_TYPE is not allowed for defining this generic class
|
||||
#endif
|
||||
#if !defined(GENERIC_ALLOW_KEY_TYPE) && defined(GENERIC_KEY_TYPE)
|
||||
#error Defining GENERIC_KEY_TYPE is not allowed for defining this generic class
|
||||
#endif
|
||||
|
||||
#if defined(GENERIC_TYPE)
|
||||
#define TYPE GENERIC_TYPE
|
||||
#endif
|
||||
#if defined(GENERIC_VALUE_TYPE)
|
||||
#define VTYPE GENERIC_VALUE_TYPE
|
||||
#endif
|
||||
#if defined(GENERIC_KEY_TYPE)
|
||||
#define KTYPE GENERIC_KEY_TYPE
|
||||
#endif
|
53
include/ds/internal/generic/end.h
Normal file
53
include/ds/internal/generic/end.h
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright 2022 Darwin Schuppan <darwin@nobrain.org>
|
||||
// SPDX license identifier: MIT
|
||||
|
||||
#if defined(GENERIC_TYPE)
|
||||
#undef TYPE
|
||||
#undef GENERIC_TYPE
|
||||
#endif
|
||||
#if defined(GENERIC_VALUE_TYPE)
|
||||
#undef VTYPE
|
||||
#undef GENERIC_VALUE_TYPE
|
||||
#endif
|
||||
#if defined(GENERIC_KEY_TYPE)
|
||||
#undef KTYPE
|
||||
#undef GENERIC_KEY_TYPE
|
||||
#endif
|
||||
|
||||
#ifdef GENERIC_REQUIRE_TYPE
|
||||
#undef GENERIC_REQUIRE_TYPE
|
||||
#endif
|
||||
#ifdef GENERIC_REQUIRE_VALUE_TYPE
|
||||
#undef GENERIC_REQUIRE_VALUE_TYPE
|
||||
#endif
|
||||
#ifdef GENERIC_REQUIRE_KEY_TYPE
|
||||
#undef GENERIC_REQUIRE_KEY_TYPE
|
||||
#endif
|
||||
|
||||
#ifdef GENERIC_ALLOW_TYPE
|
||||
#undef GENERIC_ALLOW_TYPE
|
||||
#endif
|
||||
#ifdef GENERIC_ALLOW_VALUE_TYPE
|
||||
#undef GENERIC_ALLOW_VALUE_TYPE
|
||||
#endif
|
||||
#ifdef GENERIC_ALLOW_KEY_TYPE
|
||||
#undef GENERIC_ALLOW_KEY_TYPE
|
||||
#endif
|
||||
|
||||
#undef GENERIC_PREFIX
|
||||
#undef FUNC
|
||||
#undef FUNCDECL
|
||||
#undef FUNCDEF
|
||||
#undef VAR
|
||||
#undef VARDECL
|
||||
#undef VARDEF
|
||||
|
||||
#undef NAME
|
||||
#undef NAME_STR
|
||||
#undef GENERIC_NAME
|
||||
|
||||
#undef _GENERIC_STRINGIZE
|
||||
#undef GENERIC_STRINGIZE
|
||||
|
||||
#undef _GENERIC_CONCAT
|
||||
#undef GENERIC_CONCAT
|
11
include/ds/string.h
Normal file
11
include/ds/string.h
Normal file
@ -0,0 +1,11 @@
|
||||
// Copyright 2022 Darwin Schuppan <darwin@nobrain.org>
|
||||
// SPDX license identifier: MIT
|
||||
|
||||
#ifndef __DS_STRING_H__
|
||||
#define __DS_STRING_H__
|
||||
|
||||
#ifndef strdup /* we may want to replace strdup for testing */
|
||||
char *strdup(const char *s);
|
||||
#endif
|
||||
|
||||
#endif
|
15
include/ds/types.h
Normal file
15
include/ds/types.h
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright 2022 Darwin Schuppan <darwin@nobrain.org>
|
||||
// SPDX license identifier: MIT
|
||||
|
||||
#ifndef __DS_TYPE_H__
|
||||
#define __DS_TYPE_H__
|
||||
|
||||
/* ssize_t */
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
typedef SSIZE_T ssize_t;
|
||||
#else
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
#endif
|
85
src/ds/error.c
Normal file
85
src/ds/error.c
Normal file
@ -0,0 +1,85 @@
|
||||
// Copyright 2022 Darwin Schuppan <darwin@nobrain.org>
|
||||
// SPDX license identifier: MIT
|
||||
|
||||
#include <ds/error.h>
|
||||
|
||||
void *error_reserved_for_error = NULL;
|
||||
|
||||
void error_init() {
|
||||
if (error_reserved_for_error != NULL) {
|
||||
fprintf(stderr, "error: error_init() can only be called once.\n");
|
||||
exit(1);
|
||||
}
|
||||
error_reserved_for_error = malloc(ERROR_RESERVATION_SIZE);
|
||||
}
|
||||
|
||||
void error_term() {
|
||||
free(error_reserved_for_error);
|
||||
}
|
||||
|
||||
size_t error_to_string(char *buf, size_t size, Error e, bool destroy) {
|
||||
size_t written = 0;
|
||||
if (e.has_location) {
|
||||
size_t left = size > written ? size - written : 0;
|
||||
written += snprintf(buf + written, left, "%s:%zu: ", e.file, e.line);
|
||||
}
|
||||
switch (e.kind) {
|
||||
case ErrorNone: {
|
||||
size_t left = size > written ? size - written : 0;
|
||||
written += snprintf(buf + written, left, "Success");
|
||||
}
|
||||
break;
|
||||
case ErrorOutOfMemory: {
|
||||
size_t left = size > written ? size - written : 0;
|
||||
written += snprintf(buf + written, left, "Out of memory");
|
||||
}
|
||||
break;
|
||||
case ErrorString: {
|
||||
size_t left = size > written ? size - written : 0;
|
||||
written += snprintf(buf + written, left, "%s", e.str);
|
||||
if (e.str_on_heap && destroy)
|
||||
free(e.str);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (e.has_annex) {
|
||||
{
|
||||
size_t left = size > written ? size - written : 0;
|
||||
written += snprintf(buf + written, left, ": ");
|
||||
}
|
||||
{
|
||||
size_t left = size > written ? size - written : 0;
|
||||
written += error_to_string(buf + written, left, *e.annex, destroy);
|
||||
}
|
||||
if (destroy)
|
||||
free(e.annex);
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
Error *_error_heapify(Error e) {
|
||||
if (error_reserved_for_error == NULL) {
|
||||
fprintf(stderr, "error: error_init() must be called at the beginning of the program before working with heap-allocated errors.\n");
|
||||
return NULL;
|
||||
}
|
||||
Error *res = malloc(sizeof(*res));
|
||||
if (res == NULL) {
|
||||
free(error_reserved_for_error);
|
||||
error_reserved_for_error = NULL;
|
||||
res = malloc(sizeof(*res));
|
||||
if (res == NULL) {
|
||||
/* we should now have enough memory, if not we're seriously fucked */
|
||||
fprintf(stderr, "error: Could not obtain enough memory to construct the appropriate error.");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
*res = e;
|
||||
return res;
|
||||
}
|
||||
|
||||
Error _error_hereify(const char *file, size_t line, Error e) {
|
||||
e.has_location = true;
|
||||
e.file = file;
|
||||
e.line = line;
|
||||
return e;
|
||||
}
|
580
src/ds/fmt.c
Normal file
580
src/ds/fmt.c
Normal file
@ -0,0 +1,580 @@
|
||||
// Copyright 2022 Darwin Schuppan <darwin@nobrain.org>
|
||||
// SPDX license identifier: MIT
|
||||
|
||||
#define GENERIC_IMPL_STATIC
|
||||
|
||||
#include <ds/fmt.h>
|
||||
#include <ds/types.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define GENERIC_TYPE FmtPrintFunc
|
||||
#define GENERIC_NAME _PrintFuncMap
|
||||
#define GENERIC_PREFIX _print_func_map
|
||||
#include <ds/generic/smap.h>
|
||||
|
||||
/* Contains all builtin and custom formatting functions. */
|
||||
static _PrintFuncMap _print_funcs;
|
||||
/* Whether the fmt library was initialized. */
|
||||
static bool initialized = false;
|
||||
|
||||
/********************************\
|
||||
|* Builtin fmt Functions *|
|
||||
\********************************/
|
||||
static FmtPrintFuncRet _print_string_func(FmtContext *restrict ctx, FmtAttrs *restrict attrs, va_list v) {
|
||||
int pad = 0, padchar = ' ';
|
||||
if (attrs != NULL) {
|
||||
size_t i;
|
||||
for (i = 0; i < attrs->len; i++) {
|
||||
if (strcmp(attrs->names[i], "p") == 0) {
|
||||
pad = attrs->vals[i];
|
||||
} else if (strcmp(attrs->names[i], "c") == 0) {
|
||||
padchar = attrs->vals[i];
|
||||
} else {
|
||||
return FMT_PRINT_FUNC_RET_INVALID_ATTR(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
const char *s = va_arg(v, const char *);
|
||||
if (s == NULL)
|
||||
s = "(null)";
|
||||
size_t len = strlen(s);
|
||||
if (pad > len) {
|
||||
size_t j;
|
||||
for (j = 0; j < pad - len; j++)
|
||||
ctx->putc_func(ctx, padchar);
|
||||
}
|
||||
while (*s != 0)
|
||||
ctx->putc_func(ctx, *s++);
|
||||
return FMT_PRINT_FUNC_RET_OK();
|
||||
}
|
||||
|
||||
/* Helper function to print any integer. */
|
||||
static FmtPrintFuncRet _print_integer(FmtContext *restrict ctx, FmtAttrs *restrict attrs, unsigned long long int val, bool negative) {
|
||||
int pad = 0, padchar = ' ', base = 10;
|
||||
bool uppercase = false;
|
||||
if (attrs != NULL) {
|
||||
size_t i;
|
||||
for (i = 0; i < attrs->len; i++) {
|
||||
if (strcmp(attrs->names[i], "X") == 0) {
|
||||
uppercase = true;
|
||||
base = 16;
|
||||
} else if (strcmp(attrs->names[i], "x") == 0) {
|
||||
uppercase = false;
|
||||
base = 16;
|
||||
} else if (strcmp(attrs->names[i], "o") == 0) {
|
||||
base = 8;
|
||||
} else if (strcmp(attrs->names[i], "p") == 0) {
|
||||
pad = attrs->vals[i];
|
||||
} else if (strcmp(attrs->names[i], "c") == 0) {
|
||||
padchar = attrs->vals[i];
|
||||
} else if (strcmp(attrs->names[i], "b") == 0) {
|
||||
base = attrs->vals[i];
|
||||
} else {
|
||||
return FMT_PRINT_FUNC_RET_INVALID_ATTR(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (base < 2 || base > 16)
|
||||
base = 10;
|
||||
size_t i = 0;
|
||||
char buf[65]; /* max: 64-bit binary number with negative sign */
|
||||
if (val == 0)
|
||||
buf[i++] = '0';
|
||||
else {
|
||||
char a_begin = uppercase ? 'A' : 'a';
|
||||
while (val != 0) {
|
||||
unsigned long long int rem = val % base;
|
||||
buf[i++] = rem > 9 ? (rem - 10) + a_begin : rem + '0';
|
||||
val /= base;
|
||||
}
|
||||
if (negative)
|
||||
buf[i++] = '-';
|
||||
}
|
||||
if (pad > i) {
|
||||
size_t j;
|
||||
for (j = 0; j < pad - i; j++)
|
||||
ctx->putc_func(ctx, padchar);
|
||||
}
|
||||
while (i--)
|
||||
ctx->putc_func(ctx, buf[i]);
|
||||
return FMT_PRINT_FUNC_RET_OK();
|
||||
}
|
||||
|
||||
static FmtPrintFuncRet _print_int_func(FmtContext *restrict ctx, FmtAttrs *restrict attrs, va_list v) {
|
||||
int val = va_arg(v, int);
|
||||
if (val < 0)
|
||||
return _print_integer(ctx, attrs, -val, true);
|
||||
else
|
||||
return _print_integer(ctx, attrs, val, false);
|
||||
}
|
||||
|
||||
static FmtPrintFuncRet _print_lint_func(FmtContext *restrict ctx, FmtAttrs *restrict attrs, va_list v) {
|
||||
long int val = va_arg(v, long int);
|
||||
if (val < 0)
|
||||
return _print_integer(ctx, attrs, -val, true);
|
||||
else
|
||||
return _print_integer(ctx, attrs, val, false);
|
||||
}
|
||||
|
||||
static FmtPrintFuncRet _print_llint_func(FmtContext *restrict ctx, FmtAttrs *restrict attrs, va_list v) {
|
||||
long long int val = va_arg(v, long long int);
|
||||
if (val < 0)
|
||||
return _print_integer(ctx, attrs, -val, true);
|
||||
else
|
||||
return _print_integer(ctx, attrs, val, false);
|
||||
}
|
||||
|
||||
static FmtPrintFuncRet _print_uint_func(FmtContext *restrict ctx, FmtAttrs *restrict attrs, va_list v) {
|
||||
return _print_integer(ctx, attrs, va_arg(v, unsigned int), false);
|
||||
}
|
||||
|
||||
static FmtPrintFuncRet _print_ulint_func(FmtContext *restrict ctx, FmtAttrs *restrict attrs, va_list v) {
|
||||
return _print_integer(ctx, attrs, va_arg(v, unsigned long int), false);
|
||||
}
|
||||
|
||||
static FmtPrintFuncRet _print_ullint_func(FmtContext *restrict ctx, FmtAttrs *restrict attrs, va_list v) {
|
||||
return _print_integer(ctx, attrs, va_arg(v, unsigned long long int), false);
|
||||
}
|
||||
|
||||
static FmtPrintFuncRet _print_size_t_func(FmtContext *restrict ctx, FmtAttrs *restrict attrs, va_list v) {
|
||||
return _print_integer(ctx, attrs, va_arg(v, size_t), false);
|
||||
}
|
||||
|
||||
static FmtPrintFuncRet _print_ssize_t_func(FmtContext *restrict ctx, FmtAttrs *restrict attrs, va_list v) {
|
||||
return _print_integer(ctx, attrs, va_arg(v, ssize_t), false);
|
||||
}
|
||||
|
||||
static void _print_error(FmtContext *restrict ctx, Error val, bool destroy) {
|
||||
if (val.has_location) {
|
||||
fmtc(ctx, "%s:%zu: ", val.file, val.line);
|
||||
}
|
||||
switch (val.kind) {
|
||||
case ErrorNone:
|
||||
fmtc(ctx, "Success");
|
||||
break;
|
||||
case ErrorOutOfMemory:
|
||||
fmtc(ctx, "Out of memory");
|
||||
break;
|
||||
case ErrorString:
|
||||
fmtc(ctx, "%s", val.str);
|
||||
if (val.str_on_heap && destroy)
|
||||
free(val.str);
|
||||
break;
|
||||
}
|
||||
if (val.has_annex) {
|
||||
fmtc(ctx, ": ");
|
||||
_print_error(ctx, *val.annex, destroy);
|
||||
if (destroy)
|
||||
free(val.annex);
|
||||
}
|
||||
}
|
||||
|
||||
static FmtPrintFuncRet _print_error_func(FmtContext *restrict ctx, FmtAttrs *restrict attrs, va_list v) {
|
||||
bool destroy = false;
|
||||
if (attrs != NULL) {
|
||||
size_t i;
|
||||
for (i = 0; i < attrs->len; i++) {
|
||||
if (strcmp(attrs->names[i], "destroy") == 0) {
|
||||
destroy = true;
|
||||
} else {
|
||||
return FMT_PRINT_FUNC_RET_INVALID_ATTR(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
Error val = va_arg(v, Error);
|
||||
_print_error(ctx, val, destroy);
|
||||
return FMT_PRINT_FUNC_RET_OK();
|
||||
}
|
||||
|
||||
/********************************\
|
||||
|* Helper Functions *|
|
||||
\********************************/
|
||||
typedef enum {
|
||||
UnclosedCharLiteral,
|
||||
End,
|
||||
Colon,
|
||||
Equals,
|
||||
Comma,
|
||||
Asterisk,
|
||||
String,
|
||||
Number, /* also returned as a string, but guaranteed to be a valid number */
|
||||
CharLiteral, /* char literal encased in '' */
|
||||
} _Token;
|
||||
|
||||
static _Token _next_token(const char **restrict fmt, char *restrict strbuf, size_t strbuf_size) {
|
||||
const char *c = *fmt;
|
||||
if (strbuf_size == 0)
|
||||
strbuf = NULL;
|
||||
switch (*c) {
|
||||
case '}':
|
||||
case 0:
|
||||
*fmt = ++c;
|
||||
return End;
|
||||
case ':':
|
||||
*fmt = ++c;
|
||||
return Colon;
|
||||
case '=':
|
||||
*fmt = ++c;
|
||||
return Equals;
|
||||
case ',':
|
||||
*fmt = ++c;
|
||||
return Comma;
|
||||
case '*':
|
||||
*fmt = ++c;
|
||||
return Asterisk;
|
||||
case '\'':
|
||||
c++;
|
||||
if (!(c[0] != 0 && c[1] == '\''))
|
||||
return UnclosedCharLiteral;
|
||||
else if (strbuf != NULL) {
|
||||
*strbuf++ = *c++;
|
||||
*strbuf++ = 0;
|
||||
}
|
||||
*fmt = ++c;
|
||||
return CharLiteral;
|
||||
}
|
||||
_Token ret = (*c == '-') || (*c >= '0' && *c <= '9') ? Number : String;
|
||||
for (size_t i = 0;; c++, i++) {
|
||||
if (*c == 0 || *c == '}' || *c == ':' || *c == '=' || *c == ',' || *c == '*')
|
||||
break;
|
||||
if (ret == Number && !(*c >= '0' && *c <= '9'))
|
||||
ret = String;
|
||||
if (strbuf != NULL && i < strbuf_size - 1)
|
||||
*strbuf++ = *c;
|
||||
}
|
||||
if (strbuf != NULL)
|
||||
*strbuf++ = 0;
|
||||
*fmt = c;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void _fmtf_putc_func(FmtContext *restrict ctx, char c) {
|
||||
FILE *f = ctx->ctx_data;
|
||||
fputc(c, f);
|
||||
}
|
||||
|
||||
typedef struct _FmtsContext {
|
||||
char *buf;
|
||||
size_t size, written;
|
||||
} _FmtsContext;
|
||||
|
||||
static void _fmts_putc_func(FmtContext *restrict ctx, char c) {
|
||||
_FmtsContext *ctxs = ctx->ctx_data;
|
||||
if (ctxs->size > 0 && ctxs->written < ctxs->size - 1)
|
||||
ctxs->buf[ctxs->written] = c;
|
||||
ctxs->written++;
|
||||
}
|
||||
|
||||
|
||||
/********************************\
|
||||
|* The guts of any fmt function *|
|
||||
\********************************/
|
||||
static Error _fmt_main(bool use_err_location, const char *file, size_t line, FmtContext *ctx, const char *restrict format, va_list args) {
|
||||
if (!initialized) {
|
||||
const char *err_str = "fmt: fmt_init() must be called at the beginning of the program before using any other fmt functions, and fmt_term() must be called when done\n";
|
||||
return use_err_location ? ERROR_STRING_LOCATION(file, line, err_str) : ERROR_STRING(err_str);
|
||||
}
|
||||
const char *c = format;
|
||||
while (*c != 0) {
|
||||
if (*c == '%') {
|
||||
c++;
|
||||
if (*c == '{') {
|
||||
FmtAttrs attrs = {0};
|
||||
c++;
|
||||
_Token t;
|
||||
char name[128];
|
||||
t = _next_token(&c, name, 128);
|
||||
if (t != String) {
|
||||
const char *err_str = "fmt: Expected valid type name after %{";
|
||||
return use_err_location ? ERROR_STRING_LOCATION(file, line, err_str) : ERROR_STRING(err_str);
|
||||
}
|
||||
FmtPrintFunc *print_func = _print_func_map_get(_print_funcs, name);
|
||||
if (print_func == NULL) {
|
||||
char errbuf[256];
|
||||
snprintf(errbuf, 256, "fmt: Unrecognized type name: '%s'", name);
|
||||
return use_err_location ? ERROR_HEAP_STRING_LOCATION(file, line, strdup(errbuf)) : ERROR_HEAP_STRING(strdup(errbuf));
|
||||
}
|
||||
t = _next_token(&c, NULL, 0);
|
||||
if (t == Colon) {
|
||||
while (attrs.len < FMT_MAX_ATTRS) {
|
||||
t = _next_token(&c, attrs.names[attrs.len], FMT_MAX_ATTR_LEN);
|
||||
if (t != String) {
|
||||
const char *err_str = "fmt: Expected valid attribute name after %{<type>:";
|
||||
return use_err_location ? ERROR_STRING_LOCATION(file, line, err_str) : ERROR_STRING(err_str);
|
||||
}
|
||||
t = _next_token(&c, NULL, 0);
|
||||
if (t == Equals) {
|
||||
char num[64];
|
||||
t = _next_token(&c, num, 64);
|
||||
if (t != Number && t != CharLiteral && t != Asterisk) {
|
||||
if (t == UnclosedCharLiteral) {
|
||||
const char *err_str = "fmt: Char literal after %{<type>:<attr>= unclosed or containing more than one character";
|
||||
return use_err_location ? ERROR_STRING_LOCATION(file, line, err_str) : ERROR_STRING(err_str);
|
||||
} else {
|
||||
const char *err_str = "fmt: Expected number, char literal or asterisk after %{<type>:<attr>=";
|
||||
return use_err_location ? ERROR_STRING_LOCATION(file, line, err_str) : ERROR_STRING(err_str);
|
||||
}
|
||||
}
|
||||
if (t == Number)
|
||||
attrs.vals[attrs.len] = atoi(num);
|
||||
else if (t == CharLiteral)
|
||||
attrs.vals[attrs.len] = num[0];
|
||||
else if (t == Asterisk)
|
||||
attrs.vals[attrs.len] = va_arg(args, int);
|
||||
t = _next_token(&c, NULL, 0);
|
||||
} else
|
||||
attrs.vals[attrs.len] = -1;
|
||||
attrs.len++;
|
||||
if (t == End)
|
||||
break;
|
||||
if (t != Comma) {
|
||||
const char *err_str = "fmt: Expected ',' or '}' after %{<type>:<attr>=<val>";
|
||||
return use_err_location ? ERROR_STRING_LOCATION(file, line, err_str) : ERROR_STRING(err_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
FmtPrintFuncRet res = (*print_func)(ctx, &attrs, args);
|
||||
if (res.invalid_attr) {
|
||||
char errbuf[256];
|
||||
snprintf(errbuf, 256, "fmt: Invalid attribute: '%s'", attrs.names[res.invalid_attr_idx]);
|
||||
return use_err_location ? ERROR_HEAP_STRING_LOCATION(file, line, strdup(errbuf)) : ERROR_HEAP_STRING(strdup(errbuf));
|
||||
}
|
||||
if (t != End) {
|
||||
const char *err_str = "fmt: Expected end";
|
||||
return use_err_location ? ERROR_STRING_LOCATION(file, line, err_str) : ERROR_STRING(err_str);
|
||||
}
|
||||
} else {
|
||||
enum {
|
||||
MOD_NONE,
|
||||
MOD_Z,
|
||||
MOD_L,
|
||||
MOD_LL,
|
||||
MOD_H,
|
||||
MOD_HH,
|
||||
};
|
||||
int mod = MOD_NONE;
|
||||
switch (*c) {
|
||||
case 'z':
|
||||
c++;
|
||||
mod = MOD_Z;
|
||||
break;
|
||||
case 'l':
|
||||
c++;
|
||||
if (*c == 'l') {
|
||||
c++;
|
||||
mod = MOD_LL;
|
||||
} else
|
||||
mod = MOD_L;
|
||||
break;
|
||||
case 'h':
|
||||
c++;
|
||||
if (*c == 'h') {
|
||||
c++;
|
||||
mod = MOD_HH;
|
||||
} else
|
||||
mod = MOD_H;
|
||||
break;
|
||||
}
|
||||
switch (*c) {
|
||||
case '%':
|
||||
c++;
|
||||
ctx->putc_func(ctx, '%');
|
||||
break;
|
||||
case 's': {
|
||||
c++;
|
||||
_print_string_func(ctx, NULL, args);
|
||||
break;
|
||||
}
|
||||
case 'c': {
|
||||
c++;
|
||||
ctx->putc_func(ctx, va_arg(args, int));
|
||||
break;
|
||||
}
|
||||
case 'i':
|
||||
case 'd':
|
||||
c++;
|
||||
switch (mod) {
|
||||
default:
|
||||
case MOD_H:
|
||||
case MOD_HH:
|
||||
_print_int_func(ctx, NULL, args);
|
||||
break;
|
||||
case MOD_Z:
|
||||
_print_ssize_t_func(ctx, NULL, args);
|
||||
break;
|
||||
case MOD_L:
|
||||
_print_lint_func(ctx, NULL, args);
|
||||
break;
|
||||
case MOD_LL:
|
||||
_print_llint_func(ctx, NULL, args);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'u':
|
||||
c++;
|
||||
switch (mod) {
|
||||
default:
|
||||
case MOD_H:
|
||||
case MOD_HH:
|
||||
_print_uint_func(ctx, NULL, args);
|
||||
break;
|
||||
case MOD_Z:
|
||||
_print_size_t_func(ctx, NULL, args);
|
||||
break;
|
||||
case MOD_L:
|
||||
_print_ulint_func(ctx, NULL, args);
|
||||
break;
|
||||
case MOD_LL:
|
||||
_print_ullint_func(ctx, NULL, args);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'x':
|
||||
c++;
|
||||
FmtAttrs custom_attrs = {
|
||||
.len = 1,
|
||||
.names = { { 'x' } },
|
||||
};
|
||||
switch (mod) {
|
||||
default:
|
||||
case MOD_H:
|
||||
case MOD_HH:
|
||||
_print_uint_func(ctx, &custom_attrs, args);
|
||||
break;
|
||||
case MOD_Z:
|
||||
_print_size_t_func(ctx, &custom_attrs, args);
|
||||
break;
|
||||
case MOD_L:
|
||||
_print_ulint_func(ctx, &custom_attrs, args);
|
||||
break;
|
||||
case MOD_LL:
|
||||
_print_ullint_func(ctx, &custom_attrs, args);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'X': {
|
||||
c++;
|
||||
FmtAttrs custom_attrs = {
|
||||
.len = 1,
|
||||
.names = { { 'X' } },
|
||||
};
|
||||
switch (mod) {
|
||||
default:
|
||||
case MOD_H:
|
||||
case MOD_HH:
|
||||
_print_uint_func(ctx, &custom_attrs, args);
|
||||
break;
|
||||
case MOD_Z:
|
||||
_print_size_t_func(ctx, &custom_attrs, args);
|
||||
break;
|
||||
case MOD_L:
|
||||
_print_ulint_func(ctx, &custom_attrs, args);
|
||||
break;
|
||||
case MOD_LL:
|
||||
_print_ullint_func(ctx, &custom_attrs, args);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'p': {
|
||||
c++;
|
||||
ctx->putc_func(ctx, '0');
|
||||
ctx->putc_func(ctx, 'x');
|
||||
FmtAttrs custom_attrs = {
|
||||
.len = 3,
|
||||
.names = { { 'p' }, { 'b' }, { 'c' } },
|
||||
.vals = { 12, 16, '0' },
|
||||
};
|
||||
_print_size_t_func(ctx, &custom_attrs, args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else
|
||||
ctx->putc_func(ctx, *c++);
|
||||
}
|
||||
return OK();
|
||||
}
|
||||
|
||||
/********************************\
|
||||
|* Public functions *|
|
||||
\********************************/
|
||||
void fmt_register(const char *restrict keyword, FmtPrintFunc print_func) {
|
||||
_print_func_map_set(&_print_funcs, keyword, print_func);
|
||||
}
|
||||
|
||||
void _fmtv(const char *file, size_t line, const char *restrict format, va_list args) {
|
||||
FmtContext ctx = {
|
||||
.ctx_data = stdout,
|
||||
.putc_func = _fmtf_putc_func,
|
||||
};
|
||||
_fmtcv(file, line, &ctx, format, args);
|
||||
}
|
||||
|
||||
void _fmt(const char *file, size_t line, const char *restrict format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
_fmtv(file, line, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
size_t _fmtsv(const char *restrict file, size_t line, char *restrict buf, size_t size, const char *restrict format, va_list args) {
|
||||
_FmtsContext ctxs = {
|
||||
.buf = buf,
|
||||
.size = size,
|
||||
};
|
||||
FmtContext ctx = {
|
||||
.ctx_data = &ctxs,
|
||||
.putc_func = _fmts_putc_func,
|
||||
};
|
||||
memset(buf, 0, size); /* TODO: Remove this without valgrind complaining. */
|
||||
_fmtcv(file, line, &ctx, format, args);
|
||||
if (size > 0)
|
||||
buf[ctxs.written] = 0;
|
||||
return ctxs.written;
|
||||
}
|
||||
|
||||
size_t _fmts(const char *restrict file, size_t line, char *restrict buf, size_t size, const char *restrict format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
size_t res = _fmtsv(file, line, buf, size, format, args);
|
||||
va_end(args);
|
||||
return res;
|
||||
}
|
||||
|
||||
void _fmtcv(const char *restrict file, size_t line, FmtContext *ctx, const char *restrict format, va_list args) {
|
||||
ERROR_ASSERT(_fmt_main(true, file, line, ctx, format, args));
|
||||
}
|
||||
|
||||
void _fmtc(const char *restrict file, size_t line, FmtContext *ctx, const char *restrict format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
_fmtcv(file, line, ctx, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void fmt_init() {
|
||||
if (initialized) {
|
||||
ERROR_ASSERT(ERROR_STRING("fmt: fmt_init() can only be called once"));
|
||||
}
|
||||
initialized = true;
|
||||
_print_funcs = _print_func_map();
|
||||
|
||||
fmt_register("str", _print_string_func);
|
||||
|
||||
fmt_register("int", _print_int_func);
|
||||
|
||||
fmt_register("unsigned int", _print_uint_func);
|
||||
fmt_register("uint", _print_uint_func);
|
||||
|
||||
fmt_register("size_t", _print_size_t_func);
|
||||
fmt_register("ssize_t", _print_ssize_t_func);
|
||||
|
||||
fmt_register("Error", _print_error_func);
|
||||
}
|
||||
|
||||
void fmt_term() {
|
||||
_print_func_map_term(_print_funcs);
|
||||
}
|
14
src/ds/string.c
Normal file
14
src/ds/string.c
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright 2022 Darwin Schuppan <darwin@nobrain.org>
|
||||
// SPDX license identifier: MIT
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
char *strdup(const char *s) {
|
||||
size_t len = strlen(s);
|
||||
char *res = malloc(len + 1);
|
||||
if(res == NULL)
|
||||
return NULL;
|
||||
memcpy(res, s, len+1);
|
||||
return res;
|
||||
}
|
132
tests/error.c
Normal file
132
tests/error.c
Normal file
@ -0,0 +1,132 @@
|
||||
// Copyright 2022 Darwin Schuppan <darwin@nobrain.org>
|
||||
// SPDX license identifier: MIT
|
||||
|
||||
#include <ds/error.h>
|
||||
#include <ds/fmt.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
static int iq;
|
||||
static char iqstr[1024];
|
||||
|
||||
static Error no_error() {
|
||||
return OK();
|
||||
}
|
||||
|
||||
static Error no_brain() {
|
||||
return ERROR_STRING("You do not have sufficient neural functionality to pass this test");
|
||||
}
|
||||
|
||||
static Error no_brain_heap() {
|
||||
char *s = malloc(128);
|
||||
assert(s != NULL);
|
||||
snprintf(s, 128, "Insufficient IQ: %d (required: 55)", iq);
|
||||
return ERROR_HEAP_STRING(s);
|
||||
}
|
||||
|
||||
static Error start_javascript_engine() {
|
||||
return ERROR_OUT_OF_MEMORY();
|
||||
}
|
||||
|
||||
static Error nested() {
|
||||
return ERROR_NESTED(ERROR_STRING("Human error"), ERROR_NESTED(no_brain(), no_brain_heap()));
|
||||
}
|
||||
|
||||
static Error idiot_takes_iq_test() {
|
||||
return nested();
|
||||
}
|
||||
|
||||
static Error dennis_takes_iq_test() {
|
||||
return OK();
|
||||
}
|
||||
|
||||
static Error try_to_take_the_iq_test(bool *failed) {
|
||||
*failed = false;
|
||||
TRY(idiot_takes_iq_test(), *failed = true);
|
||||
return OK();
|
||||
}
|
||||
|
||||
static Error force_dennis_to_take_the_iq_test(bool *failed) {
|
||||
*failed = false;
|
||||
TRY(dennis_takes_iq_test(), *failed = true);
|
||||
return OK();
|
||||
}
|
||||
|
||||
int main() {
|
||||
srand(time(NULL));
|
||||
iq = rand() % 55;
|
||||
snprintf(iqstr, 1024, "Insufficient IQ: %d (required: 55)", iq);
|
||||
|
||||
fmt_init();
|
||||
error_init();
|
||||
|
||||
Error e;
|
||||
char *s = malloc(2048);
|
||||
assert(s != NULL);
|
||||
|
||||
e = no_error();
|
||||
assert(e.kind == ErrorNone);
|
||||
assert(error_to_string(s, 2048, e, true) == strlen("Success"));
|
||||
assert(strcmp(s, "Success") == 0);
|
||||
|
||||
e = no_brain();
|
||||
assert(e.kind == ErrorString);
|
||||
assert(!e.str_on_heap);
|
||||
assert(error_to_string(s, 2048, e, true) == strlen("You do not have sufficient neural functionality to pass this test"));
|
||||
assert(strcmp(s, "You do not have sufficient neural functionality to pass this test") == 0);
|
||||
|
||||
e = no_brain_heap();
|
||||
assert(e.kind == ErrorString);
|
||||
assert(e.str_on_heap);
|
||||
assert(error_to_string(s, 2048, e, true) == strlen(iqstr));
|
||||
assert(strcmp(s, iqstr) == 0);
|
||||
|
||||
e = start_javascript_engine();
|
||||
assert(e.kind == ErrorOutOfMemory);
|
||||
assert(error_to_string(s, 2048, e, true) == strlen("Out of memory"));
|
||||
assert(strcmp(s, "Out of memory") == 0);
|
||||
|
||||
snprintf(iqstr, 1024, "Human error: You do not have sufficient neural functionality to pass this test: Insufficient IQ: %d (required: 55)", iq);
|
||||
e = nested();
|
||||
assert(e.kind == ErrorString);
|
||||
assert(e.has_annex);
|
||||
assert(error_to_string(NULL, 0, e, false) == strlen(iqstr)); /* should still give us the right size, also shouldn't free/destroy anything */
|
||||
assert(error_to_string(s, 2048, e, true) == strlen(iqstr));
|
||||
assert(strcmp(s, iqstr) == 0);
|
||||
|
||||
bool failed;
|
||||
e = try_to_take_the_iq_test(&failed);
|
||||
assert(e.kind == ErrorString);
|
||||
assert(failed);
|
||||
assert(error_to_string(s, 2048, e, true) == strlen(iqstr));
|
||||
assert(strcmp(s, iqstr) == 0);
|
||||
|
||||
e = ERROR_HEREIFY(e);
|
||||
assert(e.kind == ErrorString);
|
||||
assert(e.has_location);
|
||||
assert(e.file != NULL);
|
||||
assert(e.line != 0);
|
||||
|
||||
e = force_dennis_to_take_the_iq_test(&failed);
|
||||
assert(e.kind == ErrorNone);
|
||||
assert(!failed);
|
||||
assert(error_to_string(s, 2048, e, true) == strlen("Success"));
|
||||
assert(strcmp(s, "Success") == 0);
|
||||
|
||||
e = nested();
|
||||
fmts(s, 2048, "%{Error}", e);
|
||||
assert(strcmp(s, iqstr) == 0);
|
||||
fmts(NULL, 0, "%{Error:destroy}", e);
|
||||
|
||||
fmts(s, 2048, "%{Error:destroy}", nested());
|
||||
assert(strcmp(s, iqstr) == 0);
|
||||
|
||||
free(s);
|
||||
|
||||
error_term();
|
||||
fmt_term();
|
||||
}
|
18
tests/fmt.c
Normal file
18
tests/fmt.c
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2022 Darwin Schuppan <darwin@nobrain.org>
|
||||
// SPDX license identifier: MIT
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <ds/fmt.h>
|
||||
|
||||
int main() {
|
||||
fmt_init();
|
||||
char buf[128];
|
||||
size_t size = fmts(buf, 128, "%hhu, %s %c%c %{uint:X,p=1024,c='.'}", ' ', "abc123", 'A', 65, 99999999);
|
||||
assert(size == 1038);
|
||||
assert(strcmp(buf, "32, abc123 AA .................................................................................................................") == 0);
|
||||
size = fmts(NULL, 0, "%%");
|
||||
assert(size == 1);
|
||||
fmt_term();
|
||||
}
|
76
tests/generic/map.c
Normal file
76
tests/generic/map.c
Normal file
@ -0,0 +1,76 @@
|
||||
// Copyright 2022 Darwin Schuppan <darwin@nobrain.org>
|
||||
// SPDX license identifier: MIT
|
||||
|
||||
#define GENERIC_IMPL_STATIC
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <ds/fmt.h>
|
||||
|
||||
static bool malloc_fail = false;
|
||||
static void *custom_malloc(size_t size) {
|
||||
return malloc_fail ? NULL : malloc(size);
|
||||
}
|
||||
#define malloc(size) custom_malloc(size)
|
||||
|
||||
#define GENERIC_KEY_TYPE int
|
||||
#define GENERIC_VALUE_TYPE int
|
||||
#define GENERIC_NAME IntIntMap
|
||||
#define GENERIC_PREFIX int_int_map
|
||||
#include <ds/generic/map.h>
|
||||
|
||||
#define GENERIC_KEY_TYPE int
|
||||
#define GENERIC_VALUE_TYPE float
|
||||
#define GENERIC_NAME IntFloatMap
|
||||
#define GENERIC_PREFIX int_float_map
|
||||
#include <ds/generic/map.h>
|
||||
|
||||
int main() {
|
||||
fmt_init();
|
||||
|
||||
IntIntMap m = int_int_map();
|
||||
int_int_map_rehash(&m, 10);
|
||||
// Insert
|
||||
for (size_t i = 10; i < 80; i++)
|
||||
ERROR_ASSERT(int_int_map_set(&m, i, i + 20));
|
||||
assert(int_int_map_del(m, 15));
|
||||
assert(!int_int_map_del(m, 15));
|
||||
// Get
|
||||
for (size_t i = 10; i < 80; i++) {
|
||||
int *p = int_int_map_get(m, i);
|
||||
if (i == 15) {
|
||||
assert(p == NULL);
|
||||
} else {
|
||||
assert(p != NULL);
|
||||
assert(*p == i + 20);
|
||||
}
|
||||
}
|
||||
// Invalid rehash
|
||||
int_int_map_rehash(&m, 2);
|
||||
// Replace
|
||||
for (size_t i = 10; i < 80; i++)
|
||||
ERROR_ASSERT(int_int_map_set(&m, i, i + 20));
|
||||
// Use iterators to access every item
|
||||
IntIntMapItem *i = NULL;
|
||||
while (int_int_map_it_next(m, &i)) {
|
||||
assert(i->key != 0);
|
||||
assert(i->val != 0);
|
||||
}
|
||||
// Print using fmt
|
||||
int_int_map_fmt_register("%d", "%d");
|
||||
char buf[2048];
|
||||
fmts(buf, 2048, "%{IntIntMap}", m);
|
||||
assert(strcmp(buf, "{69: 89, 52: 72, 39: 59, 22: 42, 77: 97, 60: 80, 47: 67, 30: 50, 68: 88, 55: 75, 38: 58, 17: 37, 76: 96, 63: 83, 46: 66, 25: 45, 71: 91, 54: 74, 33: 53, 16: 36, 79: 99, 62: 82, 41: 61, 24: 44, 11: 31, 70: 90, 49: 69, 32: 52, 19: 39, 78: 98, 57: 77, 40: 60, 27: 47, 10: 30, 65: 85, 48: 68, 35: 55, 18: 38, 13: 33, 73: 93, 56: 76, 43: 63, 26: 46, 21: 41, 64: 84, 51: 71, 34: 54, 29: 49, 12: 32, 72: 92, 59: 79, 42: 62, 37: 57, 20: 40, 67: 87, 50: 70, 45: 65, 28: 48, 15: 35, 75: 95, 58: 78, 53: 73, 36: 56, 23: 43, 66: 86, 61: 81, 44: 64, 31: 51, 14: 34, 74: 94}") == 0);
|
||||
int_int_map_term(m);
|
||||
// Error recovery
|
||||
malloc_fail = true;
|
||||
IntFloatMap fm = int_float_map();
|
||||
Error err = int_float_map_set(&fm, 10, 10.f);
|
||||
assert(err.kind == ErrorOutOfMemory);
|
||||
assert(fm.len == 0);
|
||||
assert(fm.cap == 0);
|
||||
int_float_map_term(fm);
|
||||
|
||||
fmt_term();
|
||||
}
|
108
tests/generic/smap.c
Normal file
108
tests/generic/smap.c
Normal file
@ -0,0 +1,108 @@
|
||||
// Copyright 2022 Darwin Schuppan <darwin@nobrain.org>
|
||||
// SPDX license identifier: MIT
|
||||
|
||||
#define GENERIC_IMPL_STATIC
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <ds/fmt.h>
|
||||
|
||||
static bool malloc_fail = false;
|
||||
static void *custom_malloc(size_t size) {
|
||||
return malloc_fail ? NULL : malloc(size);
|
||||
}
|
||||
#define malloc(size) custom_malloc(size)
|
||||
|
||||
static bool strdup_fail = false;
|
||||
static void *custom_strdup(const char *str) {
|
||||
return strdup_fail ? NULL : strdup(str);
|
||||
}
|
||||
#define strdup(str) custom_strdup(str)
|
||||
|
||||
#define GENERIC_TYPE int
|
||||
#define GENERIC_NAME IntMap
|
||||
#define GENERIC_PREFIX int_map
|
||||
#include <ds/generic/smap.h>
|
||||
|
||||
#define GENERIC_TYPE size_t
|
||||
#define GENERIC_NAME SizeTMap
|
||||
#define GENERIC_PREFIX size_t_map
|
||||
#include <ds/generic/smap.h>
|
||||
|
||||
typedef struct Test {
|
||||
size_t n;
|
||||
const char *s;
|
||||
double d;
|
||||
} Test;
|
||||
|
||||
#define GENERIC_TYPE Test
|
||||
#define GENERIC_NAME TestMap
|
||||
#define GENERIC_PREFIX test_map
|
||||
#include <ds/generic/smap.h>
|
||||
|
||||
int main() {
|
||||
fmt_init();
|
||||
|
||||
IntMap m = int_map();
|
||||
// Insert
|
||||
for (size_t i = 10; i < 80; i++) {
|
||||
char buf[64];
|
||||
snprintf(buf, 64, "number: %d", (int)i);
|
||||
ERROR_ASSERT(int_map_set(&m, buf, (int)i));
|
||||
}
|
||||
assert(int_map_del(m, "number: 15"));
|
||||
assert(!int_map_del(m, "number: 15"));
|
||||
// Get
|
||||
for (size_t i = 10; i < 80; i++) {
|
||||
char buf[64];
|
||||
snprintf(buf, 64, "number: %d", (int)i);
|
||||
int *p = int_map_get(m, buf);
|
||||
if (i != 15) {
|
||||
assert(p);
|
||||
assert(*p == (int)i);
|
||||
}
|
||||
}
|
||||
// Replace
|
||||
for (size_t i = 10; i < 80; i++) {
|
||||
char buf[64];
|
||||
snprintf(buf, 64, "number: %d", (int)i);
|
||||
ERROR_ASSERT(int_map_set(&m, buf, (int)i));
|
||||
}
|
||||
// Use iterators get every item
|
||||
IntMapItem *i = NULL;
|
||||
while (int_map_it_next(m, &i)) {
|
||||
assert(i->key != 0);
|
||||
assert(i->val != 0);
|
||||
}
|
||||
// Print using fmt
|
||||
int_map_fmt_register("%d");
|
||||
char buf[2048];
|
||||
fmts(buf, 2048, "%{IntMap}", m);
|
||||
assert(strcmp(buf, "{\"number: 64\": 64, \"number: 29\": 29, \"number: 46\": 46, \"number: 63\": 63, \"number: 20\": 20, \"number: 55\": 55, \"number: 74\": 74, \"number: 27\": 27, \"number: 52\": 52, \"number: 14\": 14, \"number: 30\": 30, \"number: 65\": 65, \"number: 47\": 47, \"number: 21\": 21, \"number: 54\": 54, \"number: 73\": 73, \"number: 17\": 17, \"number: 37\": 37, \"number: 66\": 66, \"number: 44\": 44, \"number: 22\": 22, \"number: 57\": 57, \"number: 11\": 11, \"number: 72\": 72, \"number: 16\": 16, \"number: 36\": 36, \"number: 42\": 42, \"number: 67\": 67, \"number: 59\": 59, \"number: 45\": 45, \"number: 23\": 23, \"number: 56\": 56, \"number: 10\": 10, \"number: 71\": 71, \"number: 48\": 48, \"number: 19\": 19, \"number: 35\": 35, \"number: 43\": 43, \"number: 60\": 60, \"number: 58\": 58, \"number: 77\": 77, \"number: 24\": 24, \"number: 51\": 51, \"number: 13\": 13, \"number: 70\": 70, \"number: 33\": 33, \"number: 49\": 49, \"number: 79\": 79, \"number: 18\": 18, \"number: 34\": 34, \"number: 40\": 40, \"number: 61\": 61, \"number: 76\": 76, \"number: 68\": 68, \"number: 39\": 39, \"number: 25\": 25, \"number: 50\": 50, \"number: 12\": 12, \"number: 32\": 32, \"number: 78\": 78, \"number: 28\": 28, \"number: 41\": 41, \"number: 62\": 62, \"number: 75\": 75, \"number: 69\": 69, \"number: 38\": 38, \"number: 26\": 26, \"number: 53\": 53, \"number: 15\": 15, \"number: 31\": 31}") == 0);
|
||||
int_map_term(m);
|
||||
|
||||
SizeTMap sm = size_t_map();
|
||||
size_t_map_term(sm);
|
||||
|
||||
TestMap tm = test_map();
|
||||
// Error recovery (malloc fail)
|
||||
malloc_fail = true;
|
||||
Error err = test_map_set(&tm, "a", (Test){ .n = 22 });
|
||||
assert(err.kind == ErrorOutOfMemory);
|
||||
assert(tm.len == 0);
|
||||
assert(tm.cap == 0);
|
||||
// Error recovery (strdup fail)
|
||||
malloc_fail = false;
|
||||
strdup_fail = true;
|
||||
err = test_map_set(&tm, "a", (Test){ .n = 22 });
|
||||
assert(err.kind == ErrorOutOfMemory);
|
||||
assert(tm.len == 0);
|
||||
assert(tm.cap == 8);
|
||||
|
||||
test_map_term(tm);
|
||||
|
||||
fmt_term();
|
||||
}
|
62
tests/generic/vec.c
Normal file
62
tests/generic/vec.c
Normal file
@ -0,0 +1,62 @@
|
||||
// Copyright 2022 Darwin Schuppan <darwin@nobrain.org>
|
||||
// SPDX license identifier: MIT
|
||||
|
||||
#define GENERIC_IMPL_STATIC
|
||||
|
||||
#define GENERIC_TYPE int
|
||||
#define GENERIC_NAME IntVec
|
||||
#define GENERIC_PREFIX int_vec
|
||||
#include <ds/generic/vec.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
int main() {
|
||||
fmt_init();
|
||||
|
||||
IntVec v = int_vec();
|
||||
|
||||
assert(vec_len(v) == 0);
|
||||
assert(vec_cap(v) == 0);
|
||||
assert(int_vec_len(v) == 0);
|
||||
assert(int_vec_cap(v) == 0);
|
||||
|
||||
// Push
|
||||
for (size_t i = 0; i < 512; i++)
|
||||
int_vec_push(&v, i + 1);
|
||||
assert(vec_len(v) == 512);
|
||||
assert(vec_cap(v) == 512);
|
||||
assert(vec_len(v) == 512);
|
||||
assert(vec_cap(v) == 512);
|
||||
// Retrieve
|
||||
for (size_t i = 0; i < 512; i++)
|
||||
assert(v[i] == i + 1);
|
||||
// Delete
|
||||
int_vec_del(v, 256);
|
||||
assert(vec_len(v) == 511);
|
||||
assert(vec_cap(v) == 512);
|
||||
for (size_t i = 0; i < 256; i++)
|
||||
assert(v[i] == i + 1);
|
||||
for (size_t i = 256; i < 511; i++)
|
||||
assert(v[i] == i + 2);
|
||||
// Retrieve via pop() and back()
|
||||
assert(int_vec_pop(v) == 512);
|
||||
assert(*int_vec_back(v) == 511);
|
||||
// Insert
|
||||
int_vec_insert(&v, 1, 999);
|
||||
assert(vec_len(v) == 511);
|
||||
assert(vec_cap(v) == 512);
|
||||
assert(v[0] == 1);
|
||||
assert(v[1] == 999);
|
||||
for (size_t i = 2; i < 256; i++)
|
||||
assert(v[i] == i);
|
||||
// Print via fmt
|
||||
int_vec_fmt_register("%d");
|
||||
char buf[4096];
|
||||
fmts(buf, 4096, "%{IntVec}", v);
|
||||
assert(strcmp(buf, "{1, 999, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511}") == 0);
|
||||
|
||||
int_vec_term(v);
|
||||
|
||||
fmt_term();
|
||||
}
|
Loading…
Reference in New Issue
Block a user