added:
dir -> directory walker similar to dirent
dirwatch -> lets you watch a directory for changes in another thread
vec -> generic vector
This commit is contained in:
parent
aa08240ec9
commit
59b55c7f6c
26 changed files with 4037 additions and 2726 deletions
|
|
@ -3,16 +3,20 @@ add_library(Colla STATIC
|
|||
tracelog.c
|
||||
http.c
|
||||
strstream.c
|
||||
strview.c
|
||||
str.c
|
||||
coroutine.c
|
||||
os.c
|
||||
fs.c
|
||||
file.c
|
||||
dir.c
|
||||
dirwatch.c
|
||||
)
|
||||
|
||||
IF (WIN32)
|
||||
if(MSVC)
|
||||
target_link_libraries(Colla ws2_32.lib)
|
||||
ELSE()
|
||||
# posix
|
||||
ENDIF()
|
||||
target_compile_options(Colla PRIVATE /W4)
|
||||
else()
|
||||
target_link_libraries(Colla pthread)
|
||||
target_compile_options(Colla PRIVATE -Wall -Wextra -Wpedantic)
|
||||
target_compile_definitions(Colla PUBLIC _DEFAULT_SOURCE)
|
||||
endif()
|
||||
147
dir.c
Normal file
147
dir.c
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
#include "dir.h"
|
||||
#include "tracelog.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define VC_EXTRALEAN
|
||||
#include <windows.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "strstream.h"
|
||||
|
||||
typedef struct {
|
||||
dir_entry_t cur;
|
||||
dir_entry_t next;
|
||||
HANDLE handle;
|
||||
} _dir_internal_t;
|
||||
|
||||
static dir_entry_t _fillDirEntry(WIN32_FIND_DATAW *data) {
|
||||
return (dir_entry_t) {
|
||||
.type =
|
||||
data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ?
|
||||
FS_TYPE_DIR : FS_TYPE_FILE,
|
||||
.name = strFromWCHAR(data->cFileName, 0)
|
||||
};
|
||||
}
|
||||
|
||||
static _dir_internal_t _getFirst(const wchar_t *path) {
|
||||
_dir_internal_t res = {0};
|
||||
WIN32_FIND_DATAW data = {0};
|
||||
|
||||
res.handle = FindFirstFileW(path, &data);
|
||||
|
||||
if(res.handle != INVALID_HANDLE_VALUE) {
|
||||
res.next = _fillDirEntry(&data);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void _getNext(_dir_internal_t *ctx) {
|
||||
WIN32_FIND_DATAW data = {0};
|
||||
|
||||
BOOL result = FindNextFileW(ctx->handle, &data);
|
||||
if(!result) {
|
||||
if(GetLastError() == ERROR_NO_MORE_FILES) {
|
||||
FindClose(ctx->handle);
|
||||
ctx->handle = NULL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
ctx->next = _fillDirEntry(&data);
|
||||
}
|
||||
|
||||
dir_t dirOpen(const char *path) {
|
||||
DWORD n = GetFullPathName(path, 0, NULL, NULL);
|
||||
str_ostream_t out = ostrInitLen(n + 3);
|
||||
n = GetFullPathName(path, n, out.buf, NULL);
|
||||
assert(n > 0);
|
||||
out.size += n;
|
||||
switch(ostrBack(&out)) {
|
||||
case '\\':
|
||||
case '/':
|
||||
case ':':
|
||||
// directory ends in path separator
|
||||
// NOP
|
||||
break;
|
||||
default:
|
||||
ostrPutc(&out, '\\');
|
||||
}
|
||||
ostrPutc(&out, '*');
|
||||
|
||||
_dir_internal_t *dir = malloc(sizeof(_dir_internal_t));
|
||||
if(dir) {
|
||||
wchar_t *wpath = strToWCHAR(ostrAsStr(&out));
|
||||
assert(wpath);
|
||||
*dir = _getFirst(wpath);
|
||||
free(wpath);
|
||||
}
|
||||
ostrFree(&out);
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
dir_entry_t *dirNext(dir_t ctx) {
|
||||
_dir_internal_t *dir = (_dir_internal_t*)ctx;
|
||||
strFree(&dir->cur.name);
|
||||
if(!dir->handle) return NULL;
|
||||
dir->cur = dir->next;
|
||||
_getNext(dir);
|
||||
return &dir->cur;
|
||||
}
|
||||
|
||||
void dirClose(dir_t ctx) {
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <dirent.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// taken from https://sites.uclouvain.be/SystInfo/usr/include/dirent.h.html
|
||||
// hopefully shouldn't be needed
|
||||
#ifndef DT_DIR
|
||||
#define DT_DIR 4
|
||||
#endif
|
||||
#ifndef DT_REG
|
||||
#define DT_REG 8
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
DIR *dir;
|
||||
dir_entry_t next;
|
||||
} _dir_internal_t;
|
||||
|
||||
dir_t dirOpen(const char *path) {
|
||||
_dir_internal_t *ctx = calloc(1, sizeof(_dir_internal_t));
|
||||
if(ctx) ctx->dir = opendir(path);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void dirClose(dir_t ctx) {
|
||||
if(ctx) {
|
||||
_dir_internal_t *in = (_dir_internal_t *)ctx;
|
||||
closedir(in->dir);
|
||||
free(in);
|
||||
}
|
||||
}
|
||||
|
||||
dir_entry_t *dirNext(dir_t ctx) {
|
||||
if(!ctx) return NULL;
|
||||
_dir_internal_t *in = (_dir_internal_t *)ctx;
|
||||
strFree(&in->next.name);
|
||||
struct dirent *dp = readdir(in->dir);
|
||||
if(!dp) return NULL;
|
||||
|
||||
switch(dp->d_type) {
|
||||
case DT_DIR: in->next.type = FS_TYPE_DIR; break;
|
||||
case DT_REG: in->next.type = FS_TYPE_FILE; break;
|
||||
default: in->next.type = FS_TYPE_UNKNOWN; break;
|
||||
}
|
||||
|
||||
in->next.name = strInitStr(dp->d_name);
|
||||
return &in->next;
|
||||
}
|
||||
|
||||
#endif
|
||||
29
dir.h
Normal file
29
dir.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "str.h"
|
||||
|
||||
typedef void *dir_t;
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
str_t name;
|
||||
} dir_entry_t;
|
||||
|
||||
enum {
|
||||
FS_TYPE_UNKNOWN,
|
||||
FS_TYPE_FILE,
|
||||
FS_TYPE_DIR,
|
||||
};
|
||||
|
||||
dir_t dirOpen(const char *path);
|
||||
void dirClose(dir_t ctx);
|
||||
|
||||
dir_entry_t *dirNext(dir_t ctx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
300
dirwatch.c
Normal file
300
dirwatch.c
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
#include "dirwatch.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include "tracelog.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define VC_EXTRALEAN
|
||||
#include <windows.h>
|
||||
#include "str.h"
|
||||
|
||||
typedef struct {
|
||||
const char *path;
|
||||
dirwatch_cb_t on_event;
|
||||
BOOL should_continue;
|
||||
HANDLE stop_event;
|
||||
} __dirwatch_internal_t;
|
||||
|
||||
static DWORD watchDirThread(void *cdata) {
|
||||
__dirwatch_internal_t *desc = (__dirwatch_internal_t*)cdata;
|
||||
|
||||
// stop_event is called from another thread when watchDirThread should exit
|
||||
desc->stop_event = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
|
||||
HANDLE file = CreateFile(
|
||||
desc->path,
|
||||
FILE_LIST_DIRECTORY,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
|
||||
NULL
|
||||
);
|
||||
assert(file != INVALID_HANDLE_VALUE);
|
||||
OVERLAPPED overlapped = {0};
|
||||
overlapped.hEvent = CreateEvent(NULL, FALSE, 0, NULL);
|
||||
|
||||
uint8_t change_buf[1024];
|
||||
BOOL success = ReadDirectoryChangesW(
|
||||
file, change_buf, sizeof(change_buf), TRUE,
|
||||
FILE_NOTIFY_CHANGE_FILE_NAME |
|
||||
FILE_NOTIFY_CHANGE_DIR_NAME |
|
||||
FILE_NOTIFY_CHANGE_LAST_WRITE,
|
||||
NULL, &overlapped, NULL
|
||||
);
|
||||
assert(success);
|
||||
|
||||
HANDLE events[2] = {
|
||||
overlapped.hEvent,
|
||||
desc->stop_event
|
||||
};
|
||||
|
||||
WCHAR old_name[MAX_PATH];
|
||||
dirwatch_file_t data = {0};
|
||||
|
||||
// if the variable is 32bit aligned, a read/write is already atomice
|
||||
// https://docs.microsoft.com/en-us/windows/win32/sync/interlocked-variable-access
|
||||
while(desc->should_continue) {
|
||||
DWORD result = WaitForMultipleObjects(2, events, FALSE, INFINITE);
|
||||
|
||||
if(result == WAIT_OBJECT_0) {
|
||||
DWORD bytes_transferred;
|
||||
GetOverlappedResult(file, &overlapped, &bytes_transferred, FALSE);
|
||||
|
||||
FILE_NOTIFY_INFORMATION *event = (FILE_NOTIFY_INFORMATION*)change_buf;
|
||||
|
||||
for(;;) {
|
||||
DWORD name_len = event->FileNameLength / sizeof(wchar_t);
|
||||
|
||||
switch(event->Action) {
|
||||
case FILE_ACTION_ADDED:
|
||||
data.name = strFromWCHAR(event->FileName, name_len).buf;
|
||||
data.oldname = NULL;
|
||||
desc->on_event(desc->path, DIRWATCH_FILE_ADDED, data);
|
||||
break;
|
||||
case FILE_ACTION_REMOVED:
|
||||
data.name = strFromWCHAR(event->FileName, name_len).buf;
|
||||
data.oldname = NULL;
|
||||
desc->on_event(desc->path, DIRWATCH_FILE_REMOVED, data);
|
||||
break;
|
||||
case FILE_ACTION_RENAMED_OLD_NAME:
|
||||
memcpy(old_name, event->FileName, event->FileNameLength);
|
||||
old_name[event->FileNameLength] = '\0';
|
||||
break;
|
||||
case FILE_ACTION_RENAMED_NEW_NAME:
|
||||
data.name = strFromWCHAR(event->FileName, name_len).buf;
|
||||
data.oldname = strFromWCHAR(old_name, 0).buf;
|
||||
desc->on_event(desc->path, DIRWATCH_FILE_RENAMED, data);
|
||||
break;
|
||||
}
|
||||
|
||||
if(data.name) free(data.name);
|
||||
if(data.oldname) free(data.oldname);
|
||||
|
||||
data = (dirwatch_file_t){0};
|
||||
|
||||
if(event->NextEntryOffset) {
|
||||
*((uint8_t**)&event) += event->NextEntryOffset;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
success = ReadDirectoryChangesW(
|
||||
file, change_buf, sizeof(change_buf), TRUE,
|
||||
FILE_NOTIFY_CHANGE_FILE_NAME |
|
||||
FILE_NOTIFY_CHANGE_DIR_NAME |
|
||||
FILE_NOTIFY_CHANGE_LAST_WRITE,
|
||||
NULL, &overlapped, NULL
|
||||
);
|
||||
assert(success);
|
||||
}
|
||||
// stop_event
|
||||
else if(result == WAIT_OBJECT_0 + 1) {
|
||||
warn("stopping");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
dirwatch_t watchDir(dirwatch_desc_t desc) {
|
||||
dirwatch_t dir = {0};
|
||||
|
||||
__dirwatch_internal_t *opts = HeapAlloc(
|
||||
GetProcessHeap(),
|
||||
0,
|
||||
sizeof(__dirwatch_internal_t)
|
||||
);
|
||||
|
||||
opts->path = desc.path;
|
||||
opts->on_event = desc.on_event;
|
||||
opts->should_continue = true;
|
||||
|
||||
dir.desc = (dirwatch_desc_t *)opts;
|
||||
|
||||
dir.handle = CreateThread(
|
||||
NULL,
|
||||
0,
|
||||
watchDirThread,
|
||||
(void *)dir.desc,
|
||||
0,
|
||||
NULL
|
||||
);
|
||||
|
||||
if(dir.handle) {
|
||||
info("watching %s", desc.path);
|
||||
}
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
void waitForWatchDir(dirwatch_t *ctx) {
|
||||
if(!ctx->handle) return;
|
||||
|
||||
WaitForSingleObject((HANDLE)ctx->handle, INFINITE);
|
||||
|
||||
HeapFree(GetProcessHeap(), 0, ctx->desc);
|
||||
}
|
||||
|
||||
void stopWatchDir(dirwatch_t *ctx, bool immediately) {
|
||||
(void)immediately;
|
||||
__dirwatch_internal_t *opts = (__dirwatch_internal_t *)ctx->desc;
|
||||
opts->should_continue = false;
|
||||
if(immediately) {
|
||||
if(!SetEvent(opts->stop_event)) {
|
||||
err("couldn't signal event stop_event: %d", GetLastError());
|
||||
}
|
||||
}
|
||||
WaitForSingleObject((HANDLE)ctx->handle, INFINITE);
|
||||
|
||||
HeapFree(GetProcessHeap(), 0, ctx->desc);
|
||||
}
|
||||
|
||||
#else
|
||||
#include <sys/inotify.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h> // malloc
|
||||
#include <unistd.h> // read
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <linux/limits.h> // MAX_PATH
|
||||
#include <stdatomic.h>
|
||||
|
||||
#define EVENT_SIZE (sizeof(struct inotify_event))
|
||||
#define BUF_LEN (1024 * (EVENT_SIZE + 16))
|
||||
|
||||
#define ERROR(str) { err(str ": %s", strerror(errno)); goto error; }
|
||||
|
||||
typedef struct {
|
||||
const char *path;
|
||||
dirwatch_cb_t on_event;
|
||||
atomic_bool should_continue;
|
||||
int fd;
|
||||
int wd;
|
||||
} __dirwatch_internal_t;
|
||||
|
||||
static void *watchDirThread(void *cdata) {
|
||||
__dirwatch_internal_t *desc = (__dirwatch_internal_t *)cdata;
|
||||
info("watching %s", desc->path);
|
||||
|
||||
int length/*, fd, wd*/;
|
||||
char buffer[BUF_LEN];
|
||||
|
||||
desc->fd = inotify_init();
|
||||
|
||||
if(desc->fd < 0) {
|
||||
ERROR("inotify_init failed");
|
||||
}
|
||||
|
||||
desc->wd = inotify_add_watch(desc->fd, desc->path, IN_MOVE | IN_CREATE | IN_DELETE);
|
||||
|
||||
char old_path[PATH_MAX] = {0};
|
||||
dirwatch_file_t data = {0};
|
||||
|
||||
while(desc->should_continue) {
|
||||
length = (int)read(desc->fd, buffer, BUF_LEN);
|
||||
|
||||
if(length < 0) {
|
||||
ERROR("couldn't read");
|
||||
}
|
||||
|
||||
for(int i = 0; i < length;) {
|
||||
struct inotify_event *event = (struct inotify_event *) &buffer[i];
|
||||
if(event->len) {
|
||||
uint32_t e = event->mask;
|
||||
// bool is_dir = e & IN_ISDIR;
|
||||
|
||||
if(e & IN_CREATE) {
|
||||
data.name = event->name;
|
||||
desc->on_event(desc->path, DIRWATCH_FILE_ADDED, data);
|
||||
}
|
||||
else if(e & IN_DELETE) {
|
||||
data.name = event->name;
|
||||
desc->on_event(desc->path, DIRWATCH_FILE_REMOVED, data);
|
||||
}
|
||||
else if(e & IN_MOVED_FROM) {
|
||||
memcpy(old_path, event->name, event->len);
|
||||
old_path[event->len] = '\0';
|
||||
}
|
||||
else if(e & IN_MOVED_TO) {
|
||||
data.oldname = old_path;
|
||||
data.name = event->name;
|
||||
desc->on_event(desc->path, DIRWATCH_FILE_RENAMED, data);
|
||||
}
|
||||
}
|
||||
|
||||
i += EVENT_SIZE + event->len;
|
||||
}
|
||||
}
|
||||
|
||||
inotify_rm_watch(desc->fd, desc->wd);
|
||||
close(desc->fd);
|
||||
|
||||
return (void*)0;
|
||||
error:
|
||||
return (void*)1;
|
||||
}
|
||||
|
||||
dirwatch_t watchDir(dirwatch_desc_t desc) {
|
||||
dirwatch_t dir = {0};
|
||||
|
||||
__dirwatch_internal_t *opts = malloc(sizeof(__dirwatch_internal_t));
|
||||
opts->path = desc.path;
|
||||
opts->on_event = desc.on_event;
|
||||
opts->fd = 0;
|
||||
opts->wd = 0;
|
||||
opts->should_continue = true;
|
||||
|
||||
dir.desc = (dirwatch_desc_t *)opts;
|
||||
|
||||
pthread_t thread;
|
||||
pthread_create(&thread, NULL, watchDirThread, opts);
|
||||
|
||||
dir.handle = (void *)thread;
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
void waitForWatchDir(dirwatch_t *ctx) {
|
||||
pthread_join((pthread_t)ctx->handle, NULL);
|
||||
free(ctx->desc);
|
||||
}
|
||||
|
||||
void stopWatchDir(dirwatch_t *ctx, bool immediately) {
|
||||
__dirwatch_internal_t *opts = (__dirwatch_internal_t *)ctx->desc;
|
||||
opts->should_continue = false;
|
||||
// this one might get called in the thread first, but it doesn't matter
|
||||
if(immediately) {
|
||||
inotify_rm_watch(opts->fd, opts->wd);
|
||||
close(opts->fd);
|
||||
}
|
||||
pthread_join((pthread_t)ctx->handle, NULL);
|
||||
free(opts);
|
||||
}
|
||||
|
||||
#endif
|
||||
62
dirwatch.h
Normal file
62
dirwatch.h
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Basic usage:
|
||||
*
|
||||
* // this function will be called from another thread every time
|
||||
* // something happens to 'path'
|
||||
* void onEvent(const char *path, int action, dirwatch_file_t file) {
|
||||
* switch(action) {
|
||||
* case DIRWATCH_FILE_ADDED: [do something] break;
|
||||
* case DIRWATCH_FILE_REMOVED: [do something] break;
|
||||
* case DIRWATCH_FILE_RENAMED: [do something] break;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* int main() {
|
||||
* dirwatch_t dir = watchDir((dirwatch_desc_t){
|
||||
* .dirname = "watch/",
|
||||
* .on_event = onEvent
|
||||
* });
|
||||
*
|
||||
* waitForWatchDir(&dir);
|
||||
* }
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
enum {
|
||||
DIRWATCH_FILE_ADDED,
|
||||
DIRWATCH_FILE_REMOVED,
|
||||
DIRWATCH_FILE_RENAMED,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
char *oldname;
|
||||
} dirwatch_file_t;
|
||||
|
||||
typedef void (*dirwatch_cb_t)(const char *path, int action, dirwatch_file_t data);
|
||||
|
||||
typedef struct {
|
||||
const char *path;
|
||||
dirwatch_cb_t on_event;
|
||||
} dirwatch_desc_t;
|
||||
|
||||
typedef struct {
|
||||
void *handle;
|
||||
dirwatch_desc_t *desc;
|
||||
} dirwatch_t;
|
||||
|
||||
// Creates a thread and starts watching the folder specified by desc
|
||||
// if any action happend on_event will be called from this thread
|
||||
dirwatch_t watchDir(dirwatch_desc_t desc);
|
||||
// waits forever
|
||||
void waitForWatchDir(dirwatch_t *ctx);
|
||||
// stops dirwatch thread, if immediately is true, it will try to close it right away
|
||||
// otherwise it might wait for one last event
|
||||
void stopWatchDir(dirwatch_t *ctx, bool immediately);
|
||||
9
file.h
9
file.h
|
|
@ -1,8 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include "str.h"
|
||||
#include "strview.h"
|
||||
|
||||
enum {
|
||||
FILE_READ, FILE_WRITE, FILE_BOTH
|
||||
|
|
@ -29,3 +32,7 @@ bool fileSeekEnd(file_t *ctx);
|
|||
void fileRewind(file_t *ctx);
|
||||
|
||||
uint64_t fileTell(file_t *ctx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
18
fs.c
18
fs.c
|
|
@ -25,11 +25,12 @@ static int _modeToType(unsigned int mode) {
|
|||
|
||||
fs_stat_t fsStat(file_t fp) {
|
||||
TCHAR path[MAX_PATH];
|
||||
DWORD pathlen = GetFinalPathNameByHandle(
|
||||
GetFinalPathNameByHandle(
|
||||
(HANDLE)fp.handle,
|
||||
path,
|
||||
MAX_PATH,
|
||||
0);
|
||||
0
|
||||
);
|
||||
|
||||
struct stat statbuf;
|
||||
int res = stat(path, &statbuf);
|
||||
|
|
@ -69,6 +70,13 @@ fs_time_t fsAsTime(int64_t timer) {
|
|||
}
|
||||
}
|
||||
|
||||
bool fsIsDir(const char *path) {
|
||||
DWORD attr = GetFileAttributes(path);
|
||||
|
||||
return attr != INVALID_FILE_ATTRIBUTES &&
|
||||
attr & FILE_ATTRIBUTE_DIRECTORY;
|
||||
}
|
||||
|
||||
#else
|
||||
#include <sys/stat.h>
|
||||
|
||||
|
|
@ -118,4 +126,10 @@ fs_time_t fsAsTime(int64_t timer) {
|
|||
return (fs_time_t) { 0 };
|
||||
}
|
||||
}
|
||||
|
||||
bool fsIsDir(const char *path) {
|
||||
struct stat statbuf;
|
||||
return stat(path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode);
|
||||
}
|
||||
|
||||
#endif
|
||||
10
fs.h
10
fs.h
|
|
@ -1,5 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
|
|
@ -32,3 +36,9 @@ typedef struct {
|
|||
|
||||
fs_stat_t fsStat(file_t fp);
|
||||
fs_time_t fsAsTime(int64_t time);
|
||||
|
||||
bool fsIsDir(const char *path);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
96
http.c
96
http.c
|
|
@ -7,17 +7,20 @@
|
|||
#include "os.h"
|
||||
#include "tracelog.h"
|
||||
|
||||
#define T http_field_t
|
||||
#define VEC_SHORT_NAME field_vec
|
||||
#define VEC_DISABLE_ERASE_WHEN
|
||||
#define VEC_NO_DECLARATION
|
||||
#include "vec.h"
|
||||
|
||||
// == INTERNAL ================================================================
|
||||
|
||||
static void _setField(http_field_t **fields_ptr, int *fields_count_ptr, const char *key, const char *value) {
|
||||
http_field_t *fields = *fields_ptr;
|
||||
int fields_count = *fields_count_ptr;
|
||||
|
||||
static void _setField(field_vec_t *fields, const char *key, const char *value) {
|
||||
// search if the field already exists
|
||||
for(int i = 0; i < fields_count; ++i) {
|
||||
if(stricmp(fields[i].key, key) == 0) {
|
||||
for(size_t i = 0; i < fields->size; ++i) {
|
||||
if(stricmp(fields->buf[i].key, key) == 0) {
|
||||
// replace value
|
||||
char **curval = &fields[i].value;
|
||||
char **curval = &fields->buf[i].value;
|
||||
size_t cur = strlen(*curval);
|
||||
size_t new = strlen(value);
|
||||
if(new > cur) {
|
||||
|
|
@ -29,17 +32,16 @@ static void _setField(http_field_t **fields_ptr, int *fields_count_ptr, const ch
|
|||
}
|
||||
}
|
||||
// otherwise, add it to the list
|
||||
(*fields_count_ptr)++;;
|
||||
(*fields_ptr) = realloc(fields, sizeof(http_field_t) * (*fields_count_ptr));
|
||||
http_field_t *field = &(*fields_ptr)[(*fields_count_ptr) - 1];
|
||||
|
||||
http_field_t field;
|
||||
size_t klen = strlen(key);
|
||||
size_t vlen = strlen(value);
|
||||
field->key = malloc(klen + 1);
|
||||
field->value = malloc(vlen + 1);
|
||||
memcpy(field->key, key, klen);
|
||||
memcpy(field->value, value, vlen);
|
||||
field->key[klen] = field->value[vlen] = '\0';
|
||||
field.key = malloc(klen + 1);
|
||||
field.value = malloc(vlen + 1);
|
||||
memcpy(field.key, key, klen);
|
||||
memcpy(field.value, value, vlen);
|
||||
field.key[klen] = field.value[vlen] = '\0';
|
||||
|
||||
field_vecPush(fields, field);
|
||||
}
|
||||
|
||||
// == HTTP VERSION ============================================================
|
||||
|
|
@ -59,19 +61,19 @@ http_request_t reqInit() {
|
|||
}
|
||||
|
||||
void reqFree(http_request_t *ctx) {
|
||||
for(int i = 0; i < ctx->field_count; ++i) {
|
||||
free(ctx->fields[i].key);
|
||||
free(ctx->fields[i].value);
|
||||
for(size_t i = 0; i < ctx->fields.size; ++i) {
|
||||
free(ctx->fields.buf[i].key);
|
||||
free(ctx->fields.buf[i].value);
|
||||
}
|
||||
free(ctx->fields);
|
||||
field_vecFree(&ctx->fields);
|
||||
free(ctx->uri);
|
||||
free(ctx->body);
|
||||
memset(ctx, 0, sizeof(http_request_t));
|
||||
}
|
||||
|
||||
bool reqHasField(http_request_t *ctx, const char *key) {
|
||||
for(int i = 0; i < ctx->field_count; ++i) {
|
||||
if(stricmp(ctx->fields[i].key, key) == 0) {
|
||||
for(size_t i = 0; i < ctx->fields.size; ++i) {
|
||||
if(stricmp(ctx->fields.buf[i].key, key) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -79,7 +81,7 @@ bool reqHasField(http_request_t *ctx, const char *key) {
|
|||
}
|
||||
|
||||
void reqSetField(http_request_t *ctx, const char *key, const char *value) {
|
||||
_setField(&ctx->fields, &ctx->field_count, key, value);
|
||||
_setField(&ctx->fields, key, value);
|
||||
}
|
||||
|
||||
void reqSetUri(http_request_t *ctx, const char *uri) {
|
||||
|
|
@ -116,8 +118,8 @@ str_ostream_t reqPrepare(http_request_t *ctx) {
|
|||
method, ctx->uri, ctx->version.major, ctx->version.minor
|
||||
);
|
||||
|
||||
for(int i = 0; i < ctx->field_count; ++i) {
|
||||
ostrPrintf(&out, "%s: %s\r\n", ctx->fields[i].key, ctx->fields[i].value);
|
||||
for(size_t i = 0; i < ctx->fields.size; ++i) {
|
||||
ostrPrintf(&out, "%s: %s\r\n", ctx->fields.buf[i].key, ctx->fields.buf[i].value);
|
||||
}
|
||||
|
||||
ostrAppendview(&out, strvInit("\r\n"));
|
||||
|
|
@ -129,9 +131,9 @@ error:
|
|||
return out;
|
||||
}
|
||||
|
||||
size_t reqString(http_request_t *ctx, char **str) {
|
||||
str_t reqString(http_request_t *ctx) {
|
||||
str_ostream_t out = reqPrepare(ctx);
|
||||
return ostrMove(&out, str);
|
||||
return ostrMove(&out);
|
||||
}
|
||||
|
||||
// == HTTP RESPONSE ===========================================================
|
||||
|
|
@ -143,18 +145,18 @@ http_response_t resInit() {
|
|||
}
|
||||
|
||||
void resFree(http_response_t *ctx) {
|
||||
for(int i = 0; i < ctx->field_count; ++i) {
|
||||
free(ctx->fields[i].key);
|
||||
free(ctx->fields[i].value);
|
||||
for(size_t i = 0; i < ctx->fields.size; ++i) {
|
||||
free(ctx->fields.buf[i].key);
|
||||
free(ctx->fields.buf[i].value);
|
||||
}
|
||||
free(ctx->fields);
|
||||
field_vecFree(&ctx->fields);
|
||||
free(ctx->body);
|
||||
memset(ctx, 0, sizeof(http_response_t));
|
||||
}
|
||||
|
||||
bool resHasField(http_response_t *ctx, const char *key) {
|
||||
for(int i = 0; i < ctx->field_count; ++i) {
|
||||
if(stricmp(ctx->fields[i].key, key) == 0) {
|
||||
for(size_t i = 0; i < ctx->fields.size; ++i) {
|
||||
if(stricmp(ctx->fields.buf[i].key, key) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -162,9 +164,9 @@ bool resHasField(http_response_t *ctx, const char *key) {
|
|||
}
|
||||
|
||||
const char *resGetField(http_response_t *ctx, const char *field) {
|
||||
for(int i = 0; i < ctx->field_count; ++i) {
|
||||
if(stricmp(ctx->fields[i].key, field) == 0) {
|
||||
return ctx->fields[i].value;
|
||||
for(size_t i = 0; i < ctx->fields.size; ++i) {
|
||||
if(stricmp(ctx->fields.buf[i].key, field) == 0) {
|
||||
return ctx->fields.buf[i].value;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
|
|
@ -194,7 +196,7 @@ void resParse(http_response_t *ctx, const char *data) {
|
|||
if(tran_encoding == NULL || stricmp(tran_encoding, "chunked") != 0) {
|
||||
strview_t body = istrGetviewLen(&in, 0, SIZE_MAX);
|
||||
free(ctx->body);
|
||||
strvCopy(body, &ctx->body);
|
||||
ctx->body = strvCopy(body).buf;
|
||||
}
|
||||
else {
|
||||
fatal("chunked encoding not implemented yet");
|
||||
|
|
@ -215,10 +217,10 @@ void resParseFields(http_response_t *ctx, str_istream_t *in) {
|
|||
char *key_str = NULL;
|
||||
char *value_str = NULL;
|
||||
|
||||
strvCopy(key, &key_str);
|
||||
strvCopy(value, &value_str);
|
||||
key_str = strvCopy(key).buf;
|
||||
value_str = strvCopy(value).buf;
|
||||
|
||||
_setField(&ctx->fields, &ctx->field_count, key_str, value_str);
|
||||
_setField(&ctx->fields, key_str, value_str);
|
||||
|
||||
free(key_str);
|
||||
free(value_str);
|
||||
|
|
@ -246,7 +248,7 @@ void hcliSetHost(http_client_t *ctx, const char *hostname) {
|
|||
strview_t hostview = strvInit(hostname);
|
||||
// if the hostname starts with http:// (case insensitive)
|
||||
if(strvICompare(strvSubstr(hostview, 0, 7), strvInit("http://")) == 0) {
|
||||
strvCopy(strvSubstr(hostview, 7, SIZE_MAX), &ctx->host_name);
|
||||
ctx->host_name = strvCopy(strvSubstr(hostview, 7, SIZE_MAX)).buf;
|
||||
}
|
||||
else if(strvICompare(strvSubstr(hostview, 0, 8), strvInit("https://")) == 0) {
|
||||
err("HTTPS protocol not yet supported");
|
||||
|
|
@ -254,7 +256,7 @@ void hcliSetHost(http_client_t *ctx, const char *hostname) {
|
|||
}
|
||||
else {
|
||||
// undefined protocol, use HTTP
|
||||
strvCopy(hostview, &ctx->host_name);
|
||||
ctx->host_name = strvCopy(hostview).buf;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -281,7 +283,7 @@ http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *req) {
|
|||
}
|
||||
|
||||
http_response_t res = resInit();
|
||||
char *request_str = NULL;
|
||||
str_t req_str = strInit();
|
||||
str_ostream_t received = ostrInitLen(1024);
|
||||
|
||||
if(!skInit()) {
|
||||
|
|
@ -296,13 +298,13 @@ http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *req) {
|
|||
}
|
||||
|
||||
if(skConnect(ctx->socket, ctx->host_name, ctx->port)) {
|
||||
size_t len = reqString(req, &request_str);
|
||||
if(len == 0) {
|
||||
req_str = reqString(req);
|
||||
if(req_str.len == 0) {
|
||||
err("couldn't get string from request");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if(skSend(ctx->socket, request_str, (int)len) == SOCKET_ERROR) {
|
||||
if(skSend(ctx->socket, req_str.buf, (int)req_str.len) == SOCKET_ERROR) {
|
||||
err("couldn't send request to socket: %s", skGetErrorString());
|
||||
goto error;
|
||||
}
|
||||
|
|
@ -336,7 +338,7 @@ error:
|
|||
err("couldn't clean up sockets %s", skGetErrorString());
|
||||
}
|
||||
skopen_error:
|
||||
free(request_str);
|
||||
strFree(&req_str);
|
||||
ostrFree(&received);
|
||||
return res;
|
||||
}
|
||||
|
|
|
|||
16
http.h
16
http.h
|
|
@ -8,8 +8,8 @@ extern "C" {
|
|||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "str.h"
|
||||
#include "strstream.h"
|
||||
#include "strview.h"
|
||||
#include "socket.h"
|
||||
|
||||
enum {
|
||||
|
|
@ -64,13 +64,18 @@ typedef struct {
|
|||
char *value;
|
||||
} http_field_t;
|
||||
|
||||
#define T http_field_t
|
||||
#define VEC_SHORT_NAME field_vec
|
||||
#define VEC_DISABLE_ERASE_WHEN
|
||||
#define VEC_NO_IMPLEMENTATION
|
||||
#include "vec.h"
|
||||
|
||||
// == HTTP REQUEST ============================================================
|
||||
|
||||
typedef struct {
|
||||
int method;
|
||||
http_version_t version;
|
||||
http_field_t *fields;
|
||||
int field_count;
|
||||
field_vec_t fields;
|
||||
char *uri;
|
||||
char *body;
|
||||
} http_request_t;
|
||||
|
|
@ -84,14 +89,13 @@ void reqSetField(http_request_t *ctx, const char *key, const char *value);
|
|||
void reqSetUri(http_request_t *ctx, const char *uri);
|
||||
|
||||
str_ostream_t reqPrepare(http_request_t *ctx);
|
||||
size_t reqString(http_request_t *ctx, char **str);
|
||||
str_t reqString(http_request_t *ctx);
|
||||
|
||||
// == HTTP RESPONSE ===========================================================
|
||||
|
||||
typedef struct {
|
||||
int status_code;
|
||||
http_field_t *fields;
|
||||
int field_count;
|
||||
field_vec_t fields;
|
||||
http_version_t version;
|
||||
char *body;
|
||||
} http_response_t;
|
||||
|
|
|
|||
2
os.c
2
os.c
|
|
@ -36,7 +36,7 @@ ssize_t getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
*ptr++ = c;
|
||||
*ptr++ = (char)c;
|
||||
if(c == delimiter) {
|
||||
*ptr = '\0';
|
||||
result = ptr - *buf;
|
||||
|
|
|
|||
5
slice.h
5
slice.h
|
|
@ -1,5 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <stddef.h> // size_t
|
||||
|
||||
#define slice_t(type) struct { type buf; size_t len; }
|
||||
6
socket.c
6
socket.c
|
|
@ -51,7 +51,7 @@ bool skCleanup() {
|
|||
}
|
||||
|
||||
socket_t skOpen(skType type) {
|
||||
int sock_type;
|
||||
int sock_type = 0;
|
||||
|
||||
switch(type) {
|
||||
case SOCK_TCP: sock_type = SOCK_STREAM; break;
|
||||
|
|
@ -181,11 +181,11 @@ int skReceivePro(socket_t sock, char *buf, int len, int flags) {
|
|||
}
|
||||
|
||||
int skReceiveFrom(socket_t sock, char *buf, int len, sk_addrin_t *from) {
|
||||
int fromlen = sizeof(sk_addr_t);
|
||||
socket_len_t fromlen = sizeof(sk_addr_t);
|
||||
return skReceiveFromPro(sock, buf, len, 0, (sk_addr_t*)from, &fromlen);
|
||||
}
|
||||
|
||||
int skReceiveFromPro(socket_t sock, char *buf, int len, int flags, sk_addr_t *from, int *fromlen) {
|
||||
int skReceiveFromPro(socket_t sock, char *buf, int len, int flags, sk_addr_t *from, socket_len_t *fromlen) {
|
||||
return recvfrom(sock, buf, len, flags, from, fromlen);
|
||||
}
|
||||
|
||||
|
|
|
|||
2
socket.h
2
socket.h
|
|
@ -93,7 +93,7 @@ int skReceivePro(socket_t sock, char *buf, int len, int flags);
|
|||
// Receives a datagram and stores the source address.
|
||||
int skReceiveFrom(socket_t sock, char *buf, int len, sk_addrin_t *from);
|
||||
// Receives a datagram and stores the source address.
|
||||
int skReceiveFromPro(socket_t sock, char *buf, int len, int flags, sk_addr_t *from, int *fromlen);
|
||||
int skReceiveFromPro(socket_t sock, char *buf, int len, int flags, sk_addr_t *from, socket_len_t *fromlen);
|
||||
|
||||
// Checks that a opened socket is valid, returns true on success
|
||||
bool skIsValid(socket_t sock);
|
||||
|
|
|
|||
357
str.c
357
str.c
|
|
@ -2,7 +2,25 @@
|
|||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "tracelog.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define VC_EXTRALEAN
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <iconv.h>
|
||||
#endif
|
||||
|
||||
#ifndef min
|
||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
// == STR_T ========================================================
|
||||
|
||||
str_t strInit(void) {
|
||||
return (str_t) {
|
||||
|
|
@ -34,6 +52,99 @@ void strFree(str_t *ctx) {
|
|||
ctx->len = 0;
|
||||
}
|
||||
|
||||
str_t strFromWCHAR(const wchar_t *src, size_t len) {
|
||||
if(len == 0) len = wcslen(src);
|
||||
|
||||
#ifdef _WIN32
|
||||
// TODO CP_ACP should be CP_UTF8 but if i put CP_UTF8 it doesn't work??
|
||||
int result_len = WideCharToMultiByte(
|
||||
CP_ACP, 0,
|
||||
src, (int)len,
|
||||
NULL, 0,
|
||||
NULL, NULL
|
||||
);
|
||||
char *buf = malloc(result_len + 1);
|
||||
if(buf) {
|
||||
WideCharToMultiByte(
|
||||
CP_ACP, 0,
|
||||
src, (int)len,
|
||||
buf, result_len,
|
||||
NULL, NULL
|
||||
);
|
||||
buf[result_len] = '\0';
|
||||
}
|
||||
return (str_t) {
|
||||
.buf = buf,
|
||||
.len = result_len
|
||||
};
|
||||
#else
|
||||
size_t actual_len = len * sizeof(wchar_t);
|
||||
|
||||
size_t dest_len = len * 6;
|
||||
char *dest = malloc(dest_len);
|
||||
|
||||
iconv_t cd = iconv_open("UTF-8", "WCHAR_T");
|
||||
assert(cd);
|
||||
|
||||
size_t dest_left = dest_len;
|
||||
char *dest_temp = dest;
|
||||
char *src_temp = (char*)src;
|
||||
size_t lost = iconv(cd, &src_temp, &actual_len, &dest_temp, &dest_left);
|
||||
assert(lost != ((size_t)-1));
|
||||
|
||||
dest_len -= dest_left;
|
||||
dest = realloc(dest, dest_len + 1);
|
||||
dest[dest_len] = '\0';
|
||||
|
||||
iconv_close(cd);
|
||||
|
||||
return (str_t){
|
||||
.buf = dest,
|
||||
.len = dest_len
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
wchar_t *strToWCHAR(str_t ctx) {
|
||||
#ifdef _WIN32
|
||||
UINT codepage = CP_ACP;
|
||||
int len = MultiByteToWideChar(
|
||||
codepage, 0,
|
||||
ctx.buf, (int)ctx.len,
|
||||
NULL, 0
|
||||
);
|
||||
wchar_t *str = malloc(sizeof(wchar_t) * (len + 1));
|
||||
if(!str) return NULL;
|
||||
len = MultiByteToWideChar(
|
||||
codepage, 0,
|
||||
ctx.buf, (int)ctx.len,
|
||||
str, len
|
||||
);
|
||||
str[len] = '\0';
|
||||
return str;
|
||||
#else
|
||||
size_t dest_len = ctx.len * sizeof(wchar_t);
|
||||
char *dest = malloc(dest_len);
|
||||
|
||||
iconv_t cd = iconv_open("WCHAR_T", "UTF-8");
|
||||
assert(cd);
|
||||
|
||||
size_t dest_left = dest_len;
|
||||
char *dest_temp = dest;
|
||||
char *src_temp = ctx.buf;
|
||||
size_t lost = iconv(cd, &src_temp, &ctx.len, &dest_temp, &dest_left);
|
||||
assert(lost != ((size_t)-1));
|
||||
|
||||
dest_len -= dest_left;
|
||||
dest = realloc(dest, dest_len + 1);
|
||||
dest[dest_len] = '\0';
|
||||
|
||||
iconv_close(cd);
|
||||
|
||||
return (wchar_t *)dest;
|
||||
#endif
|
||||
}
|
||||
|
||||
str_t strMove(str_t *ctx) {
|
||||
str_t str = strInitBuf(ctx->buf, ctx->len);
|
||||
ctx->buf = NULL;
|
||||
|
|
@ -117,7 +228,13 @@ void strSwap(str_t *ctx, str_t *other) {
|
|||
ctx->len = len;
|
||||
}
|
||||
|
||||
#include "tracelog.h"
|
||||
void strReplace(str_t *ctx, char from, char to) {
|
||||
for(size_t i = 0; i < ctx->len; ++i) {
|
||||
if(ctx->buf[i] == from) {
|
||||
ctx->buf[i] = to;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
str_t strSubstr(str_t *ctx, size_t pos, size_t len) {
|
||||
if(strIsEmpty(ctx)) return strInit();
|
||||
|
|
@ -136,7 +253,7 @@ strview_t strSubview(str_t *ctx, size_t pos, size_t len) {
|
|||
|
||||
void strLower(str_t *ctx) {
|
||||
for(size_t i = 0; i < ctx->len; ++i) {
|
||||
ctx->buf[i] = tolower(ctx->buf[i]);
|
||||
ctx->buf[i] = (char)tolower(ctx->buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -146,6 +263,242 @@ str_t strToLower(str_t ctx) {
|
|||
return str;
|
||||
}
|
||||
|
||||
// == STRVIEW_T ====================================================
|
||||
|
||||
strview_t strvInit(const char *cstr) {
|
||||
return strvInitLen(cstr, cstr ? strlen(cstr) : 0);
|
||||
}
|
||||
|
||||
strview_t strvInitStr(str_t str) {
|
||||
return strvInitLen(str.buf, str.len);
|
||||
}
|
||||
|
||||
strview_t strvInitLen(const char *buf, size_t size) {
|
||||
return (strview_t) {
|
||||
.buf = buf,
|
||||
.len = size
|
||||
};
|
||||
}
|
||||
|
||||
char strvFront(strview_t ctx) {
|
||||
return ctx.buf[0];
|
||||
}
|
||||
|
||||
char strvBack(strview_t ctx) {
|
||||
return ctx.buf[ctx.len - 1];
|
||||
}
|
||||
|
||||
const char *strvBegin(strview_t *ctx) {
|
||||
return ctx->buf;
|
||||
}
|
||||
|
||||
const char *strvEnd(strview_t *ctx) {
|
||||
return ctx->buf + ctx->len;
|
||||
}
|
||||
|
||||
bool strvIsEmpty(strview_t ctx) {
|
||||
return ctx.len == 0;
|
||||
}
|
||||
|
||||
void strvRemovePrefix(strview_t *ctx, size_t n) {
|
||||
ctx->buf += n;
|
||||
ctx->len -= n;
|
||||
}
|
||||
|
||||
void strvRemoveSuffix(strview_t *ctx, size_t n) {
|
||||
ctx->len -= n;
|
||||
}
|
||||
|
||||
str_t strvCopy(strview_t ctx) {
|
||||
return strInitView(ctx);
|
||||
}
|
||||
|
||||
str_t strvCopyN(strview_t ctx, size_t count, size_t from) {
|
||||
size_t sz = ctx.len + 1 - from;
|
||||
count = min(count, sz);
|
||||
return strInitBuf(ctx.buf + from, count);
|
||||
}
|
||||
|
||||
size_t strvCopyBuf(strview_t ctx, char *buf, size_t len, size_t from) {
|
||||
size_t sz = ctx.len + 1 - from;
|
||||
len = min(len, sz);
|
||||
memcpy(buf, ctx.buf + from, len);
|
||||
buf[len - 1] = '\0';
|
||||
return len - 1;
|
||||
}
|
||||
|
||||
strview_t strvSubstr(strview_t ctx, size_t from, size_t len) {
|
||||
if(from > ctx.len) from = ctx.len - len;
|
||||
size_t sz = ctx.len - from;
|
||||
return strvInitLen(ctx.buf + from, min(len, sz));
|
||||
}
|
||||
|
||||
int strvCompare(strview_t ctx, strview_t other) {
|
||||
if(ctx.len < other.len) return -1;
|
||||
if(ctx.len > other.len) return 1;
|
||||
return memcmp(ctx.buf, other.buf, ctx.len);
|
||||
}
|
||||
|
||||
int strvICompare(strview_t ctx, strview_t other) {
|
||||
if(ctx.len < other.len) return -1;
|
||||
if(ctx.len > other.len) return 1;
|
||||
for(size_t i = 0; i < ctx.len; ++i) {
|
||||
int a = tolower(ctx.buf[i]);
|
||||
int b = tolower(other.buf[i]);
|
||||
if(a != b) return a - b;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool strvStartsWith(strview_t ctx, char c) {
|
||||
return strvFront(ctx) == c;
|
||||
}
|
||||
|
||||
bool strvStartsWithView(strview_t ctx, strview_t view) {
|
||||
if(ctx.len < view.len) return false;
|
||||
return memcmp(ctx.buf, view.buf, view.len) == 0;
|
||||
}
|
||||
|
||||
bool strvEndsWith(strview_t ctx, char c) {
|
||||
return strvBack(ctx) == c;
|
||||
}
|
||||
|
||||
bool strvEndsWithView(strview_t ctx, strview_t view) {
|
||||
if(ctx.len < view.len) return false;
|
||||
return memcmp(ctx.buf + ctx.len - view.len, view.buf, view.len) == 0;
|
||||
}
|
||||
|
||||
bool strvContains(strview_t ctx, char c) {
|
||||
for(size_t i = 0; i < ctx.len; ++i) {
|
||||
if(ctx.buf[i] == c) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool strvContainsView(strview_t ctx, strview_t view) {
|
||||
if(ctx.len < view.len) return false;
|
||||
size_t end = ctx.len - view.len;
|
||||
for(size_t i = 0; i < end; ++i) {
|
||||
if(memcmp(ctx.buf + i, view.buf, view.len) == 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t strvFind(strview_t ctx, char c, size_t from) {
|
||||
for(size_t i = from; i < ctx.len; ++i) {
|
||||
if(ctx.buf[i] == c) return i;
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
size_t strvFindView(strview_t ctx, strview_t view, size_t from) {
|
||||
if(ctx.len < view.len) return SIZE_MAX;
|
||||
size_t end = ctx.len - view.len;
|
||||
for(size_t i = from; i < end; ++i) {
|
||||
if(memcmp(ctx.buf + i, view.buf, view.len) == 0) return i;
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
size_t strvRFind(strview_t ctx, char c, size_t from) {
|
||||
if(from >= ctx.len) {
|
||||
from = ctx.len - 1;
|
||||
}
|
||||
|
||||
const char *buf = ctx.buf + from;
|
||||
for(; buf >= ctx.buf; --buf) {
|
||||
if(*buf == c) return (buf - ctx.buf);
|
||||
}
|
||||
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
size_t strvRFindView(strview_t ctx, strview_t view, size_t from) {
|
||||
from = min(from, ctx.len);
|
||||
|
||||
if(view.len > ctx.len) {
|
||||
from -= view.len;
|
||||
}
|
||||
|
||||
const char *buf = ctx.buf + from;
|
||||
for(; buf >= ctx.buf; --buf) {
|
||||
if(memcmp(buf, view.buf, view.len) == 0) return (buf - ctx.buf);
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
size_t strvFindFirstOf(strview_t ctx, strview_t view, size_t from) {
|
||||
if(ctx.len < view.len) return SIZE_MAX;
|
||||
for(size_t i = from; i < ctx.len; ++i) {
|
||||
for(size_t j = 0; j < view.len; ++j) {
|
||||
if(ctx.buf[i] == view.buf[j]) return i;
|
||||
}
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
size_t strvFindLastOf(strview_t ctx, strview_t view, size_t from) {
|
||||
if(from >= ctx.len) {
|
||||
from = ctx.len - 1;
|
||||
}
|
||||
|
||||
const char *buf = ctx.buf + from;
|
||||
for(; buf >= ctx.buf; --buf) {
|
||||
for(size_t j = 0; j < view.len; ++j) {
|
||||
if(*buf == view.buf[j]) return (buf - ctx.buf);
|
||||
}
|
||||
}
|
||||
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
size_t strvFindFirstNot(strview_t ctx, char c, size_t from) {
|
||||
size_t end = ctx.len - 1;
|
||||
for(size_t i = from; i < end; ++i) {
|
||||
if(ctx.buf[i] != c) return i;
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
size_t strvFindFirstNotOf(strview_t ctx, strview_t view, size_t from) {
|
||||
for(size_t i = from; i < ctx.len; ++i) {
|
||||
if(!strvContains(view, ctx.buf[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
size_t strvFindLastNot(strview_t ctx, char c, size_t from) {
|
||||
if(from >= ctx.len) {
|
||||
from = ctx.len - 1;
|
||||
}
|
||||
|
||||
const char *buf = ctx.buf + from;
|
||||
for(; buf >= ctx.buf; --buf) {
|
||||
if(*buf != c) {
|
||||
return buf - ctx.buf;
|
||||
}
|
||||
}
|
||||
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
size_t strvFindLastNotOf(strview_t ctx, strview_t view, size_t from) {
|
||||
if(from >= ctx.len) {
|
||||
from = ctx.len - 1;
|
||||
}
|
||||
|
||||
const char *buf = ctx.buf + from;
|
||||
for(; buf >= ctx.buf; --buf) {
|
||||
if(!strvContains(view, *buf)) {
|
||||
return buf - ctx.buf;
|
||||
}
|
||||
}
|
||||
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
#ifdef STR_TESTING
|
||||
#include <stdio.h>
|
||||
#include "tracelog.h"
|
||||
|
|
|
|||
80
str.h
80
str.h
|
|
@ -4,23 +4,35 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "slice.h"
|
||||
#include "strview.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <limits.h>
|
||||
#include <wchar.h>
|
||||
// #include "strview.h"
|
||||
|
||||
#define STR_TESTING
|
||||
#define STRV_NOT_FOUND SIZE_MAX
|
||||
|
||||
// typedef struct {
|
||||
// char *buf;
|
||||
// size_t len;
|
||||
// } str_t;
|
||||
typedef struct {
|
||||
char *buf;
|
||||
size_t len;
|
||||
} str_t;
|
||||
|
||||
typedef slice_t(char *) str_t;
|
||||
typedef struct {
|
||||
const char *buf;
|
||||
size_t len;
|
||||
} strview_t;
|
||||
|
||||
// == STR_T ========================================================
|
||||
|
||||
str_t strInit(void);
|
||||
str_t strInitStr(const char *cstr);
|
||||
str_t strInitView(strview_t view);
|
||||
str_t strInitBuf(const char *buf, size_t len);
|
||||
|
||||
str_t strFromWCHAR(const wchar_t *src, size_t len);
|
||||
wchar_t *strToWCHAR(str_t ctx);
|
||||
|
||||
void strFree(str_t *ctx);
|
||||
|
||||
str_t strMove(str_t *ctx);
|
||||
|
|
@ -46,6 +58,8 @@ char strPop(str_t *ctx);
|
|||
|
||||
void strSwap(str_t *ctx, str_t *other);
|
||||
|
||||
void strReplace(str_t *ctx, char from, char to);
|
||||
|
||||
// if len == SIZE_MAX, copies until end
|
||||
str_t strSubstr(str_t *ctx, size_t pos, size_t len);
|
||||
// if len == SIZE_MAX, returns until end
|
||||
|
|
@ -58,6 +72,56 @@ str_t strToLower(str_t ctx);
|
|||
void strTest(void);
|
||||
#endif
|
||||
|
||||
// == STRVIEW_T ====================================================
|
||||
|
||||
strview_t strvInit(const char *cstr);
|
||||
strview_t strvInitStr(str_t str);
|
||||
strview_t strvInitLen(const char *buf, size_t size);
|
||||
|
||||
char strvFront(strview_t ctx);
|
||||
char strvBack(strview_t ctx);
|
||||
const char *strvBegin(strview_t *ctx);
|
||||
const char *strvEnd(strview_t *ctx);
|
||||
// move view forward by n characters
|
||||
void strvRemovePrefix(strview_t *ctx, size_t n);
|
||||
// move view backwards by n characters
|
||||
void strvRemoveSuffix(strview_t *ctx, size_t n);
|
||||
|
||||
bool strvIsEmpty(strview_t ctx);
|
||||
|
||||
str_t strvCopy(strview_t ctx);
|
||||
str_t strvCopyN(strview_t ctx, size_t count, size_t from);
|
||||
size_t strvCopyBuf(strview_t ctx, char *buf, size_t len, size_t from);
|
||||
|
||||
strview_t strvSubstr(strview_t ctx, size_t from, size_t len);
|
||||
int strvCompare(strview_t ctx, strview_t other);
|
||||
int strvICompare(strview_t ctx, strview_t other);
|
||||
|
||||
bool strvStartsWith(strview_t ctx, char c);
|
||||
bool strvStartsWithView(strview_t ctx, strview_t view);
|
||||
|
||||
bool strvEndsWith(strview_t ctx, char c);
|
||||
bool strvEndsWithView(strview_t ctx, strview_t view);
|
||||
|
||||
bool strvContains(strview_t ctx, char c);
|
||||
bool strvContainsView(strview_t ctx, strview_t view);
|
||||
|
||||
size_t strvFind(strview_t ctx, char c, size_t from);
|
||||
size_t strvFindView(strview_t ctx, strview_t view, size_t from);
|
||||
|
||||
size_t strvRFind(strview_t ctx, char c, size_t from);
|
||||
size_t strvRFindView(strview_t ctx, strview_t view, size_t from);
|
||||
|
||||
// Finds the first occurrence of any of the characters of 'view' in this view
|
||||
size_t strvFindFirstOf(strview_t ctx, strview_t view, size_t from);
|
||||
size_t strvFindLastOf(strview_t ctx, strview_t view, size_t from);
|
||||
|
||||
size_t strvFindFirstNot(strview_t ctx, char c, size_t from);
|
||||
size_t strvFindFirstNotOf(strview_t ctx, strview_t view, size_t from);
|
||||
size_t strvFindLastNot(strview_t ctx, char c, size_t from);
|
||||
size_t strvFindLastNotOf(strview_t ctx, strview_t view, size_t from);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
64
strstream.c
64
strstream.c
|
|
@ -303,8 +303,8 @@ strview_t istrGetviewLen(str_istream_t *ctx, size_t off, size_t len) {
|
|||
|
||||
/* == OUTPUT STREAM =========================================== */
|
||||
|
||||
static void _ostrRealloc(str_ostream_t *ctx, size_t atlest) {
|
||||
ctx->allocated = (ctx->allocated * 2) + atlest;
|
||||
static void _ostrRealloc(str_ostream_t *ctx, size_t needed) {
|
||||
ctx->allocated = (ctx->allocated * 2) + needed;
|
||||
ctx->buf = realloc(ctx->buf, ctx->allocated);
|
||||
}
|
||||
|
||||
|
|
@ -329,22 +329,48 @@ str_ostream_t ostrInitStr(const char *cstr, size_t len) {
|
|||
return stream;
|
||||
}
|
||||
|
||||
char ostrBack(str_ostream_t *ctx) {
|
||||
return ctx->buf[ctx->size - 1];
|
||||
}
|
||||
|
||||
void ostrFree(str_ostream_t *ctx) {
|
||||
free(ctx->buf);
|
||||
ctx->buf = NULL;
|
||||
ctx->size = 0;
|
||||
ctx->allocated = 0;
|
||||
}
|
||||
|
||||
size_t ostrMove(str_ostream_t *ctx, char **str) {
|
||||
*str = ctx->buf;
|
||||
ctx->buf = NULL;
|
||||
size_t sz = ctx->size;
|
||||
ostrFree(ctx);
|
||||
return sz;
|
||||
void ostrClear(str_ostream_t *ctx) {
|
||||
ctx->size = 0;
|
||||
}
|
||||
|
||||
str_t ostrMove(str_ostream_t *ctx) {
|
||||
str_t str = ostrAsStr(ctx);
|
||||
*ctx = (str_ostream_t){0};
|
||||
return str;
|
||||
}
|
||||
|
||||
char ostrBack(str_ostream_t *ctx) {
|
||||
if(ctx->size == 0) return '\0';
|
||||
return ctx->buf[ctx->size - 1];
|
||||
}
|
||||
|
||||
str_t ostrAsStr(str_ostream_t *ctx) {
|
||||
return (str_t) {
|
||||
.buf = ctx->buf,
|
||||
.len = ctx->size
|
||||
};
|
||||
}
|
||||
|
||||
strview_t ostrAsView(str_ostream_t *ctx) {
|
||||
return (strview_t) {
|
||||
.buf = ctx->buf,
|
||||
.len = ctx->size
|
||||
};
|
||||
}
|
||||
|
||||
void ostrReplace(str_ostream_t *ctx, char from, char to) {
|
||||
for(size_t i = 0; i < ctx->size; ++i) {
|
||||
if(ctx->buf[i] == from) {
|
||||
ctx->buf[i] = to;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ostrPrintf(str_ostream_t *ctx, const char *fmt, ...) {
|
||||
|
|
@ -384,17 +410,21 @@ error:
|
|||
#define APPEND_BUF_LEN 20
|
||||
|
||||
void ostrPutc(str_ostream_t *ctx, char c) {
|
||||
if(ctx->size >= ctx->allocated) {
|
||||
_ostrRealloc(ctx, 1);
|
||||
}
|
||||
ctx->buf[ctx->size++] = c;
|
||||
ctx->buf[ctx->size] = '\0';
|
||||
ostrAppendchar(ctx, c);
|
||||
}
|
||||
|
||||
void ostrAppendbool(str_ostream_t *ctx, bool val) {
|
||||
ostrAppendview(ctx, strvInit(val ? "true" : "false"));
|
||||
}
|
||||
|
||||
void ostrAppendchar(str_ostream_t *ctx, char val) {
|
||||
if(ctx->size >= ctx->allocated) {
|
||||
_ostrRealloc(ctx, 1);
|
||||
}
|
||||
ctx->buf[ctx->size++] = val;
|
||||
ctx->buf[ctx->size] = '\0';
|
||||
}
|
||||
|
||||
void ostrAppendu8(str_ostream_t *ctx, uint8_t val) {
|
||||
char buf[APPEND_BUF_LEN];
|
||||
int len = snprintf(buf, sizeof(buf), "%hhu", val);
|
||||
|
|
|
|||
13
strstream.h
13
strstream.h
|
|
@ -8,7 +8,7 @@ extern "C" {
|
|||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "strview.h"
|
||||
#include "str.h"
|
||||
|
||||
/* == INPUT STREAM ============================================ */
|
||||
|
||||
|
|
@ -73,13 +73,20 @@ str_ostream_t ostrInitLen(size_t initial_alloc);
|
|||
str_ostream_t ostrInitStr(const char *buf, size_t len);
|
||||
|
||||
void ostrFree(str_ostream_t *ctx);
|
||||
size_t ostrMove(str_ostream_t *ctx, char **str);
|
||||
void ostrClear(str_ostream_t *ctx);
|
||||
str_t ostrMove(str_ostream_t *ctx);
|
||||
|
||||
char ostrBack(str_ostream_t *ctx);
|
||||
str_t ostrAsStr(str_ostream_t *ctx);
|
||||
strview_t ostrAsView(str_ostream_t *ctx);
|
||||
|
||||
void ostrReplace(str_ostream_t *ctx, char from, char to);
|
||||
|
||||
void ostrPrintf(str_ostream_t *ctx, const char *fmt, ...);
|
||||
|
||||
void ostrPutc(str_ostream_t *ctx, char c);
|
||||
|
||||
void ostrAppendbool(str_ostream_t *ctx, bool val);
|
||||
void ostrAppendchar(str_ostream_t *ctx, char val);
|
||||
void ostrAppendu8(str_ostream_t *ctx, uint8_t val);
|
||||
void ostrAppendu16(str_ostream_t *ctx, uint16_t val);
|
||||
void ostrAppendu32(str_ostream_t *ctx, uint32_t val);
|
||||
|
|
|
|||
250
strview.c
250
strview.c
|
|
@ -1,250 +0,0 @@
|
|||
#include "strview.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "tracelog.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#ifndef min
|
||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
|
||||
strview_t strvInit(const char *cstr) {
|
||||
return strvInitLen(cstr, cstr ? strlen(cstr) : 0);
|
||||
}
|
||||
|
||||
strview_t strvInitLen(const char *buf, size_t size) {
|
||||
strview_t view;
|
||||
view.buf = buf;
|
||||
view.len = size;
|
||||
return view;
|
||||
}
|
||||
|
||||
char strvFront(strview_t ctx) {
|
||||
return ctx.buf[0];
|
||||
}
|
||||
|
||||
char strvBack(strview_t ctx) {
|
||||
return ctx.buf[ctx.len - 1];
|
||||
}
|
||||
|
||||
const char *strvBegin(strview_t *ctx) {
|
||||
return ctx->buf;
|
||||
}
|
||||
|
||||
const char *strvEnd(strview_t *ctx) {
|
||||
return ctx->buf + ctx->len;
|
||||
}
|
||||
|
||||
bool strvIsEmpty(strview_t ctx) {
|
||||
return ctx.len == 0;
|
||||
}
|
||||
|
||||
void strvRemovePrefix(strview_t *ctx, size_t n) {
|
||||
ctx->buf += n;
|
||||
ctx->len -= n;
|
||||
}
|
||||
|
||||
void strvRemoveSuffix(strview_t *ctx, size_t n) {
|
||||
ctx->len -= n;
|
||||
}
|
||||
|
||||
size_t strvCopy(strview_t ctx, char **buf) {
|
||||
*buf = malloc(ctx.len + 1);
|
||||
memcpy(*buf, ctx.buf, ctx.len);
|
||||
(*buf)[ctx.len] = '\0';
|
||||
return ctx.len;
|
||||
}
|
||||
|
||||
size_t strvCopyN(strview_t ctx, char **buf, size_t count, size_t from) {
|
||||
size_t sz = ctx.len + 1 - from;
|
||||
count = min(count, sz);
|
||||
*buf = malloc(count + 1);
|
||||
memcpy(*buf, ctx.buf + from, count);
|
||||
(*buf)[count] = '\0';
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t strvCopyBuf(strview_t ctx, char *buf, size_t len, size_t from) {
|
||||
size_t sz = ctx.len + 1 - from;
|
||||
len = min(len, sz);
|
||||
memcpy(buf, ctx.buf + from, len);
|
||||
buf[len - 1] = '\0';
|
||||
return len - 1;
|
||||
}
|
||||
|
||||
strview_t strvSubstr(strview_t ctx, size_t from, size_t len) {
|
||||
if(from > ctx.len) from = ctx.len - len;
|
||||
size_t sz = ctx.len - from;
|
||||
return strvInitLen(ctx.buf + from, min(len, sz));
|
||||
}
|
||||
|
||||
int strvCompare(strview_t ctx, strview_t other) {
|
||||
if(ctx.len < other.len) return -1;
|
||||
if(ctx.len > other.len) return 1;
|
||||
return memcmp(ctx.buf, other.buf, ctx.len);
|
||||
}
|
||||
|
||||
int strvICompare(strview_t ctx, strview_t other) {
|
||||
if(ctx.len < other.len) return -1;
|
||||
if(ctx.len > other.len) return 1;
|
||||
for(size_t i = 0; i < ctx.len; ++i) {
|
||||
char a = tolower(ctx.buf[i]);
|
||||
char b = tolower(other.buf[i]);
|
||||
if(a != b) return a - b;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool strvStartsWith(strview_t ctx, char c) {
|
||||
return strvFront(ctx) == c;
|
||||
}
|
||||
|
||||
bool strvStartsWithView(strview_t ctx, strview_t view) {
|
||||
if(ctx.len < view.len) return false;
|
||||
return memcmp(ctx.buf, view.buf, view.len) == 0;
|
||||
}
|
||||
|
||||
bool strvEndsWith(strview_t ctx, char c) {
|
||||
return strvBack(ctx) == c;
|
||||
}
|
||||
|
||||
bool strvEndsWithView(strview_t ctx, strview_t view) {
|
||||
if(ctx.len < view.len) return false;
|
||||
return memcmp(ctx.buf + ctx.len - view.len, view.buf, view.len) == 0;
|
||||
}
|
||||
|
||||
bool strvContains(strview_t ctx, char c) {
|
||||
for(size_t i = 0; i < ctx.len; ++i) {
|
||||
if(ctx.buf[i] == c) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool strvContainsView(strview_t ctx, strview_t view) {
|
||||
if(ctx.len < view.len) return false;
|
||||
size_t end = ctx.len - view.len;
|
||||
for(size_t i = 0; i < end; ++i) {
|
||||
if(memcmp(ctx.buf + i, view.buf, view.len) == 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t strvFind(strview_t ctx, char c, size_t from) {
|
||||
for(size_t i = from; i < ctx.len; ++i) {
|
||||
if(ctx.buf[i] == c) return i;
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
size_t strvFindView(strview_t ctx, strview_t view, size_t from) {
|
||||
if(ctx.len < view.len) return SIZE_MAX;
|
||||
size_t end = ctx.len - view.len;
|
||||
for(size_t i = from; i < end; ++i) {
|
||||
if(memcmp(ctx.buf + i, view.buf, view.len) == 0) return i;
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
size_t strvRFind(strview_t ctx, char c, size_t from) {
|
||||
if(from >= ctx.len) {
|
||||
from = ctx.len - 1;
|
||||
}
|
||||
|
||||
const char *buf = ctx.buf + from;
|
||||
for(; buf >= ctx.buf; --buf) {
|
||||
if(*buf == c) return (buf - ctx.buf);
|
||||
}
|
||||
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
size_t strvRFindView(strview_t ctx, strview_t view, size_t from) {
|
||||
from = min(from, ctx.len);
|
||||
|
||||
if(view.len > ctx.len) {
|
||||
from -= view.len;
|
||||
}
|
||||
|
||||
const char *buf = ctx.buf + from;
|
||||
for(; buf >= ctx.buf; --buf) {
|
||||
if(memcmp(buf, view.buf, view.len) == 0) return (buf - ctx.buf);
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
size_t strvFindFirstOf(strview_t ctx, strview_t view, size_t from) {
|
||||
if(ctx.len < view.len) return SIZE_MAX;
|
||||
for(size_t i = from; i < ctx.len; ++i) {
|
||||
for(size_t j = 0; j < view.len; ++j) {
|
||||
if(ctx.buf[i] == view.buf[j]) return i;
|
||||
}
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
size_t strvFindLastOf(strview_t ctx, strview_t view, size_t from) {
|
||||
if(from >= ctx.len) {
|
||||
from = ctx.len - 1;
|
||||
}
|
||||
|
||||
const char *buf = ctx.buf + from;
|
||||
for(; buf >= ctx.buf; --buf) {
|
||||
for(size_t j = 0; j < view.len; ++j) {
|
||||
if(*buf == view.buf[j]) return (buf - ctx.buf);
|
||||
}
|
||||
}
|
||||
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
size_t strvFindFirstNot(strview_t ctx, char c, size_t from) {
|
||||
size_t end = ctx.len - 1;
|
||||
for(size_t i = from; i < end; ++i) {
|
||||
if(ctx.buf[i] != c) return i;
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
size_t strvFindFirstNotOf(strview_t ctx, strview_t view, size_t from) {
|
||||
for(size_t i = from; i < ctx.len; ++i) {
|
||||
if(!strvContains(view, ctx.buf[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
size_t strvFindLastNot(strview_t ctx, char c, size_t from) {
|
||||
if(from >= ctx.len) {
|
||||
from = ctx.len - 1;
|
||||
}
|
||||
|
||||
const char *buf = ctx.buf + from;
|
||||
for(; buf >= ctx.buf; --buf) {
|
||||
if(*buf != c) {
|
||||
return buf - ctx.buf;
|
||||
}
|
||||
}
|
||||
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
size_t strvFindLastNotOf(strview_t ctx, strview_t view, size_t from) {
|
||||
if(from >= ctx.len) {
|
||||
from = ctx.len - 1;
|
||||
}
|
||||
|
||||
const char *buf = ctx.buf + from;
|
||||
for(; buf >= ctx.buf; --buf) {
|
||||
if(!strvContains(view, *buf)) {
|
||||
return buf - ctx.buf;
|
||||
}
|
||||
}
|
||||
|
||||
return SIZE_MAX;
|
||||
}
|
||||
71
strview.h
71
strview.h
|
|
@ -1,71 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "slice.h"
|
||||
|
||||
#define STRV_NOT_FOUND SIZE_MAX
|
||||
|
||||
// typedef struct {
|
||||
// const char *buf;
|
||||
// size_t size;
|
||||
// } strview_t;
|
||||
|
||||
typedef slice_t(const char *) strview_t;
|
||||
|
||||
strview_t strvInit(const char *cstr);
|
||||
strview_t strvInitLen(const char *buf, size_t size);
|
||||
|
||||
char strvFront(strview_t ctx);
|
||||
char strvBack(strview_t ctx);
|
||||
const char *strvBegin(strview_t *ctx);
|
||||
const char *strvEnd(strview_t *ctx);
|
||||
// move view forward by n characters
|
||||
void strvRemovePrefix(strview_t *ctx, size_t n);
|
||||
// move view backwards by n characters
|
||||
void strvRemoveSuffix(strview_t *ctx, size_t n);
|
||||
|
||||
bool strvIsEmpty(strview_t ctx);
|
||||
|
||||
size_t strvCopy(strview_t ctx, char **buf);
|
||||
size_t strvCopyN(strview_t ctx, char **buf, size_t count, size_t from);
|
||||
size_t strvCopyBuf(strview_t ctx, char *buf, size_t len, size_t from);
|
||||
|
||||
strview_t strvSubstr(strview_t ctx, size_t from, size_t len);
|
||||
int strvCompare(strview_t ctx, strview_t other);
|
||||
int strvICompare(strview_t ctx, strview_t other);
|
||||
|
||||
bool strvStartsWith(strview_t ctx, char c);
|
||||
bool strvStartsWithView(strview_t ctx, strview_t view);
|
||||
|
||||
bool strvEndsWith(strview_t ctx, char c);
|
||||
bool strvEndsWithView(strview_t ctx, strview_t view);
|
||||
|
||||
bool strvContains(strview_t ctx, char c);
|
||||
bool strvContainsView(strview_t ctx, strview_t view);
|
||||
|
||||
size_t strvFind(strview_t ctx, char c, size_t from);
|
||||
size_t strvFindView(strview_t ctx, strview_t view, size_t from);
|
||||
|
||||
size_t strvRFind(strview_t ctx, char c, size_t from);
|
||||
size_t strvRFindView(strview_t ctx, strview_t view, size_t from);
|
||||
|
||||
// Finds the first occurrence of any of the characters of 'view' in this view
|
||||
size_t strvFindFirstOf(strview_t ctx, strview_t view, size_t from);
|
||||
size_t strvFindLastOf(strview_t ctx, strview_t view, size_t from);
|
||||
|
||||
size_t strvFindFirstNot(strview_t ctx, char c, size_t from);
|
||||
size_t strvFindFirstNotOf(strview_t ctx, strview_t view, size_t from);
|
||||
size_t strvFindLastNot(strview_t ctx, char c, size_t from);
|
||||
size_t strvFindLastNotOf(strview_t ctx, strview_t view, size_t from);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
|
@ -62,7 +62,7 @@ void traceLog(LogLevel level, const char *fmt, ...) {
|
|||
case LogWarning: beg = BOLD YELLOW "[WARNING]: " RESET; break;
|
||||
case LogError: beg = BOLD RED "[ERROR]: " RESET; break;
|
||||
case LogFatal: beg = BOLD RED "[FATAL]: " RESET; break;
|
||||
default: break;
|
||||
default: beg = ""; break;
|
||||
}
|
||||
|
||||
size_t offset = strlen(beg);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ typedef enum {
|
|||
void traceLog(LogLevel level, const char *fmt, ...);
|
||||
void traceUseNewline(bool use_newline);
|
||||
|
||||
#define tall(...) traceLog(LogAll, __VA_ARGS__)
|
||||
#define trace(...) traceLog(LogTrace, __VA_ARGS__)
|
||||
#define debug(...) traceLog(LogDebug, __VA_ARGS__)
|
||||
#define info(...) traceLog(LogInfo, __VA_ARGS__)
|
||||
|
|
|
|||
603
vec.h
Normal file
603
vec.h
Normal file
|
|
@ -0,0 +1,603 @@
|
|||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* Basic usage:
|
||||
* #define T int
|
||||
* // optional, if not defined all the names will be longer, e.g.
|
||||
* // vec_int_t v = vec_intInit();
|
||||
* #define VEC_SHORT_NAME veci
|
||||
* #include <vec.h> // undefines T
|
||||
* [...]
|
||||
* veci_t v = veciInit();
|
||||
* veciPush(&v, 10);
|
||||
* veciEraseAt(&v, 0);
|
||||
* veciFree(&v);
|
||||
*/
|
||||
|
||||
typedef int( *vec_cmp_fn_t)(const void *, const void *);
|
||||
|
||||
#ifndef T
|
||||
#error "Must define T before including vec.h"
|
||||
#endif
|
||||
|
||||
#define VEC_CAT(a, b) a ## b
|
||||
#define VEC_PASTE(a, b) VEC_CAT(a, b)
|
||||
#define TYPE(prefix, type) VEC_PASTE(VEC_PASTE(prefix, _), VEC_PASTE(type, _t))
|
||||
#define ITER(prefix, type) VEC_PASTE(VEC_PASTE(prefix, _), VEC_PASTE(type, _it_t))
|
||||
|
||||
#ifdef VEC_SHORT_NAME
|
||||
#define VEC_T VEC_PASTE(VEC_SHORT_NAME, _t)
|
||||
#define VEC_IT_T VEC_PASTE(VEC_SHORT_NAME, _it_t)
|
||||
#define VEC VEC_SHORT_NAME
|
||||
#else
|
||||
#define VEC_T TYPE(vec, T)
|
||||
#define VEC_IT_T ITER(vec, T)
|
||||
#define VEC VEC_PASTE(vec, VEC_PASTE(_, T))
|
||||
#endif
|
||||
|
||||
#define FUN(postfix) VEC_PASTE(VEC, postfix)
|
||||
|
||||
#ifndef VEC_NO_DECLARATION
|
||||
|
||||
typedef struct {
|
||||
T *buf;
|
||||
size_t size;
|
||||
size_t allocated;
|
||||
} VEC_T;
|
||||
|
||||
#define vec_foreach(type, name, it, vec) \
|
||||
for(type *it = VEC_PASTE(name, Beg)(&vec); it < VEC_PASTE(name, End)(&vec); ++it)
|
||||
|
||||
VEC_T FUN(Init)(void);
|
||||
VEC_T FUN(InitArr)(T *arr, size_t len);
|
||||
void FUN(Free)(VEC_T *ctx);
|
||||
|
||||
VEC_T FUN(Move)(VEC_T *ctx);
|
||||
VEC_T FUN(Copy)(VEC_T *ctx);
|
||||
|
||||
T *FUN(Beg)(VEC_T *ctx);
|
||||
T *FUN(End)(VEC_T *ctx);
|
||||
T *FUN(Back)(VEC_T *ctx);
|
||||
bool FUN(Empty)(VEC_T *ctx);
|
||||
|
||||
void FUN(Realloc)(VEC_T *ctx, size_t needed);
|
||||
void FUN(Reserve)(VEC_T *ctx, size_t newsize);
|
||||
void FUN(ShrinkToFit)(VEC_T *ctx);
|
||||
|
||||
void FUN(Clear)(VEC_T *ctx);
|
||||
void FUN(ClearZero)(VEC_T *ctx);
|
||||
|
||||
void FUN(InsertAt)(VEC_T *ctx, size_t index, T val);
|
||||
void FUN(InsertAfter)(VEC_T *ctx, T *it, T val);
|
||||
void FUN(InsertBefore)(VEC_T *ctx, T *it, T val);
|
||||
|
||||
void FUN(InsertAtSw)(VEC_T *ctx, size_t index, T val);
|
||||
void FUN(InsertAfterSw)(VEC_T *ctx, T *it, T val);
|
||||
void FUN(InsertBeforeSw)(VEC_T *ctx, T *it, T val);
|
||||
|
||||
// swaps with last
|
||||
void FUN(Erase)(VEC_T *ctx, T *it);
|
||||
void FUN(EraseAt)(VEC_T *ctx, size_t index);
|
||||
// moves whole array back one
|
||||
void FUN(EraseMv)(VEC_T *ctx, T *it);
|
||||
void FUN(EraseAtMv)(VEC_T *ctx, size_t index);
|
||||
|
||||
void FUN(Push)(VEC_T *ctx, T val);
|
||||
void FUN(PushRef)(VEC_T *ctx, T *val);
|
||||
T FUN(Pop)(VEC_T *ctx);
|
||||
|
||||
void FUN(Resize)(VEC_T *ctx, size_t newcount, T val);
|
||||
void FUN(ResizeRef)(VEC_T *ctx, size_t newcount, T *val);
|
||||
|
||||
void FUN(EraseWhen)(VEC_T *ctx, T val);
|
||||
|
||||
typedef bool (*TYPE(vec_erase_fn, T))(const T *val, void *udata);
|
||||
void FUN(EraseIf)(VEC_T *ctx, TYPE(vec_erase_fn, T) cmp, void *udata);
|
||||
void FUN(EraseIfMv)(VEC_T *ctx, TYPE(vec_erase_fn, T) cmp, void *udata);
|
||||
|
||||
// typedef int (*TYPE(vec_sort_fn, T))(const T *a, const T *b);
|
||||
void FUN(Sort)(VEC_T *ctx, vec_cmp_fn_t cmp);
|
||||
|
||||
#endif // VEC_NO_DECLARATION
|
||||
|
||||
#ifndef VEC_NO_IMPLEMENTATION
|
||||
|
||||
inline
|
||||
VEC_T FUN(Init)(void) {
|
||||
return (VEC_T){
|
||||
.buf = NULL,
|
||||
.size = 0,
|
||||
.allocated = 0
|
||||
};
|
||||
}
|
||||
|
||||
inline
|
||||
VEC_T FUN(InitArr)(T *arr, size_t len) {
|
||||
VEC_T v = FUN(Init)();
|
||||
FUN(Realloc)(&v, len);
|
||||
memcpy(v.buf, arr, len * sizeof(T));
|
||||
v.size = len;
|
||||
return v;
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(Free)(VEC_T *ctx) {
|
||||
free(ctx->buf);
|
||||
ctx->buf = NULL;
|
||||
ctx->size = 0;
|
||||
ctx->allocated = 0;
|
||||
}
|
||||
|
||||
inline
|
||||
VEC_T FUN(Move)(VEC_T *ctx) {
|
||||
VEC_T mv = *ctx;
|
||||
ctx->buf = NULL;
|
||||
FUN(Free)(ctx);
|
||||
return mv;
|
||||
}
|
||||
|
||||
inline
|
||||
VEC_T FUN(Copy)(VEC_T *ctx) {
|
||||
VEC_T cp = FUN(Init)();
|
||||
if(ctx->buf) {
|
||||
FUN(Reserve)(&cp, ctx->size);
|
||||
memcpy(cp.buf, ctx->buf, ctx->size * sizeof(T));
|
||||
cp.size = ctx->size;
|
||||
}
|
||||
return cp;
|
||||
}
|
||||
|
||||
inline
|
||||
T *FUN(Beg)(VEC_T *ctx) {
|
||||
return ctx->buf;
|
||||
}
|
||||
|
||||
inline
|
||||
T *FUN(End)(VEC_T *ctx) {
|
||||
return ctx->buf + ctx->size;
|
||||
}
|
||||
|
||||
inline
|
||||
T *FUN(Back)(VEC_T *ctx) {
|
||||
return ctx->buf ? &ctx->buf[ctx->size - 1] : NULL;
|
||||
}
|
||||
|
||||
inline
|
||||
bool FUN(Empty)(VEC_T *ctx) {
|
||||
return ctx->buf ? ctx->size == 0 : true;
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(Realloc)(VEC_T *ctx, size_t needed) {
|
||||
if((ctx->size + needed) >= ctx->allocated) {
|
||||
ctx->allocated = (ctx->allocated * 2) + needed;
|
||||
ctx->buf = (T *)realloc(ctx->buf, ctx->allocated * sizeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(Reserve)(VEC_T *ctx, size_t newsize) {
|
||||
if(ctx->allocated < newsize) {
|
||||
ctx->allocated = newsize;
|
||||
ctx->buf = (T *)realloc(ctx->buf, ctx->allocated * sizeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(ShrinkToFit)(VEC_T *ctx) {
|
||||
ctx->allocated = ctx->size;
|
||||
ctx->buf = (T *)realloc(ctx->buf, ctx->allocated * sizeof(T));
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(Clear)(VEC_T *ctx) {
|
||||
ctx->size = 0;
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(ClearZero)(VEC_T *ctx) {
|
||||
ctx->size = 0;
|
||||
memset(ctx->buf, 0, ctx->allocated * sizeof(T));
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(InsertAt)(VEC_T *ctx, size_t index, T val) {
|
||||
FUN(Realloc)(ctx, 1);
|
||||
for(size_t i = ctx->size; i > index; --i) {
|
||||
ctx->buf[i] = ctx->buf[i - 1];
|
||||
}
|
||||
ctx->buf[index] = val;
|
||||
ctx->size++;
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(InsertAfter)(VEC_T *ctx, T *it, T val) {
|
||||
size_t index = it - ctx->buf;
|
||||
// insertAt acts as insertBefore, so we just add 1
|
||||
FUN(InsertAt)(ctx, index + 1, val);
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(InsertBefore)(VEC_T *ctx, T *it, T val) {
|
||||
size_t index = it - ctx->buf;
|
||||
FUN(InsertAt)(ctx, index, val);
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(InsertAtSw)(VEC_T *ctx, size_t index, T val) {
|
||||
FUN(Realloc)(ctx, 1);
|
||||
ctx->buf[ctx->size] = ctx->buf[index];
|
||||
ctx->buf[index] = val;
|
||||
ctx->size++;
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(InsertAfterSw)(VEC_T *ctx, T *it, T val) {
|
||||
size_t index = it - ctx->buf;
|
||||
// insertAt acts as insertBefore, so we just add 1
|
||||
FUN(InsertAtSw)(ctx, index + 1, val);
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(InsertBeforeSw)(VEC_T *ctx, T *it, T val) {
|
||||
size_t index = it - ctx->buf;
|
||||
FUN(InsertAtSw)(ctx, index, val);
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(Erase)(VEC_T *ctx, T *it) {
|
||||
size_t index = it - ctx->buf;
|
||||
FUN(EraseAt)(ctx, index);
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(EraseAt)(VEC_T *ctx, size_t index) {
|
||||
ctx->size--;
|
||||
ctx->buf[index] = ctx->buf[ctx->size];
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(EraseMv)(VEC_T *ctx, T *it) {
|
||||
size_t index = it - ctx->buf;
|
||||
FUN(EraseAtMv)(ctx, index);
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(EraseAtMv)(VEC_T *ctx, size_t index) {
|
||||
ctx->size--;
|
||||
for(size_t i = index; i < ctx->size; ++i) {
|
||||
ctx->buf[i] = ctx->buf[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(Push)(VEC_T *ctx, T val) {
|
||||
FUN(Realloc)(ctx, 1);
|
||||
ctx->buf[ctx->size] = val;
|
||||
ctx->size++;
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(PushRef)(VEC_T *ctx, T *val) {
|
||||
FUN(Realloc)(ctx, 1);
|
||||
ctx->buf[ctx->size] = *val;
|
||||
ctx->size++;
|
||||
}
|
||||
|
||||
inline
|
||||
T FUN(Pop)(VEC_T *ctx) {
|
||||
ctx->size--;
|
||||
return ctx->buf[ctx->size];
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(Resize)(VEC_T *ctx, size_t newcount, T val) {
|
||||
if(newcount <= ctx->size) {
|
||||
ctx->size = newcount;
|
||||
return;
|
||||
}
|
||||
FUN(Realloc)(ctx, newcount - ctx->size);
|
||||
for(size_t i = ctx->size; i < newcount; ++i) {
|
||||
ctx->buf[i] = val;
|
||||
}
|
||||
ctx->size = newcount;
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(ResizeRef)(VEC_T *ctx, size_t newcount, T *val) {
|
||||
if(newcount <= ctx->size) {
|
||||
ctx->size = newcount;
|
||||
}
|
||||
FUN(Realloc)(ctx, newcount - ctx->size);
|
||||
if(val) {
|
||||
for(size_t i = ctx->size; i < newcount; ++i) {
|
||||
ctx->buf[i] = *val;
|
||||
}
|
||||
}
|
||||
ctx->size = newcount;
|
||||
}
|
||||
|
||||
#ifndef VEC_DISABLE_ERASE_WHEN
|
||||
|
||||
inline
|
||||
void FUN(EraseWhen)(VEC_T *ctx, T val) {
|
||||
for(size_t i = 0; i < ctx->size; ++i) {
|
||||
if(ctx->buf[i] == val) {
|
||||
FUN(EraseAt)(ctx, i);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
inline
|
||||
void FUN(EraseIf)(VEC_T *ctx, TYPE(vec_erase_fn, T) cmp, void *udata) {
|
||||
for(size_t i = 0; i < ctx->size; ++i) {
|
||||
if(cmp(&ctx->buf[i], udata)) {
|
||||
FUN(EraseAt)(ctx, i);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(EraseIfMv)(VEC_T *ctx, TYPE(vec_erase_fn, T) cmp, void *udata) {
|
||||
for(size_t i = 0; i < ctx->size; ++i) {
|
||||
if(cmp(&ctx->buf[i], udata)) {
|
||||
FUN(EraseAtMv)(ctx, i);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
void FUN(Sort)(VEC_T *ctx, vec_cmp_fn_t cmp) {
|
||||
qsort(ctx->buf, ctx->size, sizeof(T), cmp);
|
||||
}
|
||||
|
||||
#endif // VEC_NO_IMPLEMENTATION
|
||||
|
||||
#undef FUN
|
||||
#undef VEC
|
||||
#undef VEC_T
|
||||
#undef TYPE
|
||||
#undef VEC_SHORT_NAME
|
||||
#undef T
|
||||
#undef VEC_DISABLE_ERASE_WHEN
|
||||
#undef VEC_NO_DECLARATION
|
||||
#undef VEC_NO_IMPLEMENTATION
|
||||
|
||||
#if 0
|
||||
// vec.h testing:
|
||||
|
||||
#define T int
|
||||
#define VEC_SHORT_NAME veci
|
||||
#include <vec.h>
|
||||
#define foreach(it, vec) vec_foreach(int, veci, it, vec)
|
||||
|
||||
#define T char
|
||||
#define VEC_SHORT_NAME vecc
|
||||
#include <vec.h>
|
||||
#include <tracelog.h>
|
||||
|
||||
|
||||
#define PRINTVALS(v, s) \
|
||||
printf("{ "); \
|
||||
for(size_t i = 0; i < v.size; ++i) \
|
||||
printf(s " ", v.buf[i]); \
|
||||
printf("}\n");
|
||||
|
||||
#define PRINTVEC(v, s) \
|
||||
printf(#v ": {\n"); \
|
||||
printf("\tsize: %zu\n", v.size); \
|
||||
printf("\tallocated: %zu\n", v.allocated); \
|
||||
printf("\tvalues:"); \
|
||||
PRINTVALS(v, s); \
|
||||
printf("}\n"); \
|
||||
|
||||
#define PRINTVECI(v) PRINTVEC(v, "%d")
|
||||
#define PRINTVALSI(v) PRINTVALS(v, "%d")
|
||||
|
||||
bool veciEraseEven(const int *val, void *udata) {
|
||||
return *val % 2 == 0;
|
||||
}
|
||||
|
||||
bool veciEraseHigher(const int *val, void *udata) {
|
||||
return *val > 8;
|
||||
}
|
||||
|
||||
int main() {
|
||||
debug("== TESTING INIT ===========================");
|
||||
{
|
||||
veci_t v = veciInit();
|
||||
PRINTVECI(v);
|
||||
veciFree(&v);
|
||||
|
||||
v = veciInitArr((int[]){1, 2, 3, 4}, 4);
|
||||
veciPush(&v, 25);
|
||||
veciPush(&v, 13);
|
||||
PRINTVECI(v);
|
||||
veciFree(&v);
|
||||
}
|
||||
debug("== TESTING MOVE/COPY ======================");
|
||||
{
|
||||
veci_t a = veciInitArr((int[]){1, 2, 3, 4}, 4);
|
||||
info("before move");
|
||||
PRINTVECI(a);
|
||||
info("after move");
|
||||
veci_t b = veciMove(&a);
|
||||
PRINTVECI(a);
|
||||
PRINTVECI(b);
|
||||
veciFree(&a);
|
||||
veciFree(&b);
|
||||
|
||||
a = veciInitArr((int[]){1, 2, 3, 4}, 4);
|
||||
b = veciCopy(&a);
|
||||
info("copied");
|
||||
PRINTVECI(a);
|
||||
PRINTVECI(b);
|
||||
info("modified b");
|
||||
b.buf[2] = 9;
|
||||
PRINTVECI(a);
|
||||
PRINTVECI(b);
|
||||
veciFree(&a);
|
||||
veciFree(&b);
|
||||
}
|
||||
debug("== TESTING BACK ===========================");
|
||||
{
|
||||
vecc_t v = veccInitArr((char[]){'a', 'b', 'c', 'd', 'e', 'f'}, 6);
|
||||
|
||||
PRINTVEC(v, "%c");
|
||||
info("The last character is '%c'.", *veccBack(&v));
|
||||
|
||||
veccFree(&v);
|
||||
}
|
||||
debug("== TESTING EMPTY ==========================");
|
||||
{
|
||||
veci_t v = veciInit();
|
||||
info("Initially, vecEmpty(): %s", veciEmpty(&v) ? "true":"false");
|
||||
veciPush(&v, 42);
|
||||
info("After adding elements, vecEmpty(): %s", veciEmpty(&v) ? "true":"false");
|
||||
veciFree(&v);
|
||||
}
|
||||
debug("== TESTING RESERVE/SHRINK_TO_FIT/CLEAR ====");
|
||||
{
|
||||
veci_t v = veciInit();
|
||||
info("Default capacity: %zu", v.allocated);
|
||||
veciResize(&v, 100, 0);
|
||||
info("100 elements: %zu", v.allocated);
|
||||
veciResize(&v, 50, 0);
|
||||
info("after resize(50): %zu", v.allocated);
|
||||
veciShrinkToFit(&v);
|
||||
info("after shrinkToFit(): %zu", v.allocated);
|
||||
veciClear(&v);
|
||||
info("after clear(): %zu", v.allocated);
|
||||
veciShrinkToFit(&v);
|
||||
info("after shrinkToFit(): %zu", v.allocated);
|
||||
for(int i = 1000; i < 1300; ++i) {
|
||||
veciPush(&v, i);
|
||||
}
|
||||
info("after adding 300 elements: %zu", v.allocated);
|
||||
veciShrinkToFit(&v);
|
||||
info("after shrinkToFit(): %zu", v.allocated);
|
||||
veciFree(&v);
|
||||
}
|
||||
debug("== TESTING ITERATORS ======================");
|
||||
{
|
||||
veci_t v = veciInitArr((int[]){1, 2, 3, 4, 5}, 5);
|
||||
PRINTVECI(v);
|
||||
info("foreach:");
|
||||
for(int *it = veciBeg(&v); it != veciEnd(&v); ++it) {
|
||||
printf("\t*it: %d\n", *it);
|
||||
}
|
||||
veciFree(&v);
|
||||
}
|
||||
debug("== TESTING INSERT =========================");
|
||||
{
|
||||
veci_t v = veciInit();
|
||||
info("init with 3 100");
|
||||
veciResize(&v, 3, 100);
|
||||
PRINTVALSI(v);
|
||||
info("insert 200 at 0");
|
||||
veciInsertAt(&v, 0, 200);
|
||||
PRINTVALSI(v);
|
||||
info("insert 300 before beginning");
|
||||
veciInsertBefore(&v, veciBeg(&v), 300);
|
||||
PRINTVALSI(v);
|
||||
info("insert 400 after beg + 1");
|
||||
veciInsertAfter(&v, veciBeg(&v) + 1, 400);
|
||||
PRINTVALSI(v);
|
||||
info("insert swap 500 at 3");
|
||||
veciInsertAtSw(&v, 3, 500);
|
||||
PRINTVALSI(v);
|
||||
info("insert swap 600 before beg");
|
||||
veciInsertBeforeSw(&v, veciBeg(&v), 600);
|
||||
PRINTVALSI(v);
|
||||
info("insert swap 700 after end - 4");
|
||||
veciInsertAfterSw(&v, veciEnd(&v) - 4, 700);
|
||||
PRINTVALSI(v);
|
||||
|
||||
veciFree(&v);
|
||||
}
|
||||
debug("== TESTING ERASE ==========================");
|
||||
{
|
||||
veci_t v = veciInitArr((int[]){0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, 10);
|
||||
info("initializing with number from 0 to 9");
|
||||
PRINTVALSI(v);
|
||||
info("erasing beginning");
|
||||
veciErase(&v, veciBeg(&v));
|
||||
PRINTVALSI(v);
|
||||
info("erasing index 5");
|
||||
veciEraseAt(&v, 5);
|
||||
PRINTVALSI(v);
|
||||
info("erasing mv end - 3");
|
||||
veciEraseMv(&v, veciEnd(&v) - 3);
|
||||
PRINTVALSI(v);
|
||||
info("erasing mv index 1");
|
||||
veciEraseAtMv(&v, 1);
|
||||
PRINTVALSI(v);
|
||||
info("erasing mv all even numbers");
|
||||
veciEraseIfMv(&v, veciEraseEven, NULL);
|
||||
PRINTVALSI(v);
|
||||
info("erasing numbers higher than 8");
|
||||
veciEraseIf(&v, veciEraseHigher, NULL);
|
||||
PRINTVALSI(v);
|
||||
|
||||
veciFree(&v);
|
||||
}
|
||||
debug("== TESTING CLEAR_ZERO =====================");
|
||||
{
|
||||
veci_t v = veciInitArr((int[]){0, 1, 2, 3, 4, 5}, 6);
|
||||
info("initialized from 0 to 6");
|
||||
PRINTVECI(v);
|
||||
info("clearZero");
|
||||
size_t oldsize = v.size;
|
||||
veciClearZero(&v);
|
||||
for(int i = 0; i < oldsize; ++i) {
|
||||
printf("\t%d > %d\n", i, v.buf[i]);
|
||||
}
|
||||
}
|
||||
debug("== TESTING PUSH/PUSH_REF ==================");
|
||||
{
|
||||
veci_t v = veciInit();
|
||||
|
||||
info("pushing 10");
|
||||
veciPush(&v, 10);
|
||||
int value = 50;
|
||||
info("pushing reference to value: %d", value);
|
||||
veciPushRef(&v, &value);
|
||||
|
||||
info("vector holds: ");
|
||||
printf("> ");
|
||||
foreach(it, v) {
|
||||
printf("%d ", *it);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
veciFree(&v);
|
||||
}
|
||||
debug("== TESTING POP ============================");
|
||||
{
|
||||
vecc_t v = veccInitArr("hello world!", 12);
|
||||
info("initialized with %.*s", (int)v.size, v.buf);
|
||||
while(!veccEmpty(&v)) {
|
||||
printf("pooped '%c'\n", veccPop(&v));
|
||||
printf("> [%.*s]\n", (int)v.size, v.buf);
|
||||
}
|
||||
veccFree(&v);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue