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:
snarmph 2021-10-25 01:32:31 +01:00
parent aa08240ec9
commit 59b55c7f6c
26 changed files with 4037 additions and 2726 deletions

View file

@ -1,18 +1,22 @@
add_library(Colla STATIC add_library(Colla STATIC
socket.c socket.c
tracelog.c tracelog.c
http.c http.c
strstream.c strstream.c
strview.c str.c
str.c coroutine.c
coroutine.c os.c
os.c fs.c
fs.c file.c
file.c dir.c
) dirwatch.c
)
IF (WIN32)
target_link_libraries(Colla ws2_32.lib) if(MSVC)
ELSE() target_link_libraries(Colla ws2_32.lib)
# posix target_compile_options(Colla PRIVATE /W4)
ENDIF() else()
target_link_libraries(Colla pthread)
target_compile_options(Colla PRIVATE -Wall -Wextra -Wpedantic)
target_compile_definitions(Colla PUBLIC _DEFAULT_SOURCE)
endif()

View file

@ -1,11 +1,11 @@
#include "coroutine.h" #include "coroutine.h"
coroutine_t coInit() { coroutine_t coInit() {
return (coroutine_t) { return (coroutine_t) {
.state = 0 .state = 0
}; };
} }
bool coIsDead(coroutine_t co) { bool coIsDead(coroutine_t co) {
return co.state == -1; return co.state == -1;
} }

View file

@ -1,133 +1,133 @@
#pragma once #pragma once
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
#include <stdbool.h> // bool #include <stdbool.h> // bool
#include <string.h> // memset #include <string.h> // memset
#include "tracelog.h" // fatal #include "tracelog.h" // fatal
// heavily inspired by https://gist.github.com/Enichan/5f01140530ff0133fde19c9549a2a973 // heavily inspired by https://gist.github.com/Enichan/5f01140530ff0133fde19c9549a2a973
#if 0 #if 0
// Usage example: // Usage example:
typedef struct { typedef struct {
int result; int result;
coroutine_t co; coroutine_t co;
} co_int_t; } co_int_t;
bool coVoid(co_void_t *co) { bool coVoid(co_void_t *co) {
costate(co_nostate); costate(co_nostate);
coroutine({ coroutine({
printf("hello"); yield(); printf("hello"); yield();
printf("world"); yield(); printf("world"); yield();
printf("how"); yield(); printf("how"); yield();
printf("is"); yield(); printf("is"); yield();
printf("it"); yield(); printf("it"); yield();
printf("going?\n"); printf("going?\n");
}); });
} }
bool countToTen(co_int_t *co) { bool countToTen(co_int_t *co) {
costate(int i; int ii;); costate(int i; int ii;);
coroutine({ coroutine({
for(self.i = 0; self.i < 10; ++self.i) { for(self.i = 0; self.i < 10; ++self.i) {
self.ii += self.i; self.ii += self.i;
yieldVal(self.ii); yieldVal(self.ii);
} }
}); });
} }
int main() { int main() {
co_void_t covoid = {0}; co_void_t covoid = {0};
while(coVoid(&covoid)) { while(coVoid(&covoid)) {
printf(" "); printf(" ");
} }
co_int_t coint; co_int_t coint;
coint.co = coInit(); coint.co = coInit();
while(countToTen(&coint)) { while(countToTen(&coint)) {
printf("%d ", coint.result); printf("%d ", coint.result);
} }
printf("\n"); printf("\n");
// reset coroutine for next call // reset coroutine for next call
coint.co = coInit(); coint.co = coInit();
while(countToTen(&coint)) { while(countToTen(&coint)) {
printf("%d ", coint.result); printf("%d ", coint.result);
} }
printf("\n"); printf("\n");
} }
#endif #endif
typedef struct { typedef struct {
int state; int state;
} coroutine_t; } coroutine_t;
typedef struct { typedef struct {
coroutine_t co; coroutine_t co;
} co_void_t; } co_void_t;
coroutine_t coInit(); coroutine_t coInit();
bool coIsDead(coroutine_t co); bool coIsDead(coroutine_t co);
#define COVAR co #define COVAR co
#define COSELF self #define COSELF self
#define costate(...) \ #define costate(...) \
typedef struct { bool init; __VA_ARGS__ } COSTATE; \ typedef struct { bool init; __VA_ARGS__ } COSTATE; \
static COSTATE self = {0} static COSTATE self = {0}
#define co_nostate { char __dummy__; } #define co_nostate { char __dummy__; }
#define yieldBreak() \ #define yieldBreak() \
COVAR->co.state = -1; \ COVAR->co.state = -1; \
self.init = false; \ self.init = false; \
return false; return false;
#define coroutine(...) \ #define coroutine(...) \
if(!self.init) { \ if(!self.init) { \
if(COVAR->co.state != 0) { \ if(COVAR->co.state != 0) { \
fatal("coroutine not initialized in %s:%d,\n" \ fatal("coroutine not initialized in %s:%d,\n" \
"did you forget to do '= {0};'?", \ "did you forget to do '= {0};'?", \
__FILE__, __LINE__); \ __FILE__, __LINE__); \
} \ } \
memset(&self, 0, sizeof(self)); \ memset(&self, 0, sizeof(self)); \
self.init = true; \ self.init = true; \
} \ } \
switch(COVAR->co.state) { \ switch(COVAR->co.state) { \
case 0:; \ case 0:; \
__VA_ARGS__ \ __VA_ARGS__ \
} \ } \
yieldBreak(); yieldBreak();
#define yield() _yield(__COUNTER__) #define yield() _yield(__COUNTER__)
#define _yield(count) \ #define _yield(count) \
do { \ do { \
COVAR->co.state = count; \ COVAR->co.state = count; \
return true; \ return true; \
case count:; \ case count:; \
} while(0); } while(0);
#define yieldVal(v) _yieldVal(v, __COUNTER__) #define yieldVal(v) _yieldVal(v, __COUNTER__)
#define _yieldVal(v, count) \ #define _yieldVal(v, count) \
do { \ do { \
COVAR->co.state = count; \ COVAR->co.state = count; \
COVAR->result = v; \ COVAR->result = v; \
return true; \ return true; \
case count:; \ case count:; \
} while(0); } while(0);
// increment __COUNTER__ past 0 so we don't get double case errors // increment __COUNTER__ past 0 so we don't get double case errors
#define CONCAT_IMPL(x, y) x##y #define CONCAT_IMPL(x, y) x##y
#define MACRO_CONCAT(x, y) CONCAT_IMPL(x, y) #define MACRO_CONCAT(x, y) CONCAT_IMPL(x, y)
#define __INC_COUNTER int MACRO_CONCAT(__counter_tmp_, __COUNTER__) #define __INC_COUNTER int MACRO_CONCAT(__counter_tmp_, __COUNTER__)
__INC_COUNTER; __INC_COUNTER;
#undef CONCAT_IMPL #undef CONCAT_IMPL
#undef MACRO_CONCAT #undef MACRO_CONCAT
#undef __INC_COUNTER #undef __INC_COUNTER
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"
#endif #endif

147
dir.c Normal file
View 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
View 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
View 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
View 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);

11
file.h
View file

@ -1,8 +1,11 @@
#pragma once #pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h> #include <stdint.h>
#include "str.h" #include "str.h"
#include "strview.h"
enum { enum {
FILE_READ, FILE_WRITE, FILE_BOTH FILE_READ, FILE_WRITE, FILE_BOTH
@ -28,4 +31,8 @@ size_t fileWrite(file_t *ctx, const void *buf, size_t len);
bool fileSeekEnd(file_t *ctx); bool fileSeekEnd(file_t *ctx);
void fileRewind(file_t *ctx); void fileRewind(file_t *ctx);
uint64_t fileTell(file_t *ctx); uint64_t fileTell(file_t *ctx);
#ifdef __cplusplus
} // extern "C"
#endif

254
fs.c
View file

@ -1,121 +1,135 @@
#include "fs.h" #include "fs.h"
#include <time.h> #include <time.h>
#include <errno.h> #include <errno.h>
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include "tracelog.h" #include "tracelog.h"
#ifdef _WIN32 #ifdef _WIN32
#define VC_EXTRALEAN #define VC_EXTRALEAN
#include <windows.h> #include <windows.h>
#include <sys/stat.h> #include <sys/stat.h>
static int _modeToType(unsigned int mode) { static int _modeToType(unsigned int mode) {
switch(mode & _S_IFMT) { switch(mode & _S_IFMT) {
case _S_IFDIR: return FS_MODE_DIR; case _S_IFDIR: return FS_MODE_DIR;
case _S_IFCHR: return FS_MODE_CHARACTER_DEVICE; case _S_IFCHR: return FS_MODE_CHARACTER_DEVICE;
case _S_IFREG: return FS_MODE_FILE; case _S_IFREG: return FS_MODE_FILE;
case _S_IFIFO: return FS_MODE_FIFO; case _S_IFIFO: return FS_MODE_FIFO;
default: return FS_MODE_UKNOWN; default: return FS_MODE_UKNOWN;
} }
} }
fs_stat_t fsStat(file_t fp) { fs_stat_t fsStat(file_t fp) {
TCHAR path[MAX_PATH]; TCHAR path[MAX_PATH];
DWORD pathlen = GetFinalPathNameByHandle( GetFinalPathNameByHandle(
(HANDLE)fp.handle, (HANDLE)fp.handle,
path, path,
MAX_PATH, MAX_PATH,
0); 0
);
struct stat statbuf;
int res = stat(path, &statbuf); struct stat statbuf;
if(res == 0) { int res = stat(path, &statbuf);
return (fs_stat_t) { if(res == 0) {
.type = _modeToType(statbuf.st_mode), return (fs_stat_t) {
.size = statbuf.st_size, .type = _modeToType(statbuf.st_mode),
.last_access = statbuf.st_atime, .size = statbuf.st_size,
.last_modif = statbuf.st_mtime .last_access = statbuf.st_atime,
}; .last_modif = statbuf.st_mtime
} };
else { }
return (fs_stat_t) { 0 }; else {
} return (fs_stat_t) { 0 };
} }
}
fs_time_t fsAsTime(int64_t timer) {
struct tm t; fs_time_t fsAsTime(int64_t timer) {
errno_t error = localtime_s(&t, &timer); struct tm t;
errno_t error = localtime_s(&t, &timer);
if(error == 0) {
return (fs_time_t) { if(error == 0) {
.year = t.tm_year + 1900, return (fs_time_t) {
.month = t.tm_mon + 1, .year = t.tm_year + 1900,
.day = t.tm_mday, .month = t.tm_mon + 1,
.hour = t.tm_hour, .day = t.tm_mday,
.minutes = t.tm_min, .hour = t.tm_hour,
.seconds = t.tm_sec, .minutes = t.tm_min,
.daylight_saving = t.tm_isdst > 0 .seconds = t.tm_sec,
}; .daylight_saving = t.tm_isdst > 0
} };
else { }
char buf[128]; else {
strerror_s(buf, sizeof(buf), error); char buf[128];
err("%s", buf); strerror_s(buf, sizeof(buf), error);
return (fs_time_t) { 0 }; err("%s", buf);
} return (fs_time_t) { 0 };
} }
}
#else
#include <sys/stat.h> bool fsIsDir(const char *path) {
DWORD attr = GetFileAttributes(path);
static int _modeToType(unsigned int mode) {
switch(mode & __S_IFMT) { return attr != INVALID_FILE_ATTRIBUTES &&
case __S_IFDIR: return FS_MODE_DIR; attr & FILE_ATTRIBUTE_DIRECTORY;
case __S_IFCHR: return FS_MODE_CHARACTER_DEVICE; }
case __S_IFREG: return FS_MODE_FILE;
case __S_IFIFO: return FS_MODE_FIFO; #else
default: return FS_MODE_UKNOWN; #include <sys/stat.h>
}
} static int _modeToType(unsigned int mode) {
switch(mode & __S_IFMT) {
fs_stat_t fsStat(file_t fp) { case __S_IFDIR: return FS_MODE_DIR;
int fd = fileno((FILE*)fp.handle); case __S_IFCHR: return FS_MODE_CHARACTER_DEVICE;
struct stat statbuf; case __S_IFREG: return FS_MODE_FILE;
int res = fstat(fd, &statbuf); case __S_IFIFO: return FS_MODE_FIFO;
if(res == 0) { default: return FS_MODE_UKNOWN;
return (fs_stat_t) { }
.type = _modeToType(statbuf.st_mode), }
.size = statbuf.st_size,
.last_access = statbuf.st_atime, fs_stat_t fsStat(file_t fp) {
.last_modif = statbuf.st_mtime int fd = fileno((FILE*)fp.handle);
}; struct stat statbuf;
} int res = fstat(fd, &statbuf);
else { if(res == 0) {
return (fs_stat_t) { 0 }; return (fs_stat_t) {
} .type = _modeToType(statbuf.st_mode),
} .size = statbuf.st_size,
.last_access = statbuf.st_atime,
fs_time_t fsAsTime(int64_t timer) { .last_modif = statbuf.st_mtime
struct tm *t = localtime(&timer); };
}
if(t) { else {
return (fs_time_t) { return (fs_stat_t) { 0 };
.year = t->tm_year + 1900, }
.month = t->tm_mon + 1, }
.day = t->tm_mday,
.hour = t->tm_hour, fs_time_t fsAsTime(int64_t timer) {
.minutes = t->tm_min, struct tm *t = localtime(&timer);
.seconds = t->tm_sec,
.daylight_saving = t->tm_isdst > 0 if(t) {
}; return (fs_time_t) {
} .year = t->tm_year + 1900,
else { .month = t->tm_mon + 1,
err("%s", strerror(errno)); .day = t->tm_mday,
return (fs_time_t) { 0 }; .hour = t->tm_hour,
} .minutes = t->tm_min,
} .seconds = t->tm_sec,
.daylight_saving = t->tm_isdst > 0
};
}
else {
err("%s", strerror(errno));
return (fs_time_t) { 0 };
}
}
bool fsIsDir(const char *path) {
struct stat statbuf;
return stat(path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode);
}
#endif #endif

78
fs.h
View file

@ -1,34 +1,44 @@
#pragma once #pragma once
#include <stdint.h> #ifdef __cplusplus
#include <stdbool.h> extern "C" {
#endif
#include "file.h"
#include <stdint.h>
enum { #include <stdbool.h>
FS_MODE_FILE,
FS_MODE_DIR, #include "file.h"
FS_MODE_CHARACTER_DEVICE,
FS_MODE_FIFO, enum {
FS_MODE_UKNOWN, FS_MODE_FILE,
}; FS_MODE_DIR,
FS_MODE_CHARACTER_DEVICE,
typedef struct { FS_MODE_FIFO,
int type; FS_MODE_UKNOWN,
uint64_t size; };
int64_t last_access;
int64_t last_modif; typedef struct {
} fs_stat_t; int type;
uint64_t size;
typedef struct { int64_t last_access;
int year; int64_t last_modif;
int month; } fs_stat_t;
int day;
int hour; typedef struct {
int minutes; int year;
int seconds; int month;
bool daylight_saving; int day;
} fs_time_t; int hour;
int minutes;
fs_stat_t fsStat(file_t fp); int seconds;
fs_time_t fsAsTime(int64_t time); bool daylight_saving;
} fs_time_t;
fs_stat_t fsStat(file_t fp);
fs_time_t fsAsTime(int64_t time);
bool fsIsDir(const char *path);
#ifdef __cplusplus
} // extern "C"
#endif

720
http.c
View file

@ -1,359 +1,361 @@
#include "http.h" #include "http.h"
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include "os.h" #include "os.h"
#include "tracelog.h" #include "tracelog.h"
// == INTERNAL ================================================================ #define T http_field_t
#define VEC_SHORT_NAME field_vec
static void _setField(http_field_t **fields_ptr, int *fields_count_ptr, const char *key, const char *value) { #define VEC_DISABLE_ERASE_WHEN
http_field_t *fields = *fields_ptr; #define VEC_NO_DECLARATION
int fields_count = *fields_count_ptr; #include "vec.h"
// search if the field already exists // == INTERNAL ================================================================
for(int i = 0; i < fields_count; ++i) {
if(stricmp(fields[i].key, key) == 0) { static void _setField(field_vec_t *fields, const char *key, const char *value) {
// replace value // search if the field already exists
char **curval = &fields[i].value; for(size_t i = 0; i < fields->size; ++i) {
size_t cur = strlen(*curval); if(stricmp(fields->buf[i].key, key) == 0) {
size_t new = strlen(value); // replace value
if(new > cur) { char **curval = &fields->buf[i].value;
*curval = realloc(*curval, new + 1); size_t cur = strlen(*curval);
} size_t new = strlen(value);
memcpy(*curval, value, new); if(new > cur) {
(*curval)[new] = '\0'; *curval = realloc(*curval, new + 1);
return; }
} memcpy(*curval, value, new);
} (*curval)[new] = '\0';
// otherwise, add it to the list return;
(*fields_count_ptr)++;; }
(*fields_ptr) = realloc(fields, sizeof(http_field_t) * (*fields_count_ptr)); }
http_field_t *field = &(*fields_ptr)[(*fields_count_ptr) - 1]; // otherwise, add it to the list
http_field_t field;
size_t klen = strlen(key); size_t klen = strlen(key);
size_t vlen = strlen(value); size_t vlen = strlen(value);
field->key = malloc(klen + 1); field.key = malloc(klen + 1);
field->value = malloc(vlen + 1); field.value = malloc(vlen + 1);
memcpy(field->key, key, klen); memcpy(field.key, key, klen);
memcpy(field->value, value, vlen); memcpy(field.value, value, vlen);
field->key[klen] = field->value[vlen] = '\0'; field.key[klen] = field.value[vlen] = '\0';
}
field_vecPush(fields, field);
// == HTTP VERSION ============================================================ }
int httpVerNumber(http_version_t ver) { // == HTTP VERSION ============================================================
return (ver.major * 10) + ver.minor;
} int httpVerNumber(http_version_t ver) {
return (ver.major * 10) + ver.minor;
// == HTTP REQUEST ============================================================ }
http_request_t reqInit() { // == HTTP REQUEST ============================================================
http_request_t req;
memset(&req, 0, sizeof(req)); http_request_t reqInit() {
reqSetUri(&req, "/"); http_request_t req;
req.version = (http_version_t){1, 1}; memset(&req, 0, sizeof(req));
return req; reqSetUri(&req, "/");
} req.version = (http_version_t){1, 1};
return req;
void reqFree(http_request_t *ctx) { }
for(int i = 0; i < ctx->field_count; ++i) {
free(ctx->fields[i].key); void reqFree(http_request_t *ctx) {
free(ctx->fields[i].value); for(size_t i = 0; i < ctx->fields.size; ++i) {
} free(ctx->fields.buf[i].key);
free(ctx->fields); free(ctx->fields.buf[i].value);
free(ctx->uri); }
free(ctx->body); field_vecFree(&ctx->fields);
memset(ctx, 0, sizeof(http_request_t)); 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) { bool reqHasField(http_request_t *ctx, const char *key) {
return true; for(size_t i = 0; i < ctx->fields.size; ++i) {
} if(stricmp(ctx->fields.buf[i].key, key) == 0) {
} return true;
return false; }
} }
return false;
void reqSetField(http_request_t *ctx, const char *key, const char *value) { }
_setField(&ctx->fields, &ctx->field_count, key, value);
} void reqSetField(http_request_t *ctx, const char *key, const char *value) {
_setField(&ctx->fields, key, value);
void reqSetUri(http_request_t *ctx, const char *uri) { }
if(uri == NULL) return;
size_t len = strlen(uri); void reqSetUri(http_request_t *ctx, const char *uri) {
if(uri[0] != '/') { if(uri == NULL) return;
len += 1; size_t len = strlen(uri);
ctx->uri = realloc(ctx->uri, len + 1); if(uri[0] != '/') {
ctx->uri[0] = '/'; len += 1;
memcpy(ctx->uri + 1, uri, len); ctx->uri = realloc(ctx->uri, len + 1);
ctx->uri[len] = '\0'; ctx->uri[0] = '/';
} memcpy(ctx->uri + 1, uri, len);
else { ctx->uri[len] = '\0';
ctx->uri = realloc(ctx->uri, len + 1); }
memcpy(ctx->uri, uri, len); else {
ctx->uri[len] = '\0'; ctx->uri = realloc(ctx->uri, len + 1);
} memcpy(ctx->uri, uri, len);
} ctx->uri[len] = '\0';
}
str_ostream_t reqPrepare(http_request_t *ctx) { }
str_ostream_t out = ostrInitLen(1024);
str_ostream_t reqPrepare(http_request_t *ctx) {
const char *method = NULL; str_ostream_t out = ostrInitLen(1024);
switch(ctx->method) {
case REQ_GET: method = "GET"; break; const char *method = NULL;
case REQ_POST: method = "POST"; break; switch(ctx->method) {
case REQ_HEAD: method = "HEAD"; break; case REQ_GET: method = "GET"; break;
case REQ_PUT: method = "PUT"; break; case REQ_POST: method = "POST"; break;
case REQ_DELETE: method = "DELETE"; break; case REQ_HEAD: method = "HEAD"; break;
default: err("unrecognized method: %d", method); goto error; case REQ_PUT: method = "PUT"; break;
} case REQ_DELETE: method = "DELETE"; break;
default: err("unrecognized method: %d", method); goto error;
ostrPrintf(&out, "%s %s HTTP/%hhu.%hhu\r\n", }
method, ctx->uri, ctx->version.major, ctx->version.minor
); ostrPrintf(&out, "%s %s HTTP/%hhu.%hhu\r\n",
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")); }
if(ctx->body) {
ostrAppendview(&out, strvInit(ctx->body)); ostrAppendview(&out, strvInit("\r\n"));
} if(ctx->body) {
ostrAppendview(&out, strvInit(ctx->body));
error: }
return out;
} error:
return out;
size_t reqString(http_request_t *ctx, char **str) { }
str_ostream_t out = reqPrepare(ctx);
return ostrMove(&out, str); str_t reqString(http_request_t *ctx) {
} str_ostream_t out = reqPrepare(ctx);
return ostrMove(&out);
// == HTTP RESPONSE =========================================================== }
http_response_t resInit() { // == HTTP RESPONSE ===========================================================
http_response_t res;
memset(&res, 0, sizeof(res)); http_response_t resInit() {
return res; http_response_t res;
} memset(&res, 0, sizeof(res));
return res;
void resFree(http_response_t *ctx) { }
for(int i = 0; i < ctx->field_count; ++i) {
free(ctx->fields[i].key); void resFree(http_response_t *ctx) {
free(ctx->fields[i].value); for(size_t i = 0; i < ctx->fields.size; ++i) {
} free(ctx->fields.buf[i].key);
free(ctx->fields); free(ctx->fields.buf[i].value);
free(ctx->body); }
memset(ctx, 0, sizeof(http_response_t)); 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) { bool resHasField(http_response_t *ctx, const char *key) {
return true; for(size_t i = 0; i < ctx->fields.size; ++i) {
} if(stricmp(ctx->fields.buf[i].key, key) == 0) {
} return true;
return false; }
} }
return false;
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) { const char *resGetField(http_response_t *ctx, const char *field) {
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; }
} }
return NULL;
void resParse(http_response_t *ctx, const char *data) { }
str_istream_t in = istrInit(data);
void resParse(http_response_t *ctx, const char *data) {
char hp[5]; str_istream_t in = istrInit(data);
istrGetstringBuf(&in, hp, 5);
if(stricmp(hp, "http") != 0) { char hp[5];
err("response doesn't start with 'HTTP', instead with %c%c%c%c", hp[0], hp[1], hp[2], hp[3]); istrGetstringBuf(&in, hp, 5);
return; if(stricmp(hp, "http") != 0) {
} err("response doesn't start with 'HTTP', instead with %c%c%c%c", hp[0], hp[1], hp[2], hp[3]);
istrSkip(&in, 1); // skip / return;
istrGetu8(&in, &ctx->version.major); }
istrSkip(&in, 1); // skip . istrSkip(&in, 1); // skip /
istrGetu8(&in, &ctx->version.minor); istrGetu8(&in, &ctx->version.major);
istrGeti32(&in, &ctx->status_code); istrSkip(&in, 1); // skip .
istrGetu8(&in, &ctx->version.minor);
istrIgnore(&in, '\n'); istrGeti32(&in, &ctx->status_code);
istrSkip(&in, 1); // skip \n
istrIgnore(&in, '\n');
resParseFields(ctx, &in); istrSkip(&in, 1); // skip \n
const char *tran_encoding = resGetField(ctx, "transfer-encoding"); resParseFields(ctx, &in);
if(tran_encoding == NULL || stricmp(tran_encoding, "chunked") != 0) {
strview_t body = istrGetviewLen(&in, 0, SIZE_MAX); const char *tran_encoding = resGetField(ctx, "transfer-encoding");
free(ctx->body); if(tran_encoding == NULL || stricmp(tran_encoding, "chunked") != 0) {
strvCopy(body, &ctx->body); strview_t body = istrGetviewLen(&in, 0, SIZE_MAX);
} free(ctx->body);
else { ctx->body = strvCopy(body).buf;
fatal("chunked encoding not implemented yet"); }
} else {
} fatal("chunked encoding not implemented yet");
}
void resParseFields(http_response_t *ctx, str_istream_t *in) { }
strview_t line;
void resParseFields(http_response_t *ctx, str_istream_t *in) {
do { strview_t line;
line = istrGetview(in, '\r');
do {
size_t pos = strvFind(line, ':', 0); line = istrGetview(in, '\r');
if(pos != STRV_NOT_FOUND) {
strview_t key = strvSubstr(line, 0, pos); size_t pos = strvFind(line, ':', 0);
strview_t value = strvSubstr(line, pos + 2, SIZE_MAX); if(pos != STRV_NOT_FOUND) {
strview_t key = strvSubstr(line, 0, pos);
char *key_str = NULL; strview_t value = strvSubstr(line, pos + 2, SIZE_MAX);
char *value_str = NULL;
char *key_str = NULL;
strvCopy(key, &key_str); char *value_str = NULL;
strvCopy(value, &value_str);
key_str = strvCopy(key).buf;
_setField(&ctx->fields, &ctx->field_count, key_str, value_str); value_str = strvCopy(value).buf;
free(key_str); _setField(&ctx->fields, key_str, value_str);
free(value_str);
} free(key_str);
free(value_str);
istrSkip(in, 2); // skip \r\n }
} while(line.len > 2);
} istrSkip(in, 2); // skip \r\n
} while(line.len > 2);
// == HTTP CLIENT ============================================================= }
http_client_t hcliInit() { // == HTTP CLIENT =============================================================
http_client_t client;
memset(&client, 0, sizeof(client)); http_client_t hcliInit() {
client.port = 80; http_client_t client;
return client; memset(&client, 0, sizeof(client));
} client.port = 80;
return client;
void hcliFree(http_client_t *ctx) { }
free(ctx->host_name);
memset(ctx, 0, sizeof(http_client_t)); void hcliFree(http_client_t *ctx) {
} free(ctx->host_name);
memset(ctx, 0, sizeof(http_client_t));
void hcliSetHost(http_client_t *ctx, const char *hostname) { }
strview_t hostview = strvInit(hostname);
// if the hostname starts with http:// (case insensitive) void hcliSetHost(http_client_t *ctx, const char *hostname) {
if(strvICompare(strvSubstr(hostview, 0, 7), strvInit("http://")) == 0) { strview_t hostview = strvInit(hostname);
strvCopy(strvSubstr(hostview, 7, SIZE_MAX), &ctx->host_name); // if the hostname starts with http:// (case insensitive)
} if(strvICompare(strvSubstr(hostview, 0, 7), strvInit("http://")) == 0) {
else if(strvICompare(strvSubstr(hostview, 0, 8), strvInit("https://")) == 0) { ctx->host_name = strvCopy(strvSubstr(hostview, 7, SIZE_MAX)).buf;
err("HTTPS protocol not yet supported"); }
return; else if(strvICompare(strvSubstr(hostview, 0, 8), strvInit("https://")) == 0) {
} err("HTTPS protocol not yet supported");
else { return;
// undefined protocol, use HTTP }
strvCopy(hostview, &ctx->host_name); else {
} // undefined protocol, use HTTP
} ctx->host_name = strvCopy(hostview).buf;
}
http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *req) { }
if(!reqHasField(req, "Host")) {
reqSetField(req, "Host", ctx->host_name); http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *req) {
} if(!reqHasField(req, "Host")) {
if(!reqHasField(req, "Content-Length")) { reqSetField(req, "Host", ctx->host_name);
if(req->body) { }
str_ostream_t out = ostrInitLen(20); if(!reqHasField(req, "Content-Length")) {
ostrAppendu64(&out, strlen(req->body)); if(req->body) {
reqSetField(req, "Content-Length", out.buf); str_ostream_t out = ostrInitLen(20);
ostrFree(&out); ostrAppendu64(&out, strlen(req->body));
} reqSetField(req, "Content-Length", out.buf);
else { ostrFree(&out);
reqSetField(req, "Content-Length", "0"); }
} else {
} reqSetField(req, "Content-Length", "0");
if(req->method == REQ_POST && !reqHasField(req, "Content-Type")) { }
reqSetField(req, "Content-Type", "application/x-www-form-urlencoded"); }
} if(req->method == REQ_POST && !reqHasField(req, "Content-Type")) {
if(httpVerNumber(req->version) >= 11 && !reqHasField(req, "Connection")) { reqSetField(req, "Content-Type", "application/x-www-form-urlencoded");
reqSetField(req, "Connection", "close"); }
} if(httpVerNumber(req->version) >= 11 && !reqHasField(req, "Connection")) {
reqSetField(req, "Connection", "close");
http_response_t res = resInit(); }
char *request_str = NULL;
str_ostream_t received = ostrInitLen(1024); http_response_t res = resInit();
str_t req_str = strInit();
if(!skInit()) { str_ostream_t received = ostrInitLen(1024);
err("couldn't initialize sockets %s", skGetErrorString());
goto skopen_error; if(!skInit()) {
} err("couldn't initialize sockets %s", skGetErrorString());
goto skopen_error;
ctx->socket = skOpen(SOCK_TCP); }
if(ctx->socket == INVALID_SOCKET) {
err("couldn't open socket %s", skGetErrorString()); ctx->socket = skOpen(SOCK_TCP);
goto error; if(ctx->socket == INVALID_SOCKET) {
} err("couldn't open socket %s", skGetErrorString());
goto error;
if(skConnect(ctx->socket, ctx->host_name, ctx->port)) { }
size_t len = reqString(req, &request_str);
if(len == 0) { if(skConnect(ctx->socket, ctx->host_name, ctx->port)) {
err("couldn't get string from request"); req_str = reqString(req);
goto error; if(req_str.len == 0) {
} err("couldn't get string from request");
goto error;
if(skSend(ctx->socket, request_str, (int)len) == SOCKET_ERROR) { }
err("couldn't send request to socket: %s", skGetErrorString());
goto 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;
char buffer[1024]; }
int read = 0;
do { char buffer[1024];
read = skReceive(ctx->socket, buffer, sizeof(buffer)); int read = 0;
if(read == -1) { do {
err("couldn't get the data from the server: %s", skGetErrorString()); read = skReceive(ctx->socket, buffer, sizeof(buffer));
goto error; if(read == -1) {
} err("couldn't get the data from the server: %s", skGetErrorString());
ostrAppendview(&received, strvInitLen(buffer, read)); goto error;
} while(read != 0); }
ostrAppendview(&received, strvInitLen(buffer, read));
// if the data received is not null terminated } while(read != 0);
if(*(received.buf + received.size) != '\0') {
ostrPutc(&received, '\0'); // if the data received is not null terminated
received.size--; if(*(received.buf + received.size) != '\0') {
} ostrPutc(&received, '\0');
received.size--;
resParse(&res, received.buf); }
}
resParse(&res, received.buf);
if(!skClose(ctx->socket)) { }
err("Couldn't close socket");
} if(!skClose(ctx->socket)) {
err("Couldn't close socket");
error: }
if(!skCleanup()) {
err("couldn't clean up sockets %s", skGetErrorString()); error:
} if(!skCleanup()) {
skopen_error: err("couldn't clean up sockets %s", skGetErrorString());
free(request_str); }
ostrFree(&received); skopen_error:
return res; strFree(&req_str);
} ostrFree(&received);
return res;
http_response_t httpGet(const char *hostname, const char *uri) { }
http_request_t request = reqInit();
request.method = REQ_GET; http_response_t httpGet(const char *hostname, const char *uri) {
reqSetUri(&request, uri); http_request_t request = reqInit();
request.method = REQ_GET;
http_client_t client = hcliInit(); reqSetUri(&request, uri);
hcliSetHost(&client, hostname);
http_client_t client = hcliInit();
http_response_t res = hcliSendRequest(&client, &request); hcliSetHost(&client, hostname);
reqFree(&request); http_response_t res = hcliSendRequest(&client, &request);
hcliFree(&client);
reqFree(&request);
return res; hcliFree(&client);
}
return res;
}

258
http.h
View file

@ -1,128 +1,132 @@
#pragma once #pragma once
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
#include <stdint.h> #include <stdint.h>
#include <stddef.h> #include <stddef.h>
#include <stdbool.h> #include <stdbool.h>
#include "strstream.h" #include "str.h"
#include "strview.h" #include "strstream.h"
#include "socket.h" #include "socket.h"
enum { enum {
REQ_GET, REQ_GET,
REQ_POST, REQ_POST,
REQ_HEAD, REQ_HEAD,
REQ_PUT, REQ_PUT,
REQ_DELETE REQ_DELETE
}; };
enum { enum {
// 2xx: success // 2xx: success
STATUS_OK = 200, STATUS_OK = 200,
STATUS_CREATED = 201, STATUS_CREATED = 201,
STATUS_ACCEPTED = 202, STATUS_ACCEPTED = 202,
STATUS_NO_CONTENT = 204, STATUS_NO_CONTENT = 204,
STATUS_RESET_CONTENT = 205, STATUS_RESET_CONTENT = 205,
STATUS_PARTIAL_CONTENT = 206, STATUS_PARTIAL_CONTENT = 206,
// 3xx: redirection // 3xx: redirection
STATUS_MULTIPLE_CHOICES = 300, STATUS_MULTIPLE_CHOICES = 300,
STATUS_MOVED_PERMANENTLY = 301, STATUS_MOVED_PERMANENTLY = 301,
STATUS_MOVED_TEMPORARILY = 302, STATUS_MOVED_TEMPORARILY = 302,
STATUS_NOT_MODIFIED = 304, STATUS_NOT_MODIFIED = 304,
// 4xx: client error // 4xx: client error
STATUS_BAD_REQUEST = 400, STATUS_BAD_REQUEST = 400,
STATUS_UNAUTHORIZED = 401, STATUS_UNAUTHORIZED = 401,
STATUS_FORBIDDEN = 403, STATUS_FORBIDDEN = 403,
STATUS_NOT_FOUND = 404, STATUS_NOT_FOUND = 404,
STATUS_RANGE_NOT_SATISFIABLE = 407, STATUS_RANGE_NOT_SATISFIABLE = 407,
// 5xx: server error // 5xx: server error
STATUS_INTERNAL_SERVER_ERROR = 500, STATUS_INTERNAL_SERVER_ERROR = 500,
STATUS_NOT_IMPLEMENTED = 501, STATUS_NOT_IMPLEMENTED = 501,
STATUS_BAD_GATEWAY = 502, STATUS_BAD_GATEWAY = 502,
STATUS_SERVICE_NOT_AVAILABLE = 503, STATUS_SERVICE_NOT_AVAILABLE = 503,
STATUS_GATEWAY_TIMEOUT = 504, STATUS_GATEWAY_TIMEOUT = 504,
STATUS_VERSION_NOT_SUPPORTED = 505, STATUS_VERSION_NOT_SUPPORTED = 505,
}; };
typedef struct { typedef struct {
uint8_t major; uint8_t major;
uint8_t minor; uint8_t minor;
} http_version_t; } http_version_t;
// translates a http_version_t to a single readable number (e.g. 1.1 -> 11, 1.0 -> 10, etc) // translates a http_version_t to a single readable number (e.g. 1.1 -> 11, 1.0 -> 10, etc)
int httpVerNumber(http_version_t ver); int httpVerNumber(http_version_t ver);
typedef struct { typedef struct {
char *key; char *key;
char *value; char *value;
} http_field_t; } http_field_t;
// == HTTP REQUEST ============================================================ #define T http_field_t
#define VEC_SHORT_NAME field_vec
typedef struct { #define VEC_DISABLE_ERASE_WHEN
int method; #define VEC_NO_IMPLEMENTATION
http_version_t version; #include "vec.h"
http_field_t *fields;
int field_count; // == HTTP REQUEST ============================================================
char *uri;
char *body; typedef struct {
} http_request_t; int method;
http_version_t version;
http_request_t reqInit(void); field_vec_t fields;
void reqFree(http_request_t *ctx); char *uri;
char *body;
bool reqHasField(http_request_t *ctx, const char *key); } http_request_t;
void reqSetField(http_request_t *ctx, const char *key, const char *value); http_request_t reqInit(void);
void reqSetUri(http_request_t *ctx, const char *uri); void reqFree(http_request_t *ctx);
str_ostream_t reqPrepare(http_request_t *ctx); bool reqHasField(http_request_t *ctx, const char *key);
size_t reqString(http_request_t *ctx, char **str);
void reqSetField(http_request_t *ctx, const char *key, const char *value);
// == HTTP RESPONSE =========================================================== void reqSetUri(http_request_t *ctx, const char *uri);
typedef struct { str_ostream_t reqPrepare(http_request_t *ctx);
int status_code; str_t reqString(http_request_t *ctx);
http_field_t *fields;
int field_count; // == HTTP RESPONSE ===========================================================
http_version_t version;
char *body; typedef struct {
} http_response_t; int status_code;
field_vec_t fields;
http_response_t resInit(void); http_version_t version;
void resFree(http_response_t *ctx); char *body;
} http_response_t;
bool resHasField(http_response_t *ctx, const char *key);
const char *resGetField(http_response_t *ctx, const char *field); http_response_t resInit(void);
void resFree(http_response_t *ctx);
void resParse(http_response_t *ctx, const char *data);
void resParseFields(http_response_t *ctx, str_istream_t *in); bool resHasField(http_response_t *ctx, const char *key);
const char *resGetField(http_response_t *ctx, const char *field);
// == HTTP CLIENT =============================================================
void resParse(http_response_t *ctx, const char *data);
typedef struct { void resParseFields(http_response_t *ctx, str_istream_t *in);
char *host_name;
uint16_t port; // == HTTP CLIENT =============================================================
socket_t socket;
} http_client_t; typedef struct {
char *host_name;
http_client_t hcliInit(void); uint16_t port;
void hcliFree(http_client_t *ctx); socket_t socket;
} http_client_t;
void hcliSetHost(http_client_t *ctx, const char *hostname);
http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *request); http_client_t hcliInit(void);
void hcliFree(http_client_t *ctx);
// == HTTP ====================================================================
void hcliSetHost(http_client_t *ctx, const char *hostname);
http_response_t httpGet(const char *hostname, const char *uri); http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *request);
#ifdef __cplusplus // == HTTP ====================================================================
} // extern "C"
http_response_t httpGet(const char *hostname, const char *uri);
#ifdef __cplusplus
} // extern "C"
#endif #endif

132
os.c
View file

@ -1,67 +1,67 @@
#include "os.h" #include "os.h"
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <ctype.h> #include <ctype.h>
#ifdef _WIN32 #ifdef _WIN32
#define _BUFSZ 128 #define _BUFSZ 128
// modified from netbsd source http://cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/pkgtools/libnbcompat/files/getdelim.c?only_with_tag=MAIN // modified from netbsd source http://cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/pkgtools/libnbcompat/files/getdelim.c?only_with_tag=MAIN
ssize_t getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp) { ssize_t getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp) {
char *ptr, *eptr; char *ptr, *eptr;
if(*buf == NULL || *bufsz == 0) { if(*buf == NULL || *bufsz == 0) {
*bufsz = _BUFSZ; *bufsz = _BUFSZ;
if((*buf = malloc(*bufsz)) == NULL) { if((*buf = malloc(*bufsz)) == NULL) {
return -1; return -1;
} }
} }
ssize_t result = -1; ssize_t result = -1;
// usually fgetc locks every read, using windows-specific // usually fgetc locks every read, using windows-specific
// _lock_file and _unlock_file will be faster // _lock_file and _unlock_file will be faster
_lock_file(fp); _lock_file(fp);
for(ptr = *buf, eptr = *buf + *bufsz;;) { for(ptr = *buf, eptr = *buf + *bufsz;;) {
int c = _getc_nolock(fp); int c = _getc_nolock(fp);
if(c == -1) { if(c == -1) {
if(feof(fp)) { if(feof(fp)) {
ssize_t diff = (ssize_t)(ptr - *buf); ssize_t diff = (ssize_t)(ptr - *buf);
if(diff != 0) { if(diff != 0) {
*ptr = '\0'; *ptr = '\0';
result = diff; result = diff;
break; break;
} }
} }
break; break;
} }
*ptr++ = c; *ptr++ = (char)c;
if(c == delimiter) { if(c == delimiter) {
*ptr = '\0'; *ptr = '\0';
result = ptr - *buf; result = ptr - *buf;
break; break;
} }
if((ptr + 2) >= eptr) { if((ptr + 2) >= eptr) {
char *nbuf; char *nbuf;
size_t nbufsz = *bufsz * 2; size_t nbufsz = *bufsz * 2;
ssize_t d = ptr - *buf; ssize_t d = ptr - *buf;
if((nbuf = realloc(*buf, nbufsz)) == NULL) { if((nbuf = realloc(*buf, nbufsz)) == NULL) {
break; break;
} }
*buf = nbuf; *buf = nbuf;
*bufsz = nbufsz; *bufsz = nbufsz;
eptr = nbuf + nbufsz; eptr = nbuf + nbufsz;
ptr = nbuf + d; ptr = nbuf + d;
} }
} }
_unlock_file(fp); _unlock_file(fp);
return result; return result;
} }
// taken from netbsd source http://cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/pkgtools/libnbcompat/files/getline.c?only_with_tag=MAIN // taken from netbsd source http://cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/pkgtools/libnbcompat/files/getline.c?only_with_tag=MAIN
ssize_t getline(char **line_ptr, size_t *n, FILE *stream) { ssize_t getline(char **line_ptr, size_t *n, FILE *stream) {
return getdelim(line_ptr, n, '\n', stream); return getdelim(line_ptr, n, '\n', stream);
} }
#endif #endif

50
os.h
View file

@ -1,26 +1,26 @@
#pragma once #pragma once
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
#include <stddef.h> #include <stddef.h>
#include <string.h> #include <string.h>
#ifdef _WIN32 #ifdef _WIN32
#include <stdio.h> #include <stdio.h>
#include <BaseTsd.h> // SSIZE_T #include <BaseTsd.h> // SSIZE_T
typedef SSIZE_T ssize_t; typedef SSIZE_T ssize_t;
ssize_t getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp); ssize_t getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp);
ssize_t getline(char **line_ptr, size_t *n, FILE *stream); ssize_t getline(char **line_ptr, size_t *n, FILE *stream);
#define stricmp _stricmp #define stricmp _stricmp
#else #else
#define stricmp strcasecmp #define stricmp strcasecmp
#define _GNU_SOURCE #define _GNU_SOURCE
#include <stdio.h> #include <stdio.h>
#include <sys/types.h> // ssize_t #include <sys/types.h> // ssize_t
#endif #endif
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"
#endif #endif

View file

@ -1,5 +0,0 @@
#pragma once
#include <stddef.h> // size_t
#define slice_t(type) struct { type buf; size_t len; }

674
socket.c
View file

@ -1,338 +1,338 @@
#include "socket.h" #include "socket.h"
#include <stdio.h> #include <stdio.h>
#include "tracelog.h" #include "tracelog.h"
#ifndef NDEBUG #ifndef NDEBUG
// VERY MUCH NOT THREAD SAFE // VERY MUCH NOT THREAD SAFE
static int initialize_count = 0; static int initialize_count = 0;
#endif #endif
#if SOCK_WINDOWS #if SOCK_WINDOWS
#include <ws2tcpip.h> #include <ws2tcpip.h>
static bool _win_skInit(); static bool _win_skInit();
static bool _win_skCleanup(); static bool _win_skCleanup();
static int _win_skGetError(); static int _win_skGetError();
static const char *_win_skGetErrorString(); static const char *_win_skGetErrorString();
#define SOCK_CALL(fun) _win_##fun #define SOCK_CALL(fun) _win_##fun
#elif SOCK_POSIX #elif SOCK_POSIX
#include <netdb.h> #include <netdb.h>
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>
#include <string.h> // strerror #include <string.h> // strerror
#define INVALID_SOCKET (-1) #define INVALID_SOCKET (-1)
#define SOCKET_ERROR (-1) #define SOCKET_ERROR (-1)
static bool _posix_skInit(); static bool _posix_skInit();
static bool _posix_skCleanup(); static bool _posix_skCleanup();
static int _posix_skGetError(); static int _posix_skGetError();
static const char *_posix_skGetErrorString(); static const char *_posix_skGetErrorString();
#define SOCK_CALL(fun) _posix_##fun #define SOCK_CALL(fun) _posix_##fun
#endif #endif
bool skInit() { bool skInit() {
#ifndef NDEBUG #ifndef NDEBUG
++initialize_count; ++initialize_count;
#endif #endif
return SOCK_CALL(skInit()); return SOCK_CALL(skInit());
} }
bool skCleanup() { bool skCleanup() {
#ifndef NDEBUG #ifndef NDEBUG
--initialize_count; --initialize_count;
#endif #endif
return SOCK_CALL(skCleanup()); return SOCK_CALL(skCleanup());
} }
socket_t skOpen(skType type) { socket_t skOpen(skType type) {
int sock_type; int sock_type = 0;
switch(type) { switch(type) {
case SOCK_TCP: sock_type = SOCK_STREAM; break; case SOCK_TCP: sock_type = SOCK_STREAM; break;
case SOCK_UDP: sock_type = SOCK_DGRAM; break; case SOCK_UDP: sock_type = SOCK_DGRAM; break;
default: fatal("skType not recognized: %d", type); break; default: fatal("skType not recognized: %d", type); break;
} }
return skOpenPro(AF_INET, sock_type, 0); return skOpenPro(AF_INET, sock_type, 0);
} }
socket_t skOpenEx(const char *protocol) { socket_t skOpenEx(const char *protocol) {
#ifndef NDEBUG #ifndef NDEBUG
if(initialize_count <= 0) { if(initialize_count <= 0) {
fatal("skInit has not been called"); fatal("skInit has not been called");
} }
#endif #endif
struct protoent *proto = getprotobyname(protocol); struct protoent *proto = getprotobyname(protocol);
if(!proto) { if(!proto) {
return INVALID_SOCKET; return INVALID_SOCKET;
} }
return skOpenPro(AF_INET, SOCK_STREAM, proto->p_proto); return skOpenPro(AF_INET, SOCK_STREAM, proto->p_proto);
} }
socket_t skOpenPro(int af, int type, int protocol) { socket_t skOpenPro(int af, int type, int protocol) {
#ifndef NDEBUG #ifndef NDEBUG
if(initialize_count <= 0) { if(initialize_count <= 0) {
fatal("skInit has not been called"); fatal("skInit has not been called");
} }
#endif #endif
return socket(af, type, protocol); return socket(af, type, protocol);
} }
sk_addrin_t skAddrinInit(const char *ip, uint16_t port) { sk_addrin_t skAddrinInit(const char *ip, uint16_t port) {
sk_addrin_t addr; sk_addrin_t addr;
addr.sin_family = AF_INET; addr.sin_family = AF_INET;
addr.sin_port = htons(port); addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip); addr.sin_addr.s_addr = inet_addr(ip);
return addr; return addr;
} }
bool skClose(socket_t sock) { bool skClose(socket_t sock) {
#if SOCK_WINDOWS #if SOCK_WINDOWS
int error = closesocket(sock); int error = closesocket(sock);
#elif SOCK_POSIX #elif SOCK_POSIX
int error = close(sock); int error = close(sock);
#endif #endif
sock = INVALID_SOCKET; sock = INVALID_SOCKET;
return error != SOCKET_ERROR; return error != SOCKET_ERROR;
} }
bool skBind(socket_t sock, const char *ip, uint16_t port) { bool skBind(socket_t sock, const char *ip, uint16_t port) {
sk_addrin_t addr; sk_addrin_t addr;
addr.sin_family = AF_INET; addr.sin_family = AF_INET;
// TODO use inet_pton instead // TODO use inet_pton instead
addr.sin_addr.s_addr = inet_addr(ip); addr.sin_addr.s_addr = inet_addr(ip);
addr.sin_port = htons(port); addr.sin_port = htons(port);
return skBindPro(sock, (sk_addr_t *) &addr, sizeof(addr)); return skBindPro(sock, (sk_addr_t *) &addr, sizeof(addr));
} }
bool skBindPro(socket_t sock, const sk_addr_t *name, socket_len_t namelen) { bool skBindPro(socket_t sock, const sk_addr_t *name, socket_len_t namelen) {
return bind(sock, name, namelen) != SOCKET_ERROR; return bind(sock, name, namelen) != SOCKET_ERROR;
} }
bool skListen(socket_t sock) { bool skListen(socket_t sock) {
return skListenPro(sock, 1); return skListenPro(sock, 1);
} }
bool skListenPro(socket_t sock, int backlog) { bool skListenPro(socket_t sock, int backlog) {
return listen(sock, backlog) != SOCKET_ERROR; return listen(sock, backlog) != SOCKET_ERROR;
} }
socket_t skAccept(socket_t sock) { socket_t skAccept(socket_t sock) {
sk_addrin_t addr; sk_addrin_t addr;
socket_len_t addr_size = (socket_len_t)sizeof(addr); socket_len_t addr_size = (socket_len_t)sizeof(addr);
return skAcceptPro(sock, (sk_addr_t *) &addr, &addr_size); return skAcceptPro(sock, (sk_addr_t *) &addr, &addr_size);
} }
socket_t skAcceptPro(socket_t sock, sk_addr_t *addr, socket_len_t *addrlen) { socket_t skAcceptPro(socket_t sock, sk_addr_t *addr, socket_len_t *addrlen) {
return accept(sock, addr, addrlen); return accept(sock, addr, addrlen);
} }
bool skConnect(socket_t sock, const char *server, unsigned short server_port) { bool skConnect(socket_t sock, const char *server, unsigned short server_port) {
// TODO use getaddrinfo insetad // TODO use getaddrinfo insetad
struct hostent *host = gethostbyname(server); struct hostent *host = gethostbyname(server);
// if gethostbyname fails, inet_addr will also fail and return an easier to debug error // if gethostbyname fails, inet_addr will also fail and return an easier to debug error
const char *address = server; const char *address = server;
if(host) { if(host) {
address = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]); address = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]);
} }
sk_addrin_t addr; sk_addrin_t addr;
addr.sin_family = AF_INET; addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(address); addr.sin_addr.s_addr = inet_addr(address);
addr.sin_port = htons(server_port); addr.sin_port = htons(server_port);
return skConnectPro(sock, (sk_addr_t *) &addr, sizeof(addr)); return skConnectPro(sock, (sk_addr_t *) &addr, sizeof(addr));
} }
bool skConnectPro(socket_t sock, const sk_addr_t *name, socket_len_t namelen) { bool skConnectPro(socket_t sock, const sk_addr_t *name, socket_len_t namelen) {
return connect(sock, name, namelen) != SOCKET_ERROR; return connect(sock, name, namelen) != SOCKET_ERROR;
} }
int skSend(socket_t sock, const char *buf, int len) { int skSend(socket_t sock, const char *buf, int len) {
return skSendPro(sock, buf, len, 0); return skSendPro(sock, buf, len, 0);
} }
int skSendPro(socket_t sock, const char *buf, int len, int flags) { int skSendPro(socket_t sock, const char *buf, int len, int flags) {
return send(sock, buf, len, flags); return send(sock, buf, len, flags);
} }
int skSendTo(socket_t sock, const char *buf, int len, const sk_addrin_t *to) { int skSendTo(socket_t sock, const char *buf, int len, const sk_addrin_t *to) {
return skSendToPro(sock, buf, len, 0, (sk_addr_t*) to, sizeof(sk_addrin_t)); return skSendToPro(sock, buf, len, 0, (sk_addr_t*) to, sizeof(sk_addrin_t));
} }
int skSendToPro(socket_t sock, const char *buf, int len, int flags, const sk_addr_t *to, int tolen) { int skSendToPro(socket_t sock, const char *buf, int len, int flags, const sk_addr_t *to, int tolen) {
return sendto(sock, buf, len, flags, to, tolen); return sendto(sock, buf, len, flags, to, tolen);
} }
int skReceive(socket_t sock, char *buf, int len) { int skReceive(socket_t sock, char *buf, int len) {
return skReceivePro(sock, buf, len, 0); return skReceivePro(sock, buf, len, 0);
} }
int skReceivePro(socket_t sock, char *buf, int len, int flags) { int skReceivePro(socket_t sock, char *buf, int len, int flags) {
return recv(sock, buf, len, flags); return recv(sock, buf, len, flags);
} }
int skReceiveFrom(socket_t sock, char *buf, int len, sk_addrin_t *from) { 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); 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); return recvfrom(sock, buf, len, flags, from, fromlen);
} }
bool skIsValid(socket_t sock) { bool skIsValid(socket_t sock) {
return sock != INVALID_SOCKET; return sock != INVALID_SOCKET;
} }
int skGetError() { int skGetError() {
return SOCK_CALL(skGetError()); return SOCK_CALL(skGetError());
} }
const char *skGetErrorString() { const char *skGetErrorString() {
return SOCK_CALL(skGetErrorString()); return SOCK_CALL(skGetErrorString());
} }
#ifdef SOCK_WINDOWS #ifdef SOCK_WINDOWS
static bool _win_skInit() { static bool _win_skInit() {
WSADATA w; WSADATA w;
int error = WSAStartup(0x0202, &w); int error = WSAStartup(0x0202, &w);
return error == 0; return error == 0;
} }
static bool _win_skCleanup() { static bool _win_skCleanup() {
return WSACleanup() == 0; return WSACleanup() == 0;
} }
static int _win_skGetError() { static int _win_skGetError() {
return WSAGetLastError(); return WSAGetLastError();
} }
static const char *_win_skGetErrorString() { static const char *_win_skGetErrorString() {
switch(_win_skGetError()) { switch(_win_skGetError()) {
case WSA_INVALID_HANDLE: return "Specified event object handle is invalid."; case WSA_INVALID_HANDLE: return "Specified event object handle is invalid.";
case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available."; case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available.";
case WSA_INVALID_PARAMETER: return "One or more parameters are invalid."; case WSA_INVALID_PARAMETER: return "One or more parameters are invalid.";
case WSA_OPERATION_ABORTED: return "Overlapped operation aborted."; case WSA_OPERATION_ABORTED: return "Overlapped operation aborted.";
case WSA_IO_INCOMPLETE: return "Overlapped I/O event object not in signaled state."; case WSA_IO_INCOMPLETE: return "Overlapped I/O event object not in signaled state.";
case WSA_IO_PENDING: return "Overlapped operations will complete later."; case WSA_IO_PENDING: return "Overlapped operations will complete later.";
case WSAEINTR: return "Interrupted function call."; case WSAEINTR: return "Interrupted function call.";
case WSAEBADF: return "File handle is not valid."; case WSAEBADF: return "File handle is not valid.";
case WSAEACCES: return "Permission denied."; case WSAEACCES: return "Permission denied.";
case WSAEFAULT: return "Bad address."; case WSAEFAULT: return "Bad address.";
case WSAEINVAL: return "Invalid argument."; case WSAEINVAL: return "Invalid argument.";
case WSAEMFILE: return "Too many open files."; case WSAEMFILE: return "Too many open files.";
case WSAEWOULDBLOCK: return "Resource temporarily unavailable."; case WSAEWOULDBLOCK: return "Resource temporarily unavailable.";
case WSAEINPROGRESS: return "Operation now in progress."; case WSAEINPROGRESS: return "Operation now in progress.";
case WSAEALREADY: return "Operation already in progress."; case WSAEALREADY: return "Operation already in progress.";
case WSAENOTSOCK: return "Socket operation on nonsocket."; case WSAENOTSOCK: return "Socket operation on nonsocket.";
case WSAEDESTADDRREQ: return "Destination address required."; case WSAEDESTADDRREQ: return "Destination address required.";
case WSAEMSGSIZE: return "Message too long."; case WSAEMSGSIZE: return "Message too long.";
case WSAEPROTOTYPE: return "Protocol wrong type for socket."; case WSAEPROTOTYPE: return "Protocol wrong type for socket.";
case WSAENOPROTOOPT: return "Bad protocol option."; case WSAENOPROTOOPT: return "Bad protocol option.";
case WSAEPROTONOSUPPORT: return "Protocol not supported."; case WSAEPROTONOSUPPORT: return "Protocol not supported.";
case WSAESOCKTNOSUPPORT: return "Socket type not supported."; case WSAESOCKTNOSUPPORT: return "Socket type not supported.";
case WSAEOPNOTSUPP: return "Operation not supported."; case WSAEOPNOTSUPP: return "Operation not supported.";
case WSAEPFNOSUPPORT: return "Protocol family not supported."; case WSAEPFNOSUPPORT: return "Protocol family not supported.";
case WSAEAFNOSUPPORT: return "Address family not supported by protocol family."; case WSAEAFNOSUPPORT: return "Address family not supported by protocol family.";
case WSAEADDRINUSE: return "Address already in use."; case WSAEADDRINUSE: return "Address already in use.";
case WSAEADDRNOTAVAIL: return "Cannot assign requested address."; case WSAEADDRNOTAVAIL: return "Cannot assign requested address.";
case WSAENETDOWN: return "Network is down."; case WSAENETDOWN: return "Network is down.";
case WSAENETUNREACH: return "Network is unreachable."; case WSAENETUNREACH: return "Network is unreachable.";
case WSAENETRESET: return "Network dropped connection on reset."; case WSAENETRESET: return "Network dropped connection on reset.";
case WSAECONNABORTED: return "Software caused connection abort."; case WSAECONNABORTED: return "Software caused connection abort.";
case WSAECONNRESET: return "Connection reset by peer."; case WSAECONNRESET: return "Connection reset by peer.";
case WSAENOBUFS: return "No buffer space available."; case WSAENOBUFS: return "No buffer space available.";
case WSAEISCONN: return "Socket is already connected."; case WSAEISCONN: return "Socket is already connected.";
case WSAENOTCONN: return "Socket is not connected."; case WSAENOTCONN: return "Socket is not connected.";
case WSAESHUTDOWN: return "Cannot send after socket shutdown."; case WSAESHUTDOWN: return "Cannot send after socket shutdown.";
case WSAETOOMANYREFS: return "Too many references."; case WSAETOOMANYREFS: return "Too many references.";
case WSAETIMEDOUT: return "Connection timed out."; case WSAETIMEDOUT: return "Connection timed out.";
case WSAECONNREFUSED: return "Connection refused."; case WSAECONNREFUSED: return "Connection refused.";
case WSAELOOP: return "Cannot translate name."; case WSAELOOP: return "Cannot translate name.";
case WSAENAMETOOLONG: return "Name too long."; case WSAENAMETOOLONG: return "Name too long.";
case WSAEHOSTDOWN: return "Host is down."; case WSAEHOSTDOWN: return "Host is down.";
case WSAEHOSTUNREACH: return "No route to host."; case WSAEHOSTUNREACH: return "No route to host.";
case WSAENOTEMPTY: return "Directory not empty."; case WSAENOTEMPTY: return "Directory not empty.";
case WSAEPROCLIM: return "Too many processes."; case WSAEPROCLIM: return "Too many processes.";
case WSAEUSERS: return "User quota exceeded."; case WSAEUSERS: return "User quota exceeded.";
case WSAEDQUOT: return "Disk quota exceeded."; case WSAEDQUOT: return "Disk quota exceeded.";
case WSAESTALE: return "Stale file handle reference."; case WSAESTALE: return "Stale file handle reference.";
case WSAEREMOTE: return "Item is remote."; case WSAEREMOTE: return "Item is remote.";
case WSASYSNOTREADY: return "Network subsystem is unavailable."; case WSASYSNOTREADY: return "Network subsystem is unavailable.";
case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range."; case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range.";
case WSANOTINITIALISED: return "Successful WSAStartup not yet performed."; case WSANOTINITIALISED: return "Successful WSAStartup not yet performed.";
case WSAEDISCON: return "Graceful shutdown in progress."; case WSAEDISCON: return "Graceful shutdown in progress.";
case WSAENOMORE: return "No more results."; case WSAENOMORE: return "No more results.";
case WSAECANCELLED: return "Call has been canceled."; case WSAECANCELLED: return "Call has been canceled.";
case WSAEINVALIDPROCTABLE: return "Procedure call table is invalid."; case WSAEINVALIDPROCTABLE: return "Procedure call table is invalid.";
case WSAEINVALIDPROVIDER: return "Service provider is invalid."; case WSAEINVALIDPROVIDER: return "Service provider is invalid.";
case WSAEPROVIDERFAILEDINIT: return "Service provider failed to initialize."; case WSAEPROVIDERFAILEDINIT: return "Service provider failed to initialize.";
case WSASYSCALLFAILURE: return "System call failure."; case WSASYSCALLFAILURE: return "System call failure.";
case WSASERVICE_NOT_FOUND: return "Service not found."; case WSASERVICE_NOT_FOUND: return "Service not found.";
case WSATYPE_NOT_FOUND: return "Class type not found."; case WSATYPE_NOT_FOUND: return "Class type not found.";
case WSA_E_NO_MORE: return "No more results."; case WSA_E_NO_MORE: return "No more results.";
case WSA_E_CANCELLED: return "Call was canceled."; case WSA_E_CANCELLED: return "Call was canceled.";
case WSAEREFUSED: return "Database query was refused."; case WSAEREFUSED: return "Database query was refused.";
case WSAHOST_NOT_FOUND: return "Host not found."; case WSAHOST_NOT_FOUND: return "Host not found.";
case WSATRY_AGAIN: return "Nonauthoritative host not found."; case WSATRY_AGAIN: return "Nonauthoritative host not found.";
case WSANO_RECOVERY: return "This is a nonrecoverable error."; case WSANO_RECOVERY: return "This is a nonrecoverable error.";
case WSANO_DATA: return "Valid name, no data record of requested type."; case WSANO_DATA: return "Valid name, no data record of requested type.";
case WSA_QOS_RECEIVERS: return "QoS receivers."; case WSA_QOS_RECEIVERS: return "QoS receivers.";
case WSA_QOS_SENDERS: return "QoS senders."; case WSA_QOS_SENDERS: return "QoS senders.";
case WSA_QOS_NO_SENDERS: return "No QoS senders."; case WSA_QOS_NO_SENDERS: return "No QoS senders.";
case WSA_QOS_NO_RECEIVERS: return "QoS no receivers."; case WSA_QOS_NO_RECEIVERS: return "QoS no receivers.";
case WSA_QOS_REQUEST_CONFIRMED: return "QoS request confirmed."; case WSA_QOS_REQUEST_CONFIRMED: return "QoS request confirmed.";
case WSA_QOS_ADMISSION_FAILURE: return "QoS admission error."; case WSA_QOS_ADMISSION_FAILURE: return "QoS admission error.";
case WSA_QOS_POLICY_FAILURE: return "QoS policy failure."; case WSA_QOS_POLICY_FAILURE: return "QoS policy failure.";
case WSA_QOS_BAD_STYLE: return "QoS bad style."; case WSA_QOS_BAD_STYLE: return "QoS bad style.";
case WSA_QOS_BAD_OBJECT: return "QoS bad object."; case WSA_QOS_BAD_OBJECT: return "QoS bad object.";
case WSA_QOS_TRAFFIC_CTRL_ERROR: return "QoS traffic control error."; case WSA_QOS_TRAFFIC_CTRL_ERROR: return "QoS traffic control error.";
case WSA_QOS_GENERIC_ERROR: return "QoS generic error."; case WSA_QOS_GENERIC_ERROR: return "QoS generic error.";
case WSA_QOS_ESERVICETYPE: return "QoS service type error."; case WSA_QOS_ESERVICETYPE: return "QoS service type error.";
case WSA_QOS_EFLOWSPEC: return "QoS flowspec error."; case WSA_QOS_EFLOWSPEC: return "QoS flowspec error.";
case WSA_QOS_EPROVSPECBUF: return "Invalid QoS provider buffer."; case WSA_QOS_EPROVSPECBUF: return "Invalid QoS provider buffer.";
case WSA_QOS_EFILTERSTYLE: return "Invalid QoS filter style."; case WSA_QOS_EFILTERSTYLE: return "Invalid QoS filter style.";
case WSA_QOS_EFILTERTYPE: return "Invalid QoS filter type."; case WSA_QOS_EFILTERTYPE: return "Invalid QoS filter type.";
case WSA_QOS_EFILTERCOUNT: return "Incorrect QoS filter count."; case WSA_QOS_EFILTERCOUNT: return "Incorrect QoS filter count.";
case WSA_QOS_EOBJLENGTH: return "Invalid QoS object length."; case WSA_QOS_EOBJLENGTH: return "Invalid QoS object length.";
case WSA_QOS_EFLOWCOUNT: return "Incorrect QoS flow count."; case WSA_QOS_EFLOWCOUNT: return "Incorrect QoS flow count.";
case WSA_QOS_EUNKOWNPSOBJ: return "Unrecognized QoS object."; case WSA_QOS_EUNKOWNPSOBJ: return "Unrecognized QoS object.";
case WSA_QOS_EPOLICYOBJ: return "Invalid QoS policy object."; case WSA_QOS_EPOLICYOBJ: return "Invalid QoS policy object.";
case WSA_QOS_EFLOWDESC: return "Invalid QoS flow descriptor."; case WSA_QOS_EFLOWDESC: return "Invalid QoS flow descriptor.";
case WSA_QOS_EPSFLOWSPEC: return "Invalid QoS provider-specific flowspec."; case WSA_QOS_EPSFLOWSPEC: return "Invalid QoS provider-specific flowspec.";
case WSA_QOS_EPSFILTERSPEC: return "Invalid QoS provider-specific filterspec."; case WSA_QOS_EPSFILTERSPEC: return "Invalid QoS provider-specific filterspec.";
case WSA_QOS_ESDMODEOBJ: return "Invalid QoS shape discard mode object."; case WSA_QOS_ESDMODEOBJ: return "Invalid QoS shape discard mode object.";
case WSA_QOS_ESHAPERATEOBJ: return "Invalid QoS shaping rate object."; case WSA_QOS_ESHAPERATEOBJ: return "Invalid QoS shaping rate object.";
case WSA_QOS_RESERVED_PETYPE: return "Reserved policy QoS element type."; case WSA_QOS_RESERVED_PETYPE: return "Reserved policy QoS element type.";
} }
return "(nothing)"; return "(nothing)";
} }
#else #else
static bool _posix_skInit() { static bool _posix_skInit() {
return true; return true;
} }
static bool _posix_skCleanup() { static bool _posix_skCleanup() {
return true; return true;
} }
static int _posix_skGetError() { static int _posix_skGetError() {
return errno; return errno;
} }
static const char *_posix_skGetErrorString() { static const char *_posix_skGetErrorString() {
return strerror(errno); return strerror(errno);
} }
#endif #endif

228
socket.h
View file

@ -1,115 +1,115 @@
#pragma once #pragma once
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#ifdef _WIN32 #ifdef _WIN32
#define SOCK_WINDOWS 1 #define SOCK_WINDOWS 1
#else #else
#define SOCK_POSIX 1 #define SOCK_POSIX 1
#endif #endif
#if SOCK_WINDOWS #if SOCK_WINDOWS
#pragma warning(disable:4996) // _WINSOCK_DEPRECATED_NO_WARNINGS #pragma warning(disable:4996) // _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h> #include <winsock2.h>
typedef SOCKET socket_t; typedef SOCKET socket_t;
typedef int socket_len_t; typedef int socket_len_t;
#elif SOCK_POSIX #elif SOCK_POSIX
#include <sys/socket.h> #include <sys/socket.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <arpa/inet.h> #include <arpa/inet.h>
typedef int socket_t; typedef int socket_t;
typedef uint32_t socket_len_t; typedef uint32_t socket_len_t;
#define INVALID_SOCKET (-1) #define INVALID_SOCKET (-1)
#define SOCKET_ERROR (-1) #define SOCKET_ERROR (-1)
#endif #endif
typedef struct sockaddr sk_addr_t; typedef struct sockaddr sk_addr_t;
typedef struct sockaddr_in sk_addrin_t; typedef struct sockaddr_in sk_addrin_t;
typedef enum { typedef enum {
SOCK_TCP, SOCK_TCP,
SOCK_UDP, SOCK_UDP,
} skType; } skType;
// == RAW SOCKETS ========================================== // == RAW SOCKETS ==========================================
// Initialize sockets, returns true on success // Initialize sockets, returns true on success
bool skInit(void); bool skInit(void);
// Terminates sockets, returns true on success // Terminates sockets, returns true on success
bool skCleanup(void); bool skCleanup(void);
// Opens a socket, check socket_t with skValid // Opens a socket, check socket_t with skValid
socket_t skOpen(skType type); socket_t skOpen(skType type);
// Opens a socket using 'protocol', options are // Opens a socket using 'protocol', options are
// ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp // ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp
// check socket_t with skValid // check socket_t with skValid
socket_t skOpenEx(const char *protocol); socket_t skOpenEx(const char *protocol);
// Opens a socket, check socket_t with skValid // Opens a socket, check socket_t with skValid
socket_t skOpenPro(int af, int type, int protocol); socket_t skOpenPro(int af, int type, int protocol);
// Fill out a sk_addrin_t structure with "ip" and "port" // Fill out a sk_addrin_t structure with "ip" and "port"
sk_addrin_t skAddrinInit(const char *ip, uint16_t port); sk_addrin_t skAddrinInit(const char *ip, uint16_t port);
// Closes a socket, returns true on success // Closes a socket, returns true on success
bool skClose(socket_t sock); bool skClose(socket_t sock);
// Associate a local address with a socket // Associate a local address with a socket
bool skBind(socket_t sock, const char *ip, uint16_t port); bool skBind(socket_t sock, const char *ip, uint16_t port);
// Associate a local address with a socket // Associate a local address with a socket
bool skBindPro(socket_t sock, const sk_addr_t *name, socket_len_t namelen); bool skBindPro(socket_t sock, const sk_addr_t *name, socket_len_t namelen);
// Place a socket in a state in which it is listening for an incoming connection // Place a socket in a state in which it is listening for an incoming connection
bool skListen(socket_t sock); bool skListen(socket_t sock);
// Place a socket in a state in which it is listening for an incoming connection // Place a socket in a state in which it is listening for an incoming connection
bool skListenPro(socket_t sock, int backlog); bool skListenPro(socket_t sock, int backlog);
// Permits an incoming connection attempt on a socket // Permits an incoming connection attempt on a socket
socket_t skAccept(socket_t sock); socket_t skAccept(socket_t sock);
// Permits an incoming connection attempt on a socket // Permits an incoming connection attempt on a socket
socket_t skAcceptPro(socket_t sock, sk_addr_t *addr, socket_len_t *addrlen); socket_t skAcceptPro(socket_t sock, sk_addr_t *addr, socket_len_t *addrlen);
// Connects to a server (e.g. "127.0.0.1" or "google.com") with a port(e.g. 1234), returns true on success // Connects to a server (e.g. "127.0.0.1" or "google.com") with a port(e.g. 1234), returns true on success
bool skConnect(socket_t sock, const char *server, unsigned short server_port); bool skConnect(socket_t sock, const char *server, unsigned short server_port);
// Connects to a server, returns true on success // Connects to a server, returns true on success
bool skConnectPro(socket_t sock, const sk_addr_t *name, socket_len_t namelen); bool skConnectPro(socket_t sock, const sk_addr_t *name, socket_len_t namelen);
// Sends data on a socket, returns true on success // Sends data on a socket, returns true on success
int skSend(socket_t sock, const char *buf, int len); int skSend(socket_t sock, const char *buf, int len);
// Sends data on a socket, returns true on success // Sends data on a socket, returns true on success
int skSendPro(socket_t sock, const char *buf, int len, int flags); int skSendPro(socket_t sock, const char *buf, int len, int flags);
// Sends data to a specific destination // Sends data to a specific destination
int skSendTo(socket_t sock, const char *buf, int len, const sk_addrin_t *to); int skSendTo(socket_t sock, const char *buf, int len, const sk_addrin_t *to);
// Sends data to a specific destination // Sends data to a specific destination
int skSendToPro(socket_t sock, const char *buf, int len, int flags, const sk_addr_t *to, int tolen); int skSendToPro(socket_t sock, const char *buf, int len, int flags, const sk_addr_t *to, int tolen);
// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error // Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error
int skReceive(socket_t sock, char *buf, int len); int skReceive(socket_t sock, char *buf, int len);
// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error // Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error
int skReceivePro(socket_t sock, char *buf, int len, int flags); int skReceivePro(socket_t sock, char *buf, int len, int flags);
// Receives a datagram and stores the source address. // Receives a datagram and stores the source address.
int skReceiveFrom(socket_t sock, char *buf, int len, sk_addrin_t *from); int skReceiveFrom(socket_t sock, char *buf, int len, sk_addrin_t *from);
// Receives a datagram and stores the source address. // 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 // Checks that a opened socket is valid, returns true on success
bool skIsValid(socket_t sock); bool skIsValid(socket_t sock);
// Returns latest socket error, returns 0 if there is no error // Returns latest socket error, returns 0 if there is no error
int skGetError(void); int skGetError(void);
// Returns a human-readable string from a skGetError // Returns a human-readable string from a skGetError
const char *skGetErrorString(void); const char *skGetErrorString(void);
// == UDP SOCKETS ========================================== // == UDP SOCKETS ==========================================
typedef socket_t udpsock_t; typedef socket_t udpsock_t;
#ifdef __cplusplus #ifdef __cplusplus
} // extern "C" } // extern "C"
#endif #endif

885
str.c
View file

@ -1,267 +1,620 @@
#include "str.h" #include "str.h"
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <ctype.h> #include <limits.h>
#include <ctype.h>
str_t strInit(void) { #include <assert.h>
return (str_t) { #include <stdio.h>
.buf = NULL,
.len = 0 #include "tracelog.h"
};
} #ifdef _WIN32
#define VC_EXTRALEAN
str_t strInitStr(const char *cstr) { #include <windows.h>
return strInitBuf(cstr, strlen(cstr)); #else
} #include <iconv.h>
#endif
str_t strInitView(strview_t view) {
return strInitBuf(view.buf, view.len); #ifndef min
} #define min(a, b) ((a) < (b) ? (a) : (b))
#endif
str_t strInitBuf(const char *buf, size_t len) {
str_t str; // == STR_T ========================================================
str.len = len;
str.buf = malloc(len + 1); str_t strInit(void) {
memcpy(str.buf, buf, len); return (str_t) {
str.buf[len] = '\0'; .buf = NULL,
return str; .len = 0
} };
}
void strFree(str_t *ctx) {
free(ctx->buf); str_t strInitStr(const char *cstr) {
ctx->buf = NULL; return strInitBuf(cstr, strlen(cstr));
ctx->len = 0; }
}
str_t strInitView(strview_t view) {
str_t strMove(str_t *ctx) { return strInitBuf(view.buf, view.len);
str_t str = strInitBuf(ctx->buf, ctx->len); }
ctx->buf = NULL;
ctx->len = 0; str_t strInitBuf(const char *buf, size_t len) {
return str; str_t str;
} str.len = len;
str.buf = malloc(len + 1);
str_t strDup(str_t ctx) { memcpy(str.buf, buf, len);
return strInitBuf(ctx.buf, ctx.len); str.buf[len] = '\0';
} return str;
}
strview_t strGetView(str_t *ctx) {
return (strview_t) { void strFree(str_t *ctx) {
.buf = ctx->buf, free(ctx->buf);
.len = ctx->len ctx->buf = NULL;
}; ctx->len = 0;
} }
char *strBegin(str_t *ctx) { str_t strFromWCHAR(const wchar_t *src, size_t len) {
return ctx->buf; if(len == 0) len = wcslen(src);
}
#ifdef _WIN32
char *strEnd(str_t *ctx) { // TODO CP_ACP should be CP_UTF8 but if i put CP_UTF8 it doesn't work??
return ctx->buf ? ctx->buf + ctx->len : NULL; int result_len = WideCharToMultiByte(
} CP_ACP, 0,
src, (int)len,
char strBack(str_t *ctx) { NULL, 0,
return ctx->buf ? ctx->buf[ctx->len - 1] : '\0'; NULL, NULL
} );
char *buf = malloc(result_len + 1);
bool strIsEmpty(str_t *ctx) { if(buf) {
return ctx->len == 0; WideCharToMultiByte(
} CP_ACP, 0,
src, (int)len,
void strAppend(str_t *ctx, const char *str) { buf, result_len,
strAppendBuf(ctx, str, strlen(str)); NULL, NULL
} );
buf[result_len] = '\0';
void strAppendStr(str_t *ctx, str_t str) { }
strAppendBuf(ctx, str.buf, str.len); return (str_t) {
} .buf = buf,
.len = result_len
void strAppendView(str_t *ctx, strview_t view) { };
strAppendBuf(ctx, view.buf, view.len); #else
} size_t actual_len = len * sizeof(wchar_t);
void strAppendBuf(str_t *ctx, const char *buf, size_t len) { size_t dest_len = len * 6;
size_t oldlen = ctx->len; char *dest = malloc(dest_len);
ctx->len += len;
ctx->buf = realloc(ctx->buf, ctx->len + 1); iconv_t cd = iconv_open("UTF-8", "WCHAR_T");
memcpy(ctx->buf + oldlen, buf, len); assert(cd);
ctx->buf[ctx->len] = '\0';
} size_t dest_left = dest_len;
char *dest_temp = dest;
void strAppendChars(str_t *ctx, char c, size_t count) { char *src_temp = (char*)src;
size_t oldlen = ctx->len; size_t lost = iconv(cd, &src_temp, &actual_len, &dest_temp, &dest_left);
ctx->len += count; assert(lost != ((size_t)-1));
ctx->buf = realloc(ctx->buf, ctx->len + 1);
memset(ctx->buf + oldlen, c, count); dest_len -= dest_left;
ctx->buf[ctx->len] = '\0'; dest = realloc(dest, dest_len + 1);
} dest[dest_len] = '\0';
void strPush(str_t *ctx, char c) { iconv_close(cd);
strAppendChars(ctx, c, 1);
} return (str_t){
.buf = dest,
char strPop(str_t *ctx) { .len = dest_len
char c = strBack(ctx); };
ctx->buf = realloc(ctx->buf, ctx->len); #endif
ctx->len -= 1; }
ctx->buf[ctx->len] = '\0';
return c; wchar_t *strToWCHAR(str_t ctx) {
} #ifdef _WIN32
UINT codepage = CP_ACP;
void strSwap(str_t *ctx, str_t *other) { int len = MultiByteToWideChar(
char *buf = other->buf; codepage, 0,
size_t len = other->len; ctx.buf, (int)ctx.len,
other->buf = ctx->buf; NULL, 0
other->len = ctx->len; );
ctx->buf = buf; wchar_t *str = malloc(sizeof(wchar_t) * (len + 1));
ctx->len = len; if(!str) return NULL;
} len = MultiByteToWideChar(
codepage, 0,
#include "tracelog.h" ctx.buf, (int)ctx.len,
str, len
str_t strSubstr(str_t *ctx, size_t pos, size_t len) { );
if(strIsEmpty(ctx)) return strInit(); str[len] = '\0';
if(len == SIZE_MAX || (pos + len) > ctx->len) len = ctx->len - pos; return str;
return strInitBuf(ctx->buf + pos, len); #else
} size_t dest_len = ctx.len * sizeof(wchar_t);
char *dest = malloc(dest_len);
strview_t strSubview(str_t *ctx, size_t pos, size_t len) {
if(strIsEmpty(ctx)) return strvInit(NULL); iconv_t cd = iconv_open("WCHAR_T", "UTF-8");
if(len == SIZE_MAX || (pos + len) > ctx->len) len = ctx->len - pos; assert(cd);
return (strview_t) {
.buf = ctx->buf + pos, size_t dest_left = dest_len;
.len = 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));
void strLower(str_t *ctx) {
for(size_t i = 0; i < ctx->len; ++i) { dest_len -= dest_left;
ctx->buf[i] = tolower(ctx->buf[i]); dest = realloc(dest, dest_len + 1);
} dest[dest_len] = '\0';
}
iconv_close(cd);
str_t strToLower(str_t ctx) {
str_t str = strDup(ctx); return (wchar_t *)dest;
strLower(&str); #endif
return str; }
}
str_t strMove(str_t *ctx) {
#ifdef STR_TESTING str_t str = strInitBuf(ctx->buf, ctx->len);
#include <stdio.h> ctx->buf = NULL;
#include "tracelog.h" ctx->len = 0;
return str;
void strTest(void) { }
str_t s;
debug("== testing init ================="); str_t strDup(str_t ctx) {
{ return strInitBuf(ctx.buf, ctx.len);
s = strInit(); }
printf("%s %zu\n", s.buf, s.len);
strFree(&s); strview_t strGetView(str_t *ctx) {
s = strInitStr("hello world"); return (strview_t) {
printf("\"%s\" %zu\n", s.buf, s.len); .buf = ctx->buf,
strFree(&s); .len = ctx->len
uint8_t buf[] = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' }; };
s = strInitBuf((char *)buf, sizeof(buf)); }
printf("\"%s\" %zu\n", s.buf, s.len);
strFree(&s); char *strBegin(str_t *ctx) {
} return ctx->buf;
debug("== testing view ================="); }
{
s = strInitStr("hello world"); char *strEnd(str_t *ctx) {
strview_t view = strGetView(&s); return ctx->buf ? ctx->buf + ctx->len : NULL;
printf("\"%.*s\" %zu\n", (int)view.len, view.buf, view.len); }
strFree(&s);
} char strBack(str_t *ctx) {
debug("== testing begin/end ============"); return ctx->buf ? ctx->buf[ctx->len - 1] : '\0';
{ }
s = strInitStr("hello world");
char *beg = strBegin(&s); bool strIsEmpty(str_t *ctx) {
char *end = strEnd(&s); return ctx->len == 0;
printf("[ "); }
for(; beg < end; ++beg) {
printf("%c ", *beg); void strAppend(str_t *ctx, const char *str) {
} strAppendBuf(ctx, str, strlen(str));
printf("]\n"); }
strFree(&s);
} void strAppendStr(str_t *ctx, str_t str) {
debug("== testing back/isempty ========="); strAppendBuf(ctx, str.buf, str.len);
{ }
s = strInitStr("hello world");
printf("[ "); void strAppendView(str_t *ctx, strview_t view) {
while(!strIsEmpty(&s)) { strAppendBuf(ctx, view.buf, view.len);
printf("%c ", strBack(&s)); }
strPop(&s);
} void strAppendBuf(str_t *ctx, const char *buf, size_t len) {
printf("]\n"); size_t oldlen = ctx->len;
strFree(&s); ctx->len += len;
} ctx->buf = realloc(ctx->buf, ctx->len + 1);
debug("== testing append ==============="); memcpy(ctx->buf + oldlen, buf, len);
{ ctx->buf[ctx->len] = '\0';
s = strInitStr("hello "); }
printf("\"%s\" %zu\n", s.buf, s.len);
strAppend(&s, "world"); void strAppendChars(str_t *ctx, char c, size_t count) {
printf("\"%s\" %zu\n", s.buf, s.len); size_t oldlen = ctx->len;
strAppendView(&s, strvInit(", how is it ")); ctx->len += count;
printf("\"%s\" %zu\n", s.buf, s.len); ctx->buf = realloc(ctx->buf, ctx->len + 1);
uint8_t buf[] = { 'g', 'o', 'i', 'n', 'g' }; memset(ctx->buf + oldlen, c, count);
strAppendBuf(&s, (char*)buf, sizeof(buf)); ctx->buf[ctx->len] = '\0';
printf("\"%s\" %zu\n", s.buf, s.len); }
strAppendChars(&s, '?', 2);
printf("\"%s\" %zu\n", s.buf, s.len); void strPush(str_t *ctx, char c) {
strFree(&s); strAppendChars(ctx, c, 1);
} }
debug("== testing push/pop =============");
{ char strPop(str_t *ctx) {
s = strInit(); char c = strBack(ctx);
str_t s2 = strInitStr("hello world"); ctx->buf = realloc(ctx->buf, ctx->len);
ctx->len -= 1;
printf("%-14s %-14s\n", "s", "s2"); ctx->buf[ctx->len] = '\0';
printf("----------------------------\n"); return c;
while(!strIsEmpty(&s2)) { }
printf("%-14s %-14s\n", s.buf, s2.buf);
strPush(&s, strPop(&s2)); void strSwap(str_t *ctx, str_t *other) {
} char *buf = other->buf;
printf("%-14s %-14s\n", s.buf, s2.buf); size_t len = other->len;
other->buf = ctx->buf;
strFree(&s); other->len = ctx->len;
strFree(&s2); ctx->buf = buf;
} ctx->len = len;
debug("== testing swap ================="); }
{
s = strInitStr("hello"); void strReplace(str_t *ctx, char from, char to) {
str_t s2 = strInitStr("world"); for(size_t i = 0; i < ctx->len; ++i) {
printf("%-8s %-8s\n", "s", "s2"); if(ctx->buf[i] == from) {
printf("----------------\n"); ctx->buf[i] = to;
printf("%-8s %-8s\n", s.buf, s2.buf); }
strSwap(&s, &s2); }
printf("%-8s %-8s\n", s.buf, s2.buf); }
strFree(&s); str_t strSubstr(str_t *ctx, size_t pos, size_t len) {
strFree(&s2); if(strIsEmpty(ctx)) return strInit();
} if(len == SIZE_MAX || (pos + len) > ctx->len) len = ctx->len - pos;
debug("== testing substr ==============="); return strInitBuf(ctx->buf + pos, len);
{ }
s = strInitStr("hello world");
printf("s: %s\n", s.buf); strview_t strSubview(str_t *ctx, size_t pos, size_t len) {
if(strIsEmpty(ctx)) return strvInit(NULL);
printf("-- string\n"); if(len == SIZE_MAX || (pos + len) > ctx->len) len = ctx->len - pos;
str_t s2 = strSubstr(&s, 0, 5); return (strview_t) {
printf("0..5: \"%s\"\n", s2.buf); .buf = ctx->buf + pos,
strFree(&s2); .len = len
};
s2 = strSubstr(&s, 5, SIZE_MAX); }
printf("6..SIZE_MAX: \"%s\"\n", s2.buf);
strFree(&s2); void strLower(str_t *ctx) {
for(size_t i = 0; i < ctx->len; ++i) {
printf("-- view\n"); ctx->buf[i] = (char)tolower(ctx->buf[i]);
strview_t v = strSubview(&s, 0, 5); }
printf("0..5: \"%.*s\"\n", (int)v.len, v.buf); }
v = strSubview(&s, 5, SIZE_MAX);
printf("6..SIZE_MAX: \"%.*s\"\n", (int)v.len, v.buf); str_t strToLower(str_t ctx) {
str_t str = strDup(ctx);
strFree(&s); strLower(&str);
} return str;
}
strFree(&s);
} // == 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"
void strTest(void) {
str_t s;
debug("== testing init =================");
{
s = strInit();
printf("%s %zu\n", s.buf, s.len);
strFree(&s);
s = strInitStr("hello world");
printf("\"%s\" %zu\n", s.buf, s.len);
strFree(&s);
uint8_t buf[] = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' };
s = strInitBuf((char *)buf, sizeof(buf));
printf("\"%s\" %zu\n", s.buf, s.len);
strFree(&s);
}
debug("== testing view =================");
{
s = strInitStr("hello world");
strview_t view = strGetView(&s);
printf("\"%.*s\" %zu\n", (int)view.len, view.buf, view.len);
strFree(&s);
}
debug("== testing begin/end ============");
{
s = strInitStr("hello world");
char *beg = strBegin(&s);
char *end = strEnd(&s);
printf("[ ");
for(; beg < end; ++beg) {
printf("%c ", *beg);
}
printf("]\n");
strFree(&s);
}
debug("== testing back/isempty =========");
{
s = strInitStr("hello world");
printf("[ ");
while(!strIsEmpty(&s)) {
printf("%c ", strBack(&s));
strPop(&s);
}
printf("]\n");
strFree(&s);
}
debug("== testing append ===============");
{
s = strInitStr("hello ");
printf("\"%s\" %zu\n", s.buf, s.len);
strAppend(&s, "world");
printf("\"%s\" %zu\n", s.buf, s.len);
strAppendView(&s, strvInit(", how is it "));
printf("\"%s\" %zu\n", s.buf, s.len);
uint8_t buf[] = { 'g', 'o', 'i', 'n', 'g' };
strAppendBuf(&s, (char*)buf, sizeof(buf));
printf("\"%s\" %zu\n", s.buf, s.len);
strAppendChars(&s, '?', 2);
printf("\"%s\" %zu\n", s.buf, s.len);
strFree(&s);
}
debug("== testing push/pop =============");
{
s = strInit();
str_t s2 = strInitStr("hello world");
printf("%-14s %-14s\n", "s", "s2");
printf("----------------------------\n");
while(!strIsEmpty(&s2)) {
printf("%-14s %-14s\n", s.buf, s2.buf);
strPush(&s, strPop(&s2));
}
printf("%-14s %-14s\n", s.buf, s2.buf);
strFree(&s);
strFree(&s2);
}
debug("== testing swap =================");
{
s = strInitStr("hello");
str_t s2 = strInitStr("world");
printf("%-8s %-8s\n", "s", "s2");
printf("----------------\n");
printf("%-8s %-8s\n", s.buf, s2.buf);
strSwap(&s, &s2);
printf("%-8s %-8s\n", s.buf, s2.buf);
strFree(&s);
strFree(&s2);
}
debug("== testing substr ===============");
{
s = strInitStr("hello world");
printf("s: %s\n", s.buf);
printf("-- string\n");
str_t s2 = strSubstr(&s, 0, 5);
printf("0..5: \"%s\"\n", s2.buf);
strFree(&s2);
s2 = strSubstr(&s, 5, SIZE_MAX);
printf("6..SIZE_MAX: \"%s\"\n", s2.buf);
strFree(&s2);
printf("-- view\n");
strview_t v = strSubview(&s, 0, 5);
printf("0..5: \"%.*s\"\n", (int)v.len, v.buf);
v = strSubview(&s, 5, SIZE_MAX);
printf("6..SIZE_MAX: \"%.*s\"\n", (int)v.len, v.buf);
strFree(&s);
}
strFree(&s);
}
#endif #endif

188
str.h
View file

@ -1,63 +1,127 @@
#pragma once #pragma once
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
#include "slice.h" #include <stdint.h>
#include "strview.h" #include <stdbool.h>
#include <stddef.h>
#define STR_TESTING #include <limits.h>
#include <wchar.h>
// typedef struct { // #include "strview.h"
// char *buf;
// size_t len; #define STRV_NOT_FOUND SIZE_MAX
// } str_t;
typedef struct {
typedef slice_t(char *) str_t; char *buf;
size_t len;
str_t strInit(void); } str_t;
str_t strInitStr(const char *cstr);
str_t strInitView(strview_t view); typedef struct {
str_t strInitBuf(const char *buf, size_t len); const char *buf;
size_t len;
void strFree(str_t *ctx); } strview_t;
str_t strMove(str_t *ctx); // == STR_T ========================================================
str_t strDup(str_t ctx);
str_t strInit(void);
strview_t strGetView(str_t *ctx); str_t strInitStr(const char *cstr);
str_t strInitView(strview_t view);
char *strBegin(str_t *ctx); str_t strInitBuf(const char *buf, size_t len);
char *strEnd(str_t *ctx);
str_t strFromWCHAR(const wchar_t *src, size_t len);
char strBack(str_t *ctx); wchar_t *strToWCHAR(str_t ctx);
bool strIsEmpty(str_t *ctx); void strFree(str_t *ctx);
void strAppend(str_t *ctx, const char *str); str_t strMove(str_t *ctx);
void strAppendStr(str_t *ctx, str_t str); str_t strDup(str_t ctx);
void strAppendView(str_t *ctx, strview_t view);
void strAppendBuf(str_t *ctx, const char *buf, size_t len); strview_t strGetView(str_t *ctx);
void strAppendChars(str_t *ctx, char c, size_t count);
char *strBegin(str_t *ctx);
void strPush(str_t *ctx, char c); char *strEnd(str_t *ctx);
char strPop(str_t *ctx);
char strBack(str_t *ctx);
void strSwap(str_t *ctx, str_t *other);
bool strIsEmpty(str_t *ctx);
// if len == SIZE_MAX, copies until end
str_t strSubstr(str_t *ctx, size_t pos, size_t len); void strAppend(str_t *ctx, const char *str);
// if len == SIZE_MAX, returns until end void strAppendStr(str_t *ctx, str_t str);
strview_t strSubview(str_t *ctx, size_t pos, size_t len); void strAppendView(str_t *ctx, strview_t view);
void strAppendBuf(str_t *ctx, const char *buf, size_t len);
void strLower(str_t *ctx); void strAppendChars(str_t *ctx, char c, size_t count);
str_t strToLower(str_t ctx);
void strPush(str_t *ctx, char c);
#ifdef STR_TESTING char strPop(str_t *ctx);
void strTest(void);
#endif void strSwap(str_t *ctx, str_t *other);
#ifdef __cplusplus void strReplace(str_t *ctx, char from, char to);
} // extern "C"
// 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
strview_t strSubview(str_t *ctx, size_t pos, size_t len);
void strLower(str_t *ctx);
str_t strToLower(str_t ctx);
#ifdef STR_TESTING
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 #endif

File diff suppressed because it is too large Load diff

View file

@ -1,97 +1,104 @@
#pragma once #pragma once
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include "strview.h" #include "str.h"
/* == INPUT STREAM ============================================ */ /* == INPUT STREAM ============================================ */
typedef struct { typedef struct {
const char *start; const char *start;
const char *cur; const char *cur;
size_t size; size_t size;
} str_istream_t; } str_istream_t;
// initialize with null-terminated string // initialize with null-terminated string
str_istream_t istrInit(const char *str); str_istream_t istrInit(const char *str);
str_istream_t istrInitLen(const char *str, size_t len); str_istream_t istrInitLen(const char *str, size_t len);
// get the current character and advance // get the current character and advance
char istrGet(str_istream_t *ctx); char istrGet(str_istream_t *ctx);
// get the current character but don't advance // get the current character but don't advance
char istrPeek(str_istream_t *ctx); char istrPeek(str_istream_t *ctx);
// ignore characters until the delimiter // ignore characters until the delimiter
void istrIgnore(str_istream_t *ctx, char delim); void istrIgnore(str_istream_t *ctx, char delim);
// skip n characters // skip n characters
void istrSkip(str_istream_t *ctx, size_t n); void istrSkip(str_istream_t *ctx, size_t n);
// read len bytes into buffer, the buffer will not be null terminated // read len bytes into buffer, the buffer will not be null terminated
void istrRead(str_istream_t *ctx, char *buf, size_t len); void istrRead(str_istream_t *ctx, char *buf, size_t len);
// read a maximum of len bytes into buffer, the buffer will not be null terminated // read a maximum of len bytes into buffer, the buffer will not be null terminated
// returns the number of bytes read // returns the number of bytes read
size_t istrReadMax(str_istream_t *ctx, char *buf, size_t len); size_t istrReadMax(str_istream_t *ctx, char *buf, size_t len);
// return to the beginning of the stream // return to the beginning of the stream
void istrRewind(str_istream_t *ctx); void istrRewind(str_istream_t *ctx);
// return the number of bytes read from beginning of stream // return the number of bytes read from beginning of stream
size_t istrTell(str_istream_t *ctx); size_t istrTell(str_istream_t *ctx);
// return true if the stream doesn't have any new bytes to read // return true if the stream doesn't have any new bytes to read
bool istrIsFinished(str_istream_t *ctx); bool istrIsFinished(str_istream_t *ctx);
bool istrGetbool(str_istream_t *ctx, bool *val); bool istrGetbool(str_istream_t *ctx, bool *val);
bool istrGetu8(str_istream_t *ctx, uint8_t *val); bool istrGetu8(str_istream_t *ctx, uint8_t *val);
bool istrGetu16(str_istream_t *ctx, uint16_t *val); bool istrGetu16(str_istream_t *ctx, uint16_t *val);
bool istrGetu32(str_istream_t *ctx, uint32_t *val); bool istrGetu32(str_istream_t *ctx, uint32_t *val);
bool istrGetu64(str_istream_t *ctx, uint64_t *val); bool istrGetu64(str_istream_t *ctx, uint64_t *val);
bool istrGeti8(str_istream_t *ctx, int8_t *val); bool istrGeti8(str_istream_t *ctx, int8_t *val);
bool istrGeti16(str_istream_t *ctx, int16_t *val); bool istrGeti16(str_istream_t *ctx, int16_t *val);
bool istrGeti32(str_istream_t *ctx, int32_t *val); bool istrGeti32(str_istream_t *ctx, int32_t *val);
bool istrGeti64(str_istream_t *ctx, int64_t *val); bool istrGeti64(str_istream_t *ctx, int64_t *val);
bool istrGetfloat(str_istream_t *ctx, float *val); bool istrGetfloat(str_istream_t *ctx, float *val);
bool istrGetdouble(str_istream_t *ctx, double *val); bool istrGetdouble(str_istream_t *ctx, double *val);
// get a string until a delimiter, the string is allocated by the function and should be freed // get a string until a delimiter, the string is allocated by the function and should be freed
size_t istrGetstring(str_istream_t *ctx, char **val, char delim); size_t istrGetstring(str_istream_t *ctx, char **val, char delim);
// get a string of maximum size len, the string is not allocated by the function and will be null terminated // get a string of maximum size len, the string is not allocated by the function and will be null terminated
size_t istrGetstringBuf(str_istream_t *ctx, char *val, size_t len); size_t istrGetstringBuf(str_istream_t *ctx, char *val, size_t len);
strview_t istrGetview(str_istream_t *ctx, char delim); strview_t istrGetview(str_istream_t *ctx, char delim);
strview_t istrGetviewLen(str_istream_t *ctx, size_t off, size_t len); strview_t istrGetviewLen(str_istream_t *ctx, size_t off, size_t len);
/* == OUTPUT STREAM =========================================== */ /* == OUTPUT STREAM =========================================== */
typedef struct { typedef struct {
char *buf; char *buf;
size_t size; size_t size;
size_t allocated; size_t allocated;
} str_ostream_t; } str_ostream_t;
str_ostream_t ostrInit(void); str_ostream_t ostrInit(void);
str_ostream_t ostrInitLen(size_t initial_alloc); str_ostream_t ostrInitLen(size_t initial_alloc);
str_ostream_t ostrInitStr(const char *buf, size_t len); str_ostream_t ostrInitStr(const char *buf, size_t len);
void ostrFree(str_ostream_t *ctx); 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);
void ostrPrintf(str_ostream_t *ctx, const char *fmt, ...);
char ostrBack(str_ostream_t *ctx);
void ostrPutc(str_ostream_t *ctx, char c); str_t ostrAsStr(str_ostream_t *ctx);
strview_t ostrAsView(str_ostream_t *ctx);
void ostrAppendbool(str_ostream_t *ctx, bool val);
void ostrAppendu8(str_ostream_t *ctx, uint8_t val); void ostrReplace(str_ostream_t *ctx, char from, char to);
void ostrAppendu16(str_ostream_t *ctx, uint16_t val);
void ostrAppendu32(str_ostream_t *ctx, uint32_t val); void ostrPrintf(str_ostream_t *ctx, const char *fmt, ...);
void ostrAppendu64(str_ostream_t *ctx, uint64_t val); void ostrPutc(str_ostream_t *ctx, char c);
void ostrAppendi8(str_ostream_t *ctx, int8_t val);
void ostrAppendi16(str_ostream_t *ctx, int16_t val); void ostrAppendbool(str_ostream_t *ctx, bool val);
void ostrAppendi32(str_ostream_t *ctx, int32_t val); void ostrAppendchar(str_ostream_t *ctx, char val);
void ostrAppendi64(str_ostream_t *ctx, int64_t val); void ostrAppendu8(str_ostream_t *ctx, uint8_t val);
void ostrAppendfloat(str_ostream_t *ctx, float val); void ostrAppendu16(str_ostream_t *ctx, uint16_t val);
void ostrAppenddouble(str_ostream_t *ctx, double val); void ostrAppendu32(str_ostream_t *ctx, uint32_t val);
void ostrAppendview(str_ostream_t *ctx, strview_t view); void ostrAppendu64(str_ostream_t *ctx, uint64_t val);
void ostrAppendi8(str_ostream_t *ctx, int8_t val);
#ifdef __cplusplus void ostrAppendi16(str_ostream_t *ctx, int16_t val);
} // extern "C" void ostrAppendi32(str_ostream_t *ctx, int32_t val);
void ostrAppendi64(str_ostream_t *ctx, int64_t val);
void ostrAppendfloat(str_ostream_t *ctx, float val);
void ostrAppenddouble(str_ostream_t *ctx, double val);
void ostrAppendview(str_ostream_t *ctx, strview_t view);
#ifdef __cplusplus
} // extern "C"
#endif #endif

250
strview.c
View file

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

View file

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

View file

@ -1,91 +1,91 @@
#include "tracelog.h" #include "tracelog.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdlib.h> #include <stdlib.h>
#ifdef _WIN32 #ifdef _WIN32
#pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS. #pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS.
#endif #endif
#ifdef TLOG_VS #ifdef TLOG_VS
#ifdef _WIN32 #ifdef _WIN32
#ifndef TLOG_NO_COLOURS #ifndef TLOG_NO_COLOURS
#define TLOG_NO_COLOURS #define TLOG_NO_COLOURS
#endif #endif
#define VC_EXTRALEAN #define VC_EXTRALEAN
#include <windows.h> #include <windows.h>
#else #else
#error "can't use TLOG_VS if not on windows" #error "can't use TLOG_VS if not on windows"
#endif #endif
#endif #endif
#ifdef TLOG_NO_COLOURS #ifdef TLOG_NO_COLOURS
#define BLACK "" #define BLACK ""
#define RED "" #define RED ""
#define GREEN "" #define GREEN ""
#define YELLOW "" #define YELLOW ""
#define BLUE "" #define BLUE ""
#define MAGENTA "" #define MAGENTA ""
#define CYAN "" #define CYAN ""
#define WHITE "" #define WHITE ""
#define RESET "" #define RESET ""
#define BOLD "" #define BOLD ""
#else #else
#define BLACK "\033[30m" #define BLACK "\033[30m"
#define RED "\033[31m" #define RED "\033[31m"
#define GREEN "\033[32m" #define GREEN "\033[32m"
#define YELLOW "\033[33m" #define YELLOW "\033[33m"
#define BLUE "\033[22;34m" #define BLUE "\033[22;34m"
#define MAGENTA "\033[35m" #define MAGENTA "\033[35m"
#define CYAN "\033[36m" #define CYAN "\033[36m"
#define WHITE "\033[37m" #define WHITE "\033[37m"
#define RESET "\033[0m" #define RESET "\033[0m"
#define BOLD "\033[1m" #define BOLD "\033[1m"
#endif #endif
#define MAX_TRACELOG_MSG_LENGTH 1024 #define MAX_TRACELOG_MSG_LENGTH 1024
bool use_newline = true; bool use_newline = true;
void traceLog(LogLevel level, const char *fmt, ...) { void traceLog(LogLevel level, const char *fmt, ...) {
char buffer[MAX_TRACELOG_MSG_LENGTH]; char buffer[MAX_TRACELOG_MSG_LENGTH];
memset(buffer, 0, sizeof(buffer)); memset(buffer, 0, sizeof(buffer));
const char *beg; const char *beg;
switch (level) { switch (level) {
case LogTrace: beg = BOLD WHITE "[TRACE]: " RESET; break; case LogTrace: beg = BOLD WHITE "[TRACE]: " RESET; break;
case LogDebug: beg = BOLD BLUE "[DEBUG]: " RESET; break; case LogDebug: beg = BOLD BLUE "[DEBUG]: " RESET; break;
case LogInfo: beg = BOLD GREEN "[INFO]: " RESET; break; case LogInfo: beg = BOLD GREEN "[INFO]: " RESET; break;
case LogWarning: beg = BOLD YELLOW "[WARNING]: " RESET; break; case LogWarning: beg = BOLD YELLOW "[WARNING]: " RESET; break;
case LogError: beg = BOLD RED "[ERROR]: " RESET; break; case LogError: beg = BOLD RED "[ERROR]: " RESET; break;
case LogFatal: beg = BOLD RED "[FATAL]: " RESET; break; case LogFatal: beg = BOLD RED "[FATAL]: " RESET; break;
default: break; default: beg = ""; break;
} }
size_t offset = strlen(beg); size_t offset = strlen(beg);
strncpy(buffer, beg, sizeof(buffer)); strncpy(buffer, beg, sizeof(buffer));
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
vsnprintf(buffer + offset, sizeof(buffer) - offset, fmt, args); vsnprintf(buffer + offset, sizeof(buffer) - offset, fmt, args);
va_end(args); va_end(args);
#ifdef TLOG_VS #ifdef TLOG_VS
OutputDebugStringA(buffer); OutputDebugStringA(buffer);
if(use_newline) OutputDebugStringA("\n"); if(use_newline) OutputDebugStringA("\n");
#else #else
printf("%s", buffer); printf("%s", buffer);
if(use_newline) puts(""); if(use_newline) puts("");
#endif #endif
#ifndef TLOG_DONT_EXIT_ON_FATAL #ifndef TLOG_DONT_EXIT_ON_FATAL
if (level == LogFatal) exit(1); if (level == LogFatal) exit(1);
#endif #endif
} }
void traceUseNewline(bool newline) { void traceUseNewline(bool newline) {
use_newline = newline; use_newline = newline;
} }

View file

@ -1,31 +1,32 @@
#pragma once #pragma once
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
/* Define any of this to turn on the option /* Define any of this to turn on the option
* -> TLOG_NO_COLOURS: print without using colours * -> TLOG_NO_COLOURS: print without using colours
* -> TLOG_VS: print to visual studio console, also turns on TLOG_NO_COLOURS * -> TLOG_VS: print to visual studio console, also turns on TLOG_NO_COLOURS
* -> TLOG_DONT_EXIT_ON_FATAL: don't call 'exit(1)' when using LogFatal * -> TLOG_DONT_EXIT_ON_FATAL: don't call 'exit(1)' when using LogFatal
*/ */
#include <stdbool.h> #include <stdbool.h>
typedef enum { typedef enum {
LogAll, LogTrace, LogDebug, LogInfo, LogWarning, LogError, LogFatal LogAll, LogTrace, LogDebug, LogInfo, LogWarning, LogError, LogFatal
} LogLevel; } LogLevel;
void traceLog(LogLevel level, const char *fmt, ...); void traceLog(LogLevel level, const char *fmt, ...);
void traceUseNewline(bool use_newline); void traceUseNewline(bool use_newline);
#define trace(...) traceLog(LogTrace, __VA_ARGS__) #define tall(...) traceLog(LogAll, __VA_ARGS__)
#define debug(...) traceLog(LogDebug, __VA_ARGS__) #define trace(...) traceLog(LogTrace, __VA_ARGS__)
#define info(...) traceLog(LogInfo, __VA_ARGS__) #define debug(...) traceLog(LogDebug, __VA_ARGS__)
#define warn(...) traceLog(LogWarning, __VA_ARGS__) #define info(...) traceLog(LogInfo, __VA_ARGS__)
#define err(...) traceLog(LogError, __VA_ARGS__) #define warn(...) traceLog(LogWarning, __VA_ARGS__)
#define fatal(...) traceLog(LogFatal, __VA_ARGS__) #define err(...) traceLog(LogError, __VA_ARGS__)
#define fatal(...) traceLog(LogFatal, __VA_ARGS__)
#ifdef __cplusplus
} // extern "C" #ifdef __cplusplus
} // extern "C"
#endif #endif

603
vec.h Normal file
View 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