From 59b55c7f6c0986d798a79f20a9673f18cc245186 Mon Sep 17 00:00:00 2001 From: snarmph Date: Mon, 25 Oct 2021 01:32:31 +0100 Subject: [PATCH] added: dir -> directory walker similar to dirent dirwatch -> lets you watch a directory for changes in another thread vec -> generic vector --- CMakeLists.txt | 40 +- coroutine.c | 20 +- coroutine.h | 264 ++++++------ dir.c | 147 +++++++ dir.h | 29 ++ dirwatch.c | 300 ++++++++++++++ dirwatch.h | 62 +++ file.h | 11 +- fs.c | 254 ++++++------ fs.h | 78 ++-- http.c | 720 ++++++++++++++++----------------- http.h | 258 ++++++------ os.c | 132 +++--- os.h | 50 +-- slice.h | 5 - socket.c | 674 +++++++++++++++---------------- socket.h | 228 +++++------ str.c | 885 ++++++++++++++++++++++++++++------------ str.h | 188 ++++++--- strstream.c | 1054 +++++++++++++++++++++++++----------------------- strstream.h | 199 ++++----- strview.c | 250 ------------ strview.h | 71 ---- tracelog.c | 180 ++++----- tracelog.h | 61 +-- vec.h | 603 +++++++++++++++++++++++++++ 26 files changed, 4037 insertions(+), 2726 deletions(-) create mode 100644 dir.c create mode 100644 dir.h create mode 100644 dirwatch.c create mode 100644 dirwatch.h delete mode 100644 slice.h delete mode 100644 strview.c delete mode 100644 strview.h create mode 100644 vec.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ea67c7..571b555 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,18 +1,22 @@ -add_library(Colla STATIC - socket.c - tracelog.c - http.c - strstream.c - strview.c - str.c - coroutine.c - os.c - fs.c - file.c -) - -IF (WIN32) - target_link_libraries(Colla ws2_32.lib) -ELSE() - # posix -ENDIF() \ No newline at end of file +add_library(Colla STATIC + socket.c + tracelog.c + http.c + strstream.c + str.c + coroutine.c + os.c + fs.c + file.c + dir.c + dirwatch.c +) + +if(MSVC) + target_link_libraries(Colla ws2_32.lib) + target_compile_options(Colla PRIVATE /W4) +else() + target_link_libraries(Colla pthread) + target_compile_options(Colla PRIVATE -Wall -Wextra -Wpedantic) + target_compile_definitions(Colla PUBLIC _DEFAULT_SOURCE) +endif() \ No newline at end of file diff --git a/coroutine.c b/coroutine.c index 26d4d82..76b8a53 100644 --- a/coroutine.c +++ b/coroutine.c @@ -1,11 +1,11 @@ -#include "coroutine.h" - -coroutine_t coInit() { - return (coroutine_t) { - .state = 0 - }; -} - -bool coIsDead(coroutine_t co) { - return co.state == -1; +#include "coroutine.h" + +coroutine_t coInit() { + return (coroutine_t) { + .state = 0 + }; +} + +bool coIsDead(coroutine_t co) { + return co.state == -1; } \ No newline at end of file diff --git a/coroutine.h b/coroutine.h index e4acbd5..92bf65c 100644 --- a/coroutine.h +++ b/coroutine.h @@ -1,133 +1,133 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include // bool -#include // memset -#include "tracelog.h" // fatal - -// heavily inspired by https://gist.github.com/Enichan/5f01140530ff0133fde19c9549a2a973 - -#if 0 -// Usage example: -typedef struct { - int result; - coroutine_t co; -} co_int_t; - -bool coVoid(co_void_t *co) { - costate(co_nostate); - coroutine({ - printf("hello"); yield(); - printf("world"); yield(); - printf("how"); yield(); - printf("is"); yield(); - printf("it"); yield(); - printf("going?\n"); - }); -} - -bool countToTen(co_int_t *co) { - costate(int i; int ii;); - coroutine({ - for(self.i = 0; self.i < 10; ++self.i) { - self.ii += self.i; - yieldVal(self.ii); - } - }); -} - -int main() { - co_void_t covoid = {0}; - while(coVoid(&covoid)) { - printf(" "); - } - - co_int_t coint; - coint.co = coInit(); - while(countToTen(&coint)) { - printf("%d ", coint.result); - } - printf("\n"); - // reset coroutine for next call - coint.co = coInit(); - while(countToTen(&coint)) { - printf("%d ", coint.result); - } - printf("\n"); -} -#endif - -typedef struct { - int state; -} coroutine_t; - -typedef struct { - coroutine_t co; -} co_void_t; - -coroutine_t coInit(); -bool coIsDead(coroutine_t co); - -#define COVAR co -#define COSELF self - -#define costate(...) \ - typedef struct { bool init; __VA_ARGS__ } COSTATE; \ - static COSTATE self = {0} - -#define co_nostate { char __dummy__; } - -#define yieldBreak() \ - COVAR->co.state = -1; \ - self.init = false; \ - return false; - -#define coroutine(...) \ - if(!self.init) { \ - if(COVAR->co.state != 0) { \ - fatal("coroutine not initialized in %s:%d,\n" \ - "did you forget to do '= {0};'?", \ - __FILE__, __LINE__); \ - } \ - memset(&self, 0, sizeof(self)); \ - self.init = true; \ - } \ - switch(COVAR->co.state) { \ - case 0:; \ - __VA_ARGS__ \ - } \ - yieldBreak(); - -#define yield() _yield(__COUNTER__) -#define _yield(count) \ - do { \ - COVAR->co.state = count; \ - return true; \ - case count:; \ - } while(0); - -#define yieldVal(v) _yieldVal(v, __COUNTER__) -#define _yieldVal(v, count) \ - do { \ - COVAR->co.state = count; \ - COVAR->result = v; \ - return true; \ - case count:; \ - } while(0); - -// increment __COUNTER__ past 0 so we don't get double case errors -#define CONCAT_IMPL(x, y) x##y -#define MACRO_CONCAT(x, y) CONCAT_IMPL(x, y) -#define __INC_COUNTER int MACRO_CONCAT(__counter_tmp_, __COUNTER__) -__INC_COUNTER; - -#undef CONCAT_IMPL -#undef MACRO_CONCAT -#undef __INC_COUNTER - -#ifdef __cplusplus -} // extern "C" +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include // bool +#include // memset +#include "tracelog.h" // fatal + +// heavily inspired by https://gist.github.com/Enichan/5f01140530ff0133fde19c9549a2a973 + +#if 0 +// Usage example: +typedef struct { + int result; + coroutine_t co; +} co_int_t; + +bool coVoid(co_void_t *co) { + costate(co_nostate); + coroutine({ + printf("hello"); yield(); + printf("world"); yield(); + printf("how"); yield(); + printf("is"); yield(); + printf("it"); yield(); + printf("going?\n"); + }); +} + +bool countToTen(co_int_t *co) { + costate(int i; int ii;); + coroutine({ + for(self.i = 0; self.i < 10; ++self.i) { + self.ii += self.i; + yieldVal(self.ii); + } + }); +} + +int main() { + co_void_t covoid = {0}; + while(coVoid(&covoid)) { + printf(" "); + } + + co_int_t coint; + coint.co = coInit(); + while(countToTen(&coint)) { + printf("%d ", coint.result); + } + printf("\n"); + // reset coroutine for next call + coint.co = coInit(); + while(countToTen(&coint)) { + printf("%d ", coint.result); + } + printf("\n"); +} +#endif + +typedef struct { + int state; +} coroutine_t; + +typedef struct { + coroutine_t co; +} co_void_t; + +coroutine_t coInit(); +bool coIsDead(coroutine_t co); + +#define COVAR co +#define COSELF self + +#define costate(...) \ + typedef struct { bool init; __VA_ARGS__ } COSTATE; \ + static COSTATE self = {0} + +#define co_nostate { char __dummy__; } + +#define yieldBreak() \ + COVAR->co.state = -1; \ + self.init = false; \ + return false; + +#define coroutine(...) \ + if(!self.init) { \ + if(COVAR->co.state != 0) { \ + fatal("coroutine not initialized in %s:%d,\n" \ + "did you forget to do '= {0};'?", \ + __FILE__, __LINE__); \ + } \ + memset(&self, 0, sizeof(self)); \ + self.init = true; \ + } \ + switch(COVAR->co.state) { \ + case 0:; \ + __VA_ARGS__ \ + } \ + yieldBreak(); + +#define yield() _yield(__COUNTER__) +#define _yield(count) \ + do { \ + COVAR->co.state = count; \ + return true; \ + case count:; \ + } while(0); + +#define yieldVal(v) _yieldVal(v, __COUNTER__) +#define _yieldVal(v, count) \ + do { \ + COVAR->co.state = count; \ + COVAR->result = v; \ + return true; \ + case count:; \ + } while(0); + +// increment __COUNTER__ past 0 so we don't get double case errors +#define CONCAT_IMPL(x, y) x##y +#define MACRO_CONCAT(x, y) CONCAT_IMPL(x, y) +#define __INC_COUNTER int MACRO_CONCAT(__counter_tmp_, __COUNTER__) +__INC_COUNTER; + +#undef CONCAT_IMPL +#undef MACRO_CONCAT +#undef __INC_COUNTER + +#ifdef __cplusplus +} // extern "C" #endif \ No newline at end of file diff --git a/dir.c b/dir.c new file mode 100644 index 0000000..b50d646 --- /dev/null +++ b/dir.c @@ -0,0 +1,147 @@ +#include "dir.h" +#include "tracelog.h" + +#ifdef _WIN32 +#define VC_EXTRALEAN +#include +#include +#include + +#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 +#include + +// 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 \ No newline at end of file diff --git a/dir.h b/dir.h new file mode 100644 index 0000000..961509d --- /dev/null +++ b/dir.h @@ -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 \ No newline at end of file diff --git a/dirwatch.c b/dirwatch.c new file mode 100644 index 0000000..e5d691e --- /dev/null +++ b/dirwatch.c @@ -0,0 +1,300 @@ +#include "dirwatch.h" + +#include +#include +#include "tracelog.h" + +#ifdef _WIN32 +#define VC_EXTRALEAN +#include +#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 +#include +#include // malloc +#include // read +#include +#include +#include +#include // MAX_PATH +#include + +#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 \ No newline at end of file diff --git a/dirwatch.h b/dirwatch.h new file mode 100644 index 0000000..15ac816 --- /dev/null +++ b/dirwatch.h @@ -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 + +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); \ No newline at end of file diff --git a/file.h b/file.h index 895be45..7044772 100644 --- a/file.h +++ b/file.h @@ -1,8 +1,11 @@ #pragma once +#ifdef __cplusplus +extern "C" { +#endif + #include #include "str.h" -#include "strview.h" enum { 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); void fileRewind(file_t *ctx); -uint64_t fileTell(file_t *ctx); \ No newline at end of file +uint64_t fileTell(file_t *ctx); + +#ifdef __cplusplus +} // extern "C" +#endif \ No newline at end of file diff --git a/fs.c b/fs.c index 4c3b0fb..05225f2 100644 --- a/fs.c +++ b/fs.c @@ -1,121 +1,135 @@ -#include "fs.h" - -#include -#include -#include -#include - -#include "tracelog.h" - -#ifdef _WIN32 -#define VC_EXTRALEAN -#include - -#include - -static int _modeToType(unsigned int mode) { - switch(mode & _S_IFMT) { - case _S_IFDIR: return FS_MODE_DIR; - case _S_IFCHR: return FS_MODE_CHARACTER_DEVICE; - case _S_IFREG: return FS_MODE_FILE; - case _S_IFIFO: return FS_MODE_FIFO; - default: return FS_MODE_UKNOWN; - } -} - -fs_stat_t fsStat(file_t fp) { - TCHAR path[MAX_PATH]; - DWORD pathlen = GetFinalPathNameByHandle( - (HANDLE)fp.handle, - path, - MAX_PATH, - 0); - - struct stat statbuf; - int res = stat(path, &statbuf); - if(res == 0) { - return (fs_stat_t) { - .type = _modeToType(statbuf.st_mode), - .size = statbuf.st_size, - .last_access = statbuf.st_atime, - .last_modif = statbuf.st_mtime - }; - } - else { - return (fs_stat_t) { 0 }; - } -} - -fs_time_t fsAsTime(int64_t timer) { - struct tm t; - errno_t error = localtime_s(&t, &timer); - - if(error == 0) { - return (fs_time_t) { - .year = t.tm_year + 1900, - .month = t.tm_mon + 1, - .day = t.tm_mday, - .hour = t.tm_hour, - .minutes = t.tm_min, - .seconds = t.tm_sec, - .daylight_saving = t.tm_isdst > 0 - }; - } - else { - char buf[128]; - strerror_s(buf, sizeof(buf), error); - err("%s", buf); - return (fs_time_t) { 0 }; - } -} - -#else -#include - -static int _modeToType(unsigned int mode) { - switch(mode & __S_IFMT) { - case __S_IFDIR: return FS_MODE_DIR; - case __S_IFCHR: return FS_MODE_CHARACTER_DEVICE; - case __S_IFREG: return FS_MODE_FILE; - case __S_IFIFO: return FS_MODE_FIFO; - default: return FS_MODE_UKNOWN; - } -} - -fs_stat_t fsStat(file_t fp) { - int fd = fileno((FILE*)fp.handle); - struct stat statbuf; - int res = fstat(fd, &statbuf); - if(res == 0) { - return (fs_stat_t) { - .type = _modeToType(statbuf.st_mode), - .size = statbuf.st_size, - .last_access = statbuf.st_atime, - .last_modif = statbuf.st_mtime - }; - } - else { - return (fs_stat_t) { 0 }; - } -} - -fs_time_t fsAsTime(int64_t timer) { - struct tm *t = localtime(&timer); - - if(t) { - return (fs_time_t) { - .year = t->tm_year + 1900, - .month = t->tm_mon + 1, - .day = t->tm_mday, - .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 }; - } -} +#include "fs.h" + +#include +#include +#include +#include + +#include "tracelog.h" + +#ifdef _WIN32 +#define VC_EXTRALEAN +#include + +#include + +static int _modeToType(unsigned int mode) { + switch(mode & _S_IFMT) { + case _S_IFDIR: return FS_MODE_DIR; + case _S_IFCHR: return FS_MODE_CHARACTER_DEVICE; + case _S_IFREG: return FS_MODE_FILE; + case _S_IFIFO: return FS_MODE_FIFO; + default: return FS_MODE_UKNOWN; + } +} + +fs_stat_t fsStat(file_t fp) { + TCHAR path[MAX_PATH]; + GetFinalPathNameByHandle( + (HANDLE)fp.handle, + path, + MAX_PATH, + 0 + ); + + struct stat statbuf; + int res = stat(path, &statbuf); + if(res == 0) { + return (fs_stat_t) { + .type = _modeToType(statbuf.st_mode), + .size = statbuf.st_size, + .last_access = statbuf.st_atime, + .last_modif = statbuf.st_mtime + }; + } + else { + return (fs_stat_t) { 0 }; + } +} + +fs_time_t fsAsTime(int64_t timer) { + struct tm t; + errno_t error = localtime_s(&t, &timer); + + if(error == 0) { + return (fs_time_t) { + .year = t.tm_year + 1900, + .month = t.tm_mon + 1, + .day = t.tm_mday, + .hour = t.tm_hour, + .minutes = t.tm_min, + .seconds = t.tm_sec, + .daylight_saving = t.tm_isdst > 0 + }; + } + else { + char buf[128]; + strerror_s(buf, sizeof(buf), error); + err("%s", buf); + return (fs_time_t) { 0 }; + } +} + +bool fsIsDir(const char *path) { + DWORD attr = GetFileAttributes(path); + + return attr != INVALID_FILE_ATTRIBUTES && + attr & FILE_ATTRIBUTE_DIRECTORY; +} + +#else +#include + +static int _modeToType(unsigned int mode) { + switch(mode & __S_IFMT) { + case __S_IFDIR: return FS_MODE_DIR; + case __S_IFCHR: return FS_MODE_CHARACTER_DEVICE; + case __S_IFREG: return FS_MODE_FILE; + case __S_IFIFO: return FS_MODE_FIFO; + default: return FS_MODE_UKNOWN; + } +} + +fs_stat_t fsStat(file_t fp) { + int fd = fileno((FILE*)fp.handle); + struct stat statbuf; + int res = fstat(fd, &statbuf); + if(res == 0) { + return (fs_stat_t) { + .type = _modeToType(statbuf.st_mode), + .size = statbuf.st_size, + .last_access = statbuf.st_atime, + .last_modif = statbuf.st_mtime + }; + } + else { + return (fs_stat_t) { 0 }; + } +} + +fs_time_t fsAsTime(int64_t timer) { + struct tm *t = localtime(&timer); + + if(t) { + return (fs_time_t) { + .year = t->tm_year + 1900, + .month = t->tm_mon + 1, + .day = t->tm_mday, + .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 \ No newline at end of file diff --git a/fs.h b/fs.h index da70ecc..52d74d1 100644 --- a/fs.h +++ b/fs.h @@ -1,34 +1,44 @@ -#pragma once - -#include -#include - -#include "file.h" - -enum { - FS_MODE_FILE, - FS_MODE_DIR, - FS_MODE_CHARACTER_DEVICE, - FS_MODE_FIFO, - FS_MODE_UKNOWN, -}; - -typedef struct { - int type; - uint64_t size; - int64_t last_access; - int64_t last_modif; -} fs_stat_t; - -typedef struct { - int year; - int month; - int day; - int hour; - int minutes; - int seconds; - bool daylight_saving; -} fs_time_t; - -fs_stat_t fsStat(file_t fp); -fs_time_t fsAsTime(int64_t time); +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include "file.h" + +enum { + FS_MODE_FILE, + FS_MODE_DIR, + FS_MODE_CHARACTER_DEVICE, + FS_MODE_FIFO, + FS_MODE_UKNOWN, +}; + +typedef struct { + int type; + uint64_t size; + int64_t last_access; + int64_t last_modif; +} fs_stat_t; + +typedef struct { + int year; + int month; + int day; + int hour; + int minutes; + int seconds; + 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 \ No newline at end of file diff --git a/http.c b/http.c index f90cf58..bebbd0d 100644 --- a/http.c +++ b/http.c @@ -1,359 +1,361 @@ -#include "http.h" - -#include -#include -#include - -#include "os.h" -#include "tracelog.h" - -// == INTERNAL ================================================================ - -static void _setField(http_field_t **fields_ptr, int *fields_count_ptr, const char *key, const char *value) { - http_field_t *fields = *fields_ptr; - int fields_count = *fields_count_ptr; - - // search if the field already exists - for(int i = 0; i < fields_count; ++i) { - if(stricmp(fields[i].key, key) == 0) { - // replace value - char **curval = &fields[i].value; - size_t cur = strlen(*curval); - size_t new = strlen(value); - if(new > cur) { - *curval = realloc(*curval, new + 1); - } - memcpy(*curval, value, new); - (*curval)[new] = '\0'; - return; - } - } - // otherwise, add it to the list - (*fields_count_ptr)++;; - (*fields_ptr) = realloc(fields, sizeof(http_field_t) * (*fields_count_ptr)); - http_field_t *field = &(*fields_ptr)[(*fields_count_ptr) - 1]; - - size_t klen = strlen(key); - size_t vlen = strlen(value); - field->key = malloc(klen + 1); - field->value = malloc(vlen + 1); - memcpy(field->key, key, klen); - memcpy(field->value, value, vlen); - field->key[klen] = field->value[vlen] = '\0'; -} - -// == HTTP VERSION ============================================================ - -int httpVerNumber(http_version_t ver) { - return (ver.major * 10) + ver.minor; -} - -// == HTTP REQUEST ============================================================ - -http_request_t reqInit() { - http_request_t req; - memset(&req, 0, sizeof(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); - free(ctx->fields[i].value); - } - free(ctx->fields); - free(ctx->uri); - free(ctx->body); - memset(ctx, 0, sizeof(http_request_t)); -} - -bool reqHasField(http_request_t *ctx, const char *key) { - for(int i = 0; i < ctx->field_count; ++i) { - if(stricmp(ctx->fields[i].key, key) == 0) { - return true; - } - } - return false; -} - -void reqSetField(http_request_t *ctx, const char *key, const char *value) { - _setField(&ctx->fields, &ctx->field_count, key, value); -} - -void reqSetUri(http_request_t *ctx, const char *uri) { - if(uri == NULL) return; - size_t len = strlen(uri); - if(uri[0] != '/') { - len += 1; - ctx->uri = realloc(ctx->uri, len + 1); - ctx->uri[0] = '/'; - memcpy(ctx->uri + 1, uri, len); - ctx->uri[len] = '\0'; - } - else { - 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); - - const char *method = NULL; - switch(ctx->method) { - case REQ_GET: method = "GET"; break; - case REQ_POST: method = "POST"; break; - case REQ_HEAD: method = "HEAD"; break; - 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 - ); - - for(int i = 0; i < ctx->field_count; ++i) { - ostrPrintf(&out, "%s: %s\r\n", ctx->fields[i].key, ctx->fields[i].value); - } - - ostrAppendview(&out, strvInit("\r\n")); - if(ctx->body) { - ostrAppendview(&out, strvInit(ctx->body)); - } - -error: - return out; -} - -size_t reqString(http_request_t *ctx, char **str) { - str_ostream_t out = reqPrepare(ctx); - return ostrMove(&out, str); -} - -// == HTTP RESPONSE =========================================================== - -http_response_t resInit() { - 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); - free(ctx->fields[i].value); - } - free(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) { - return true; - } - } - 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) { - return ctx->fields[i].value; - } - } - return NULL; -} - -void resParse(http_response_t *ctx, const char *data) { - str_istream_t in = istrInit(data); - - char hp[5]; - istrGetstringBuf(&in, hp, 5); - 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]); - return; - } - istrSkip(&in, 1); // skip / - istrGetu8(&in, &ctx->version.major); - istrSkip(&in, 1); // skip . - istrGetu8(&in, &ctx->version.minor); - istrGeti32(&in, &ctx->status_code); - - istrIgnore(&in, '\n'); - istrSkip(&in, 1); // skip \n - - resParseFields(ctx, &in); - - const char *tran_encoding = resGetField(ctx, "transfer-encoding"); - if(tran_encoding == NULL || stricmp(tran_encoding, "chunked") != 0) { - strview_t body = istrGetviewLen(&in, 0, SIZE_MAX); - free(ctx->body); - strvCopy(body, &ctx->body); - } - else { - fatal("chunked encoding not implemented yet"); - } -} - -void resParseFields(http_response_t *ctx, str_istream_t *in) { - strview_t line; - - do { - line = istrGetview(in, '\r'); - - size_t pos = strvFind(line, ':', 0); - if(pos != STRV_NOT_FOUND) { - strview_t key = strvSubstr(line, 0, pos); - strview_t value = strvSubstr(line, pos + 2, SIZE_MAX); - - char *key_str = NULL; - char *value_str = NULL; - - strvCopy(key, &key_str); - strvCopy(value, &value_str); - - _setField(&ctx->fields, &ctx->field_count, key_str, value_str); - - free(key_str); - free(value_str); - } - - istrSkip(in, 2); // skip \r\n - } while(line.len > 2); -} - -// == HTTP CLIENT ============================================================= - -http_client_t hcliInit() { - http_client_t 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 hcliSetHost(http_client_t *ctx, const char *hostname) { - strview_t hostview = strvInit(hostname); - // if the hostname starts with http:// (case insensitive) - if(strvICompare(strvSubstr(hostview, 0, 7), strvInit("http://")) == 0) { - strvCopy(strvSubstr(hostview, 7, SIZE_MAX), &ctx->host_name); - } - else if(strvICompare(strvSubstr(hostview, 0, 8), strvInit("https://")) == 0) { - err("HTTPS protocol not yet supported"); - return; - } - else { - // undefined protocol, use HTTP - strvCopy(hostview, &ctx->host_name); - } -} - -http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *req) { - if(!reqHasField(req, "Host")) { - reqSetField(req, "Host", ctx->host_name); - } - if(!reqHasField(req, "Content-Length")) { - if(req->body) { - str_ostream_t out = ostrInitLen(20); - ostrAppendu64(&out, strlen(req->body)); - reqSetField(req, "Content-Length", out.buf); - ostrFree(&out); - } - 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(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); - - 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()); - goto error; - } - - if(skConnect(ctx->socket, ctx->host_name, ctx->port)) { - size_t len = reqString(req, &request_str); - if(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; - } - - char buffer[1024]; - int read = 0; - do { - read = skReceive(ctx->socket, buffer, sizeof(buffer)); - if(read == -1) { - err("couldn't get the data from the server: %s", skGetErrorString()); - goto error; - } - ostrAppendview(&received, strvInitLen(buffer, read)); - } while(read != 0); - - // if the data received is not null terminated - if(*(received.buf + received.size) != '\0') { - ostrPutc(&received, '\0'); - received.size--; - } - - resParse(&res, received.buf); - } - - if(!skClose(ctx->socket)) { - err("Couldn't close socket"); - } - -error: - if(!skCleanup()) { - err("couldn't clean up sockets %s", skGetErrorString()); - } -skopen_error: - free(request_str); - ostrFree(&received); - return res; -} - -http_response_t httpGet(const char *hostname, const char *uri) { - http_request_t request = reqInit(); - request.method = REQ_GET; - reqSetUri(&request, uri); - - http_client_t client = hcliInit(); - hcliSetHost(&client, hostname); - - http_response_t res = hcliSendRequest(&client, &request); - - reqFree(&request); - hcliFree(&client); - - return res; -} - +#include "http.h" + +#include +#include +#include + +#include "os.h" +#include "tracelog.h" + +#define T http_field_t +#define VEC_SHORT_NAME field_vec +#define VEC_DISABLE_ERASE_WHEN +#define VEC_NO_DECLARATION +#include "vec.h" + +// == INTERNAL ================================================================ + +static void _setField(field_vec_t *fields, const char *key, const char *value) { + // search if the field already exists + for(size_t i = 0; i < fields->size; ++i) { + if(stricmp(fields->buf[i].key, key) == 0) { + // replace value + char **curval = &fields->buf[i].value; + size_t cur = strlen(*curval); + size_t new = strlen(value); + if(new > cur) { + *curval = realloc(*curval, new + 1); + } + memcpy(*curval, value, new); + (*curval)[new] = '\0'; + return; + } + } + // otherwise, add it to the list + http_field_t field; + size_t klen = strlen(key); + size_t vlen = strlen(value); + field.key = malloc(klen + 1); + field.value = malloc(vlen + 1); + memcpy(field.key, key, klen); + memcpy(field.value, value, vlen); + field.key[klen] = field.value[vlen] = '\0'; + + field_vecPush(fields, field); +} + +// == HTTP VERSION ============================================================ + +int httpVerNumber(http_version_t ver) { + return (ver.major * 10) + ver.minor; +} + +// == HTTP REQUEST ============================================================ + +http_request_t reqInit() { + http_request_t req; + memset(&req, 0, sizeof(req)); + reqSetUri(&req, "/"); + req.version = (http_version_t){1, 1}; + return req; +} + +void reqFree(http_request_t *ctx) { + for(size_t i = 0; i < ctx->fields.size; ++i) { + free(ctx->fields.buf[i].key); + free(ctx->fields.buf[i].value); + } + field_vecFree(&ctx->fields); + free(ctx->uri); + free(ctx->body); + memset(ctx, 0, sizeof(http_request_t)); +} + +bool reqHasField(http_request_t *ctx, const char *key) { + for(size_t i = 0; i < ctx->fields.size; ++i) { + if(stricmp(ctx->fields.buf[i].key, key) == 0) { + return true; + } + } + return false; +} + +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); + if(uri[0] != '/') { + len += 1; + ctx->uri = realloc(ctx->uri, len + 1); + ctx->uri[0] = '/'; + memcpy(ctx->uri + 1, uri, len); + ctx->uri[len] = '\0'; + } + else { + 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); + + const char *method = NULL; + switch(ctx->method) { + case REQ_GET: method = "GET"; break; + case REQ_POST: method = "POST"; break; + case REQ_HEAD: method = "HEAD"; break; + 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 + ); + + 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)); + } + +error: + return out; +} + +str_t reqString(http_request_t *ctx) { + str_ostream_t out = reqPrepare(ctx); + return ostrMove(&out); +} + +// == HTTP RESPONSE =========================================================== + +http_response_t resInit() { + http_response_t res; + memset(&res, 0, sizeof(res)); + return res; +} + +void resFree(http_response_t *ctx) { + for(size_t i = 0; i < ctx->fields.size; ++i) { + free(ctx->fields.buf[i].key); + free(ctx->fields.buf[i].value); + } + field_vecFree(&ctx->fields); + free(ctx->body); + memset(ctx, 0, sizeof(http_response_t)); +} + +bool resHasField(http_response_t *ctx, const char *key) { + for(size_t i = 0; i < ctx->fields.size; ++i) { + if(stricmp(ctx->fields.buf[i].key, key) == 0) { + return true; + } + } + return false; +} + +const char *resGetField(http_response_t *ctx, const char *field) { + 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; +} + +void resParse(http_response_t *ctx, const char *data) { + str_istream_t in = istrInit(data); + + char hp[5]; + istrGetstringBuf(&in, hp, 5); + 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]); + return; + } + istrSkip(&in, 1); // skip / + istrGetu8(&in, &ctx->version.major); + istrSkip(&in, 1); // skip . + istrGetu8(&in, &ctx->version.minor); + istrGeti32(&in, &ctx->status_code); + + istrIgnore(&in, '\n'); + istrSkip(&in, 1); // skip \n + + resParseFields(ctx, &in); + + const char *tran_encoding = resGetField(ctx, "transfer-encoding"); + if(tran_encoding == NULL || stricmp(tran_encoding, "chunked") != 0) { + strview_t body = istrGetviewLen(&in, 0, SIZE_MAX); + free(ctx->body); + ctx->body = strvCopy(body).buf; + } + else { + fatal("chunked encoding not implemented yet"); + } +} + +void resParseFields(http_response_t *ctx, str_istream_t *in) { + strview_t line; + + do { + line = istrGetview(in, '\r'); + + size_t pos = strvFind(line, ':', 0); + if(pos != STRV_NOT_FOUND) { + strview_t key = strvSubstr(line, 0, pos); + strview_t value = strvSubstr(line, pos + 2, SIZE_MAX); + + char *key_str = NULL; + char *value_str = NULL; + + key_str = strvCopy(key).buf; + value_str = strvCopy(value).buf; + + _setField(&ctx->fields, key_str, value_str); + + free(key_str); + free(value_str); + } + + istrSkip(in, 2); // skip \r\n + } while(line.len > 2); +} + +// == HTTP CLIENT ============================================================= + +http_client_t hcliInit() { + http_client_t 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 hcliSetHost(http_client_t *ctx, const char *hostname) { + strview_t hostview = strvInit(hostname); + // if the hostname starts with http:// (case insensitive) + if(strvICompare(strvSubstr(hostview, 0, 7), strvInit("http://")) == 0) { + ctx->host_name = strvCopy(strvSubstr(hostview, 7, SIZE_MAX)).buf; + } + else if(strvICompare(strvSubstr(hostview, 0, 8), strvInit("https://")) == 0) { + err("HTTPS protocol not yet supported"); + return; + } + 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); + } + if(!reqHasField(req, "Content-Length")) { + if(req->body) { + str_ostream_t out = ostrInitLen(20); + ostrAppendu64(&out, strlen(req->body)); + reqSetField(req, "Content-Length", out.buf); + ostrFree(&out); + } + 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(httpVerNumber(req->version) >= 11 && !reqHasField(req, "Connection")) { + reqSetField(req, "Connection", "close"); + } + + http_response_t res = resInit(); + str_t req_str = strInit(); + str_ostream_t received = ostrInitLen(1024); + + 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()); + goto error; + } + + if(skConnect(ctx->socket, ctx->host_name, ctx->port)) { + req_str = reqString(req); + if(req_str.len == 0) { + err("couldn't get string from request"); + 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 { + read = skReceive(ctx->socket, buffer, sizeof(buffer)); + if(read == -1) { + err("couldn't get the data from the server: %s", skGetErrorString()); + goto error; + } + ostrAppendview(&received, strvInitLen(buffer, read)); + } while(read != 0); + + // if the data received is not null terminated + if(*(received.buf + received.size) != '\0') { + ostrPutc(&received, '\0'); + received.size--; + } + + resParse(&res, received.buf); + } + + if(!skClose(ctx->socket)) { + err("Couldn't close socket"); + } + +error: + if(!skCleanup()) { + err("couldn't clean up sockets %s", skGetErrorString()); + } +skopen_error: + 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; + reqSetUri(&request, uri); + + http_client_t client = hcliInit(); + hcliSetHost(&client, hostname); + + http_response_t res = hcliSendRequest(&client, &request); + + reqFree(&request); + hcliFree(&client); + + return res; +} + diff --git a/http.h b/http.h index ae9b6e7..540510b 100644 --- a/http.h +++ b/http.h @@ -1,128 +1,132 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include - -#include "strstream.h" -#include "strview.h" -#include "socket.h" - -enum { - REQ_GET, - REQ_POST, - REQ_HEAD, - REQ_PUT, - REQ_DELETE -}; - -enum { - // 2xx: success - STATUS_OK = 200, - STATUS_CREATED = 201, - STATUS_ACCEPTED = 202, - STATUS_NO_CONTENT = 204, - STATUS_RESET_CONTENT = 205, - STATUS_PARTIAL_CONTENT = 206, - - // 3xx: redirection - STATUS_MULTIPLE_CHOICES = 300, - STATUS_MOVED_PERMANENTLY = 301, - STATUS_MOVED_TEMPORARILY = 302, - STATUS_NOT_MODIFIED = 304, - - // 4xx: client error - STATUS_BAD_REQUEST = 400, - STATUS_UNAUTHORIZED = 401, - STATUS_FORBIDDEN = 403, - STATUS_NOT_FOUND = 404, - STATUS_RANGE_NOT_SATISFIABLE = 407, - - // 5xx: server error - STATUS_INTERNAL_SERVER_ERROR = 500, - STATUS_NOT_IMPLEMENTED = 501, - STATUS_BAD_GATEWAY = 502, - STATUS_SERVICE_NOT_AVAILABLE = 503, - STATUS_GATEWAY_TIMEOUT = 504, - STATUS_VERSION_NOT_SUPPORTED = 505, -}; - -typedef struct { - uint8_t major; - uint8_t minor; -} http_version_t; - -// 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); - -typedef struct { - char *key; - char *value; -} http_field_t; - -// == HTTP REQUEST ============================================================ - -typedef struct { - int method; - http_version_t version; - http_field_t *fields; - int field_count; - char *uri; - char *body; -} http_request_t; - -http_request_t reqInit(void); -void reqFree(http_request_t *ctx); - -bool reqHasField(http_request_t *ctx, const char *key); - -void reqSetField(http_request_t *ctx, const char *key, const char *value); -void reqSetUri(http_request_t *ctx, const char *uri); - -str_ostream_t reqPrepare(http_request_t *ctx); -size_t reqString(http_request_t *ctx, char **str); - -// == HTTP RESPONSE =========================================================== - -typedef struct { - int status_code; - http_field_t *fields; - int field_count; - http_version_t version; - char *body; -} http_response_t; - -http_response_t resInit(void); -void resFree(http_response_t *ctx); - -bool resHasField(http_response_t *ctx, const char *key); -const char *resGetField(http_response_t *ctx, const char *field); - -void resParse(http_response_t *ctx, const char *data); -void resParseFields(http_response_t *ctx, str_istream_t *in); - -// == HTTP CLIENT ============================================================= - -typedef struct { - char *host_name; - uint16_t port; - socket_t socket; -} http_client_t; - -http_client_t hcliInit(void); -void hcliFree(http_client_t *ctx); - -void hcliSetHost(http_client_t *ctx, const char *hostname); -http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *request); - -// == HTTP ==================================================================== - -http_response_t httpGet(const char *hostname, const char *uri); - -#ifdef __cplusplus -} // extern "C" +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include "str.h" +#include "strstream.h" +#include "socket.h" + +enum { + REQ_GET, + REQ_POST, + REQ_HEAD, + REQ_PUT, + REQ_DELETE +}; + +enum { + // 2xx: success + STATUS_OK = 200, + STATUS_CREATED = 201, + STATUS_ACCEPTED = 202, + STATUS_NO_CONTENT = 204, + STATUS_RESET_CONTENT = 205, + STATUS_PARTIAL_CONTENT = 206, + + // 3xx: redirection + STATUS_MULTIPLE_CHOICES = 300, + STATUS_MOVED_PERMANENTLY = 301, + STATUS_MOVED_TEMPORARILY = 302, + STATUS_NOT_MODIFIED = 304, + + // 4xx: client error + STATUS_BAD_REQUEST = 400, + STATUS_UNAUTHORIZED = 401, + STATUS_FORBIDDEN = 403, + STATUS_NOT_FOUND = 404, + STATUS_RANGE_NOT_SATISFIABLE = 407, + + // 5xx: server error + STATUS_INTERNAL_SERVER_ERROR = 500, + STATUS_NOT_IMPLEMENTED = 501, + STATUS_BAD_GATEWAY = 502, + STATUS_SERVICE_NOT_AVAILABLE = 503, + STATUS_GATEWAY_TIMEOUT = 504, + STATUS_VERSION_NOT_SUPPORTED = 505, +}; + +typedef struct { + uint8_t major; + uint8_t minor; +} http_version_t; + +// 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); + +typedef struct { + char *key; + char *value; +} http_field_t; + +#define T http_field_t +#define VEC_SHORT_NAME field_vec +#define VEC_DISABLE_ERASE_WHEN +#define VEC_NO_IMPLEMENTATION +#include "vec.h" + +// == HTTP REQUEST ============================================================ + +typedef struct { + int method; + http_version_t version; + field_vec_t fields; + char *uri; + char *body; +} http_request_t; + +http_request_t reqInit(void); +void reqFree(http_request_t *ctx); + +bool reqHasField(http_request_t *ctx, const char *key); + +void reqSetField(http_request_t *ctx, const char *key, const char *value); +void reqSetUri(http_request_t *ctx, const char *uri); + +str_ostream_t reqPrepare(http_request_t *ctx); +str_t reqString(http_request_t *ctx); + +// == HTTP RESPONSE =========================================================== + +typedef struct { + int status_code; + field_vec_t fields; + http_version_t version; + char *body; +} http_response_t; + +http_response_t resInit(void); +void resFree(http_response_t *ctx); + +bool resHasField(http_response_t *ctx, const char *key); +const char *resGetField(http_response_t *ctx, const char *field); + +void resParse(http_response_t *ctx, const char *data); +void resParseFields(http_response_t *ctx, str_istream_t *in); + +// == HTTP CLIENT ============================================================= + +typedef struct { + char *host_name; + uint16_t port; + socket_t socket; +} http_client_t; + +http_client_t hcliInit(void); +void hcliFree(http_client_t *ctx); + +void hcliSetHost(http_client_t *ctx, const char *hostname); +http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *request); + +// == HTTP ==================================================================== + +http_response_t httpGet(const char *hostname, const char *uri); + +#ifdef __cplusplus +} // extern "C" #endif \ No newline at end of file diff --git a/os.c b/os.c index d18c477..095d513 100644 --- a/os.c +++ b/os.c @@ -1,67 +1,67 @@ -#include "os.h" - -#include -#include -#include - -#ifdef _WIN32 -#define _BUFSZ 128 - -// 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) { - char *ptr, *eptr; - - if(*buf == NULL || *bufsz == 0) { - *bufsz = _BUFSZ; - if((*buf = malloc(*bufsz)) == NULL) { - return -1; - } - } - - ssize_t result = -1; - // usually fgetc locks every read, using windows-specific - // _lock_file and _unlock_file will be faster - _lock_file(fp); - - for(ptr = *buf, eptr = *buf + *bufsz;;) { - int c = _getc_nolock(fp); - if(c == -1) { - if(feof(fp)) { - ssize_t diff = (ssize_t)(ptr - *buf); - if(diff != 0) { - *ptr = '\0'; - result = diff; - break; - } - } - break; - } - *ptr++ = c; - if(c == delimiter) { - *ptr = '\0'; - result = ptr - *buf; - break; - } - if((ptr + 2) >= eptr) { - char *nbuf; - size_t nbufsz = *bufsz * 2; - ssize_t d = ptr - *buf; - if((nbuf = realloc(*buf, nbufsz)) == NULL) { - break; - } - *buf = nbuf; - *bufsz = nbufsz; - eptr = nbuf + nbufsz; - ptr = nbuf + d; - } - } - - _unlock_file(fp); - return result; -} - -// 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) { - return getdelim(line_ptr, n, '\n', stream); -} +#include "os.h" + +#include +#include +#include + +#ifdef _WIN32 +#define _BUFSZ 128 + +// 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) { + char *ptr, *eptr; + + if(*buf == NULL || *bufsz == 0) { + *bufsz = _BUFSZ; + if((*buf = malloc(*bufsz)) == NULL) { + return -1; + } + } + + ssize_t result = -1; + // usually fgetc locks every read, using windows-specific + // _lock_file and _unlock_file will be faster + _lock_file(fp); + + for(ptr = *buf, eptr = *buf + *bufsz;;) { + int c = _getc_nolock(fp); + if(c == -1) { + if(feof(fp)) { + ssize_t diff = (ssize_t)(ptr - *buf); + if(diff != 0) { + *ptr = '\0'; + result = diff; + break; + } + } + break; + } + *ptr++ = (char)c; + if(c == delimiter) { + *ptr = '\0'; + result = ptr - *buf; + break; + } + if((ptr + 2) >= eptr) { + char *nbuf; + size_t nbufsz = *bufsz * 2; + ssize_t d = ptr - *buf; + if((nbuf = realloc(*buf, nbufsz)) == NULL) { + break; + } + *buf = nbuf; + *bufsz = nbufsz; + eptr = nbuf + nbufsz; + ptr = nbuf + d; + } + } + + _unlock_file(fp); + return result; +} + +// 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) { + return getdelim(line_ptr, n, '\n', stream); +} #endif \ No newline at end of file diff --git a/os.h b/os.h index a941895..3c94cb5 100644 --- a/os.h +++ b/os.h @@ -1,26 +1,26 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -#ifdef _WIN32 - #include - #include // SSIZE_T - typedef SSIZE_T ssize_t; - ssize_t getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp); - ssize_t getline(char **line_ptr, size_t *n, FILE *stream); - #define stricmp _stricmp -#else - #define stricmp strcasecmp - #define _GNU_SOURCE - #include - #include // ssize_t -#endif - -#ifdef __cplusplus -} // extern "C" +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#ifdef _WIN32 + #include + #include // SSIZE_T + typedef SSIZE_T ssize_t; + ssize_t getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp); + ssize_t getline(char **line_ptr, size_t *n, FILE *stream); + #define stricmp _stricmp +#else + #define stricmp strcasecmp + #define _GNU_SOURCE + #include + #include // ssize_t +#endif + +#ifdef __cplusplus +} // extern "C" #endif \ No newline at end of file diff --git a/slice.h b/slice.h deleted file mode 100644 index f8aafd0..0000000 --- a/slice.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include // size_t - -#define slice_t(type) struct { type buf; size_t len; } diff --git a/socket.c b/socket.c index 7482202..c89bedd 100644 --- a/socket.c +++ b/socket.c @@ -1,338 +1,338 @@ -#include "socket.h" - -#include -#include "tracelog.h" - -#ifndef NDEBUG -// VERY MUCH NOT THREAD SAFE -static int initialize_count = 0; -#endif - -#if SOCK_WINDOWS -#include - -static bool _win_skInit(); -static bool _win_skCleanup(); -static int _win_skGetError(); -static const char *_win_skGetErrorString(); - -#define SOCK_CALL(fun) _win_##fun - -#elif SOCK_POSIX -#include -#include -#include -#include // strerror - -#define INVALID_SOCKET (-1) -#define SOCKET_ERROR (-1) - -static bool _posix_skInit(); -static bool _posix_skCleanup(); -static int _posix_skGetError(); -static const char *_posix_skGetErrorString(); - -#define SOCK_CALL(fun) _posix_##fun - -#endif - -bool skInit() { -#ifndef NDEBUG - ++initialize_count; -#endif - return SOCK_CALL(skInit()); -} - -bool skCleanup() { -#ifndef NDEBUG - --initialize_count; -#endif - return SOCK_CALL(skCleanup()); -} - -socket_t skOpen(skType type) { - int sock_type; - - switch(type) { - case SOCK_TCP: sock_type = SOCK_STREAM; break; - case SOCK_UDP: sock_type = SOCK_DGRAM; break; - default: fatal("skType not recognized: %d", type); break; - } - - return skOpenPro(AF_INET, sock_type, 0); -} - -socket_t skOpenEx(const char *protocol) { -#ifndef NDEBUG - if(initialize_count <= 0) { - fatal("skInit has not been called"); - } -#endif - struct protoent *proto = getprotobyname(protocol); - if(!proto) { - return INVALID_SOCKET; - } - return skOpenPro(AF_INET, SOCK_STREAM, proto->p_proto); -} - -socket_t skOpenPro(int af, int type, int protocol) { -#ifndef NDEBUG - if(initialize_count <= 0) { - fatal("skInit has not been called"); - } -#endif - return socket(af, type, protocol); -} - -sk_addrin_t skAddrinInit(const char *ip, uint16_t port) { - sk_addrin_t addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - addr.sin_addr.s_addr = inet_addr(ip); - return addr; -} - -bool skClose(socket_t sock) { -#if SOCK_WINDOWS - int error = closesocket(sock); -#elif SOCK_POSIX - int error = close(sock); -#endif - sock = INVALID_SOCKET; - return error != SOCKET_ERROR; -} - -bool skBind(socket_t sock, const char *ip, uint16_t port) { - sk_addrin_t addr; - addr.sin_family = AF_INET; - // TODO use inet_pton instead - addr.sin_addr.s_addr = inet_addr(ip); - - addr.sin_port = htons(port); - - return skBindPro(sock, (sk_addr_t *) &addr, sizeof(addr)); -} - -bool skBindPro(socket_t sock, const sk_addr_t *name, socket_len_t namelen) { - return bind(sock, name, namelen) != SOCKET_ERROR; -} - -bool skListen(socket_t sock) { - return skListenPro(sock, 1); -} - -bool skListenPro(socket_t sock, int backlog) { - return listen(sock, backlog) != SOCKET_ERROR; -} - -socket_t skAccept(socket_t sock) { - sk_addrin_t addr; - socket_len_t addr_size = (socket_len_t)sizeof(addr); - return skAcceptPro(sock, (sk_addr_t *) &addr, &addr_size); -} - -socket_t skAcceptPro(socket_t sock, sk_addr_t *addr, socket_len_t *addrlen) { - return accept(sock, addr, addrlen); -} - -bool skConnect(socket_t sock, const char *server, unsigned short server_port) { - // TODO use getaddrinfo insetad - struct hostent *host = gethostbyname(server); - // if gethostbyname fails, inet_addr will also fail and return an easier to debug error - const char *address = server; - if(host) { - address = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]); - } - - sk_addrin_t addr; - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = inet_addr(address); - addr.sin_port = htons(server_port); - - return skConnectPro(sock, (sk_addr_t *) &addr, sizeof(addr)); -} - -bool skConnectPro(socket_t sock, const sk_addr_t *name, socket_len_t namelen) { - return connect(sock, name, namelen) != SOCKET_ERROR; -} - -int skSend(socket_t sock, const char *buf, int len) { - return skSendPro(sock, buf, len, 0); -} - -int skSendPro(socket_t sock, const char *buf, int len, int flags) { - return send(sock, buf, len, flags); -} - -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)); -} - -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); -} - -int skReceive(socket_t sock, char *buf, int len) { - return skReceivePro(sock, buf, len, 0); -} - -int skReceivePro(socket_t sock, char *buf, int len, int flags) { - return recv(sock, buf, len, flags); -} - -int skReceiveFrom(socket_t sock, char *buf, int len, sk_addrin_t *from) { - int fromlen = sizeof(sk_addr_t); - return skReceiveFromPro(sock, buf, len, 0, (sk_addr_t*)from, &fromlen); -} - -int skReceiveFromPro(socket_t sock, char *buf, int len, int flags, sk_addr_t *from, int *fromlen) { - return recvfrom(sock, buf, len, flags, from, fromlen); -} - -bool skIsValid(socket_t sock) { - return sock != INVALID_SOCKET; -} - -int skGetError() { - return SOCK_CALL(skGetError()); -} - -const char *skGetErrorString() { - return SOCK_CALL(skGetErrorString()); -} - -#ifdef SOCK_WINDOWS -static bool _win_skInit() { - WSADATA w; - int error = WSAStartup(0x0202, &w); - return error == 0; -} - -static bool _win_skCleanup() { - return WSACleanup() == 0; -} - -static int _win_skGetError() { - return WSAGetLastError(); -} - -static const char *_win_skGetErrorString() { - switch(_win_skGetError()) { - case WSA_INVALID_HANDLE: return "Specified event object handle is invalid."; - case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available."; - case WSA_INVALID_PARAMETER: return "One or more parameters are invalid."; - 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_PENDING: return "Overlapped operations will complete later."; - case WSAEINTR: return "Interrupted function call."; - case WSAEBADF: return "File handle is not valid."; - case WSAEACCES: return "Permission denied."; - case WSAEFAULT: return "Bad address."; - case WSAEINVAL: return "Invalid argument."; - case WSAEMFILE: return "Too many open files."; - case WSAEWOULDBLOCK: return "Resource temporarily unavailable."; - case WSAEINPROGRESS: return "Operation now in progress."; - case WSAEALREADY: return "Operation already in progress."; - case WSAENOTSOCK: return "Socket operation on nonsocket."; - case WSAEDESTADDRREQ: return "Destination address required."; - case WSAEMSGSIZE: return "Message too long."; - case WSAEPROTOTYPE: return "Protocol wrong type for socket."; - case WSAENOPROTOOPT: return "Bad protocol option."; - case WSAEPROTONOSUPPORT: return "Protocol not supported."; - case WSAESOCKTNOSUPPORT: return "Socket type not supported."; - case WSAEOPNOTSUPP: return "Operation not supported."; - case WSAEPFNOSUPPORT: return "Protocol family not supported."; - case WSAEAFNOSUPPORT: return "Address family not supported by protocol family."; - case WSAEADDRINUSE: return "Address already in use."; - case WSAEADDRNOTAVAIL: return "Cannot assign requested address."; - case WSAENETDOWN: return "Network is down."; - case WSAENETUNREACH: return "Network is unreachable."; - case WSAENETRESET: return "Network dropped connection on reset."; - case WSAECONNABORTED: return "Software caused connection abort."; - case WSAECONNRESET: return "Connection reset by peer."; - case WSAENOBUFS: return "No buffer space available."; - case WSAEISCONN: return "Socket is already connected."; - case WSAENOTCONN: return "Socket is not connected."; - case WSAESHUTDOWN: return "Cannot send after socket shutdown."; - case WSAETOOMANYREFS: return "Too many references."; - case WSAETIMEDOUT: return "Connection timed out."; - case WSAECONNREFUSED: return "Connection refused."; - case WSAELOOP: return "Cannot translate name."; - case WSAENAMETOOLONG: return "Name too long."; - case WSAEHOSTDOWN: return "Host is down."; - case WSAEHOSTUNREACH: return "No route to host."; - case WSAENOTEMPTY: return "Directory not empty."; - case WSAEPROCLIM: return "Too many processes."; - case WSAEUSERS: return "User quota exceeded."; - case WSAEDQUOT: return "Disk quota exceeded."; - case WSAESTALE: return "Stale file handle reference."; - case WSAEREMOTE: return "Item is remote."; - case WSASYSNOTREADY: return "Network subsystem is unavailable."; - case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range."; - case WSANOTINITIALISED: return "Successful WSAStartup not yet performed."; - case WSAEDISCON: return "Graceful shutdown in progress."; - case WSAENOMORE: return "No more results."; - case WSAECANCELLED: return "Call has been canceled."; - case WSAEINVALIDPROCTABLE: return "Procedure call table is invalid."; - case WSAEINVALIDPROVIDER: return "Service provider is invalid."; - case WSAEPROVIDERFAILEDINIT: return "Service provider failed to initialize."; - case WSASYSCALLFAILURE: return "System call failure."; - case WSASERVICE_NOT_FOUND: return "Service not found."; - case WSATYPE_NOT_FOUND: return "Class type not found."; - case WSA_E_NO_MORE: return "No more results."; - case WSA_E_CANCELLED: return "Call was canceled."; - case WSAEREFUSED: return "Database query was refused."; - case WSAHOST_NOT_FOUND: return "Host not found."; - case WSATRY_AGAIN: return "Nonauthoritative host not found."; - case WSANO_RECOVERY: return "This is a nonrecoverable error."; - case WSANO_DATA: return "Valid name, no data record of requested type."; - case WSA_QOS_RECEIVERS: return "QoS receivers."; - case WSA_QOS_SENDERS: return "QoS senders."; - case WSA_QOS_NO_SENDERS: return "No QoS senders."; - case WSA_QOS_NO_RECEIVERS: return "QoS no receivers."; - case WSA_QOS_REQUEST_CONFIRMED: return "QoS request confirmed."; - case WSA_QOS_ADMISSION_FAILURE: return "QoS admission error."; - case WSA_QOS_POLICY_FAILURE: return "QoS policy failure."; - case WSA_QOS_BAD_STYLE: return "QoS bad style."; - case WSA_QOS_BAD_OBJECT: return "QoS bad object."; - case WSA_QOS_TRAFFIC_CTRL_ERROR: return "QoS traffic control error."; - case WSA_QOS_GENERIC_ERROR: return "QoS generic error."; - case WSA_QOS_ESERVICETYPE: return "QoS service type error."; - case WSA_QOS_EFLOWSPEC: return "QoS flowspec error."; - case WSA_QOS_EPROVSPECBUF: return "Invalid QoS provider buffer."; - case WSA_QOS_EFILTERSTYLE: return "Invalid QoS filter style."; - case WSA_QOS_EFILTERTYPE: return "Invalid QoS filter type."; - case WSA_QOS_EFILTERCOUNT: return "Incorrect QoS filter count."; - case WSA_QOS_EOBJLENGTH: return "Invalid QoS object length."; - case WSA_QOS_EFLOWCOUNT: return "Incorrect QoS flow count."; - case WSA_QOS_EUNKOWNPSOBJ: return "Unrecognized QoS object."; - case WSA_QOS_EPOLICYOBJ: return "Invalid QoS policy object."; - case WSA_QOS_EFLOWDESC: return "Invalid QoS flow descriptor."; - case WSA_QOS_EPSFLOWSPEC: return "Invalid QoS provider-specific flowspec."; - case WSA_QOS_EPSFILTERSPEC: return "Invalid QoS provider-specific filterspec."; - case WSA_QOS_ESDMODEOBJ: return "Invalid QoS shape discard mode object."; - case WSA_QOS_ESHAPERATEOBJ: return "Invalid QoS shaping rate object."; - case WSA_QOS_RESERVED_PETYPE: return "Reserved policy QoS element type."; - } - - return "(nothing)"; -} - -#else - -static bool _posix_skInit() { - return true; -} - -static bool _posix_skCleanup() { - return true; -} - -static int _posix_skGetError() { - return errno; -} - -static const char *_posix_skGetErrorString() { - return strerror(errno); -} +#include "socket.h" + +#include +#include "tracelog.h" + +#ifndef NDEBUG +// VERY MUCH NOT THREAD SAFE +static int initialize_count = 0; +#endif + +#if SOCK_WINDOWS +#include + +static bool _win_skInit(); +static bool _win_skCleanup(); +static int _win_skGetError(); +static const char *_win_skGetErrorString(); + +#define SOCK_CALL(fun) _win_##fun + +#elif SOCK_POSIX +#include +#include +#include +#include // strerror + +#define INVALID_SOCKET (-1) +#define SOCKET_ERROR (-1) + +static bool _posix_skInit(); +static bool _posix_skCleanup(); +static int _posix_skGetError(); +static const char *_posix_skGetErrorString(); + +#define SOCK_CALL(fun) _posix_##fun + +#endif + +bool skInit() { +#ifndef NDEBUG + ++initialize_count; +#endif + return SOCK_CALL(skInit()); +} + +bool skCleanup() { +#ifndef NDEBUG + --initialize_count; +#endif + return SOCK_CALL(skCleanup()); +} + +socket_t skOpen(skType type) { + int sock_type = 0; + + switch(type) { + case SOCK_TCP: sock_type = SOCK_STREAM; break; + case SOCK_UDP: sock_type = SOCK_DGRAM; break; + default: fatal("skType not recognized: %d", type); break; + } + + return skOpenPro(AF_INET, sock_type, 0); +} + +socket_t skOpenEx(const char *protocol) { +#ifndef NDEBUG + if(initialize_count <= 0) { + fatal("skInit has not been called"); + } +#endif + struct protoent *proto = getprotobyname(protocol); + if(!proto) { + return INVALID_SOCKET; + } + return skOpenPro(AF_INET, SOCK_STREAM, proto->p_proto); +} + +socket_t skOpenPro(int af, int type, int protocol) { +#ifndef NDEBUG + if(initialize_count <= 0) { + fatal("skInit has not been called"); + } +#endif + return socket(af, type, protocol); +} + +sk_addrin_t skAddrinInit(const char *ip, uint16_t port) { + sk_addrin_t addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = inet_addr(ip); + return addr; +} + +bool skClose(socket_t sock) { +#if SOCK_WINDOWS + int error = closesocket(sock); +#elif SOCK_POSIX + int error = close(sock); +#endif + sock = INVALID_SOCKET; + return error != SOCKET_ERROR; +} + +bool skBind(socket_t sock, const char *ip, uint16_t port) { + sk_addrin_t addr; + addr.sin_family = AF_INET; + // TODO use inet_pton instead + addr.sin_addr.s_addr = inet_addr(ip); + + addr.sin_port = htons(port); + + return skBindPro(sock, (sk_addr_t *) &addr, sizeof(addr)); +} + +bool skBindPro(socket_t sock, const sk_addr_t *name, socket_len_t namelen) { + return bind(sock, name, namelen) != SOCKET_ERROR; +} + +bool skListen(socket_t sock) { + return skListenPro(sock, 1); +} + +bool skListenPro(socket_t sock, int backlog) { + return listen(sock, backlog) != SOCKET_ERROR; +} + +socket_t skAccept(socket_t sock) { + sk_addrin_t addr; + socket_len_t addr_size = (socket_len_t)sizeof(addr); + return skAcceptPro(sock, (sk_addr_t *) &addr, &addr_size); +} + +socket_t skAcceptPro(socket_t sock, sk_addr_t *addr, socket_len_t *addrlen) { + return accept(sock, addr, addrlen); +} + +bool skConnect(socket_t sock, const char *server, unsigned short server_port) { + // TODO use getaddrinfo insetad + struct hostent *host = gethostbyname(server); + // if gethostbyname fails, inet_addr will also fail and return an easier to debug error + const char *address = server; + if(host) { + address = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]); + } + + sk_addrin_t addr; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr(address); + addr.sin_port = htons(server_port); + + return skConnectPro(sock, (sk_addr_t *) &addr, sizeof(addr)); +} + +bool skConnectPro(socket_t sock, const sk_addr_t *name, socket_len_t namelen) { + return connect(sock, name, namelen) != SOCKET_ERROR; +} + +int skSend(socket_t sock, const char *buf, int len) { + return skSendPro(sock, buf, len, 0); +} + +int skSendPro(socket_t sock, const char *buf, int len, int flags) { + return send(sock, buf, len, flags); +} + +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)); +} + +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); +} + +int skReceive(socket_t sock, char *buf, int len) { + return skReceivePro(sock, buf, len, 0); +} + +int skReceivePro(socket_t sock, char *buf, int len, int flags) { + return recv(sock, buf, len, flags); +} + +int skReceiveFrom(socket_t sock, char *buf, int len, sk_addrin_t *from) { + socket_len_t fromlen = sizeof(sk_addr_t); + return skReceiveFromPro(sock, buf, len, 0, (sk_addr_t*)from, &fromlen); +} + +int skReceiveFromPro(socket_t sock, char *buf, int len, int flags, sk_addr_t *from, socket_len_t *fromlen) { + return recvfrom(sock, buf, len, flags, from, fromlen); +} + +bool skIsValid(socket_t sock) { + return sock != INVALID_SOCKET; +} + +int skGetError() { + return SOCK_CALL(skGetError()); +} + +const char *skGetErrorString() { + return SOCK_CALL(skGetErrorString()); +} + +#ifdef SOCK_WINDOWS +static bool _win_skInit() { + WSADATA w; + int error = WSAStartup(0x0202, &w); + return error == 0; +} + +static bool _win_skCleanup() { + return WSACleanup() == 0; +} + +static int _win_skGetError() { + return WSAGetLastError(); +} + +static const char *_win_skGetErrorString() { + switch(_win_skGetError()) { + case WSA_INVALID_HANDLE: return "Specified event object handle is invalid."; + case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available."; + case WSA_INVALID_PARAMETER: return "One or more parameters are invalid."; + 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_PENDING: return "Overlapped operations will complete later."; + case WSAEINTR: return "Interrupted function call."; + case WSAEBADF: return "File handle is not valid."; + case WSAEACCES: return "Permission denied."; + case WSAEFAULT: return "Bad address."; + case WSAEINVAL: return "Invalid argument."; + case WSAEMFILE: return "Too many open files."; + case WSAEWOULDBLOCK: return "Resource temporarily unavailable."; + case WSAEINPROGRESS: return "Operation now in progress."; + case WSAEALREADY: return "Operation already in progress."; + case WSAENOTSOCK: return "Socket operation on nonsocket."; + case WSAEDESTADDRREQ: return "Destination address required."; + case WSAEMSGSIZE: return "Message too long."; + case WSAEPROTOTYPE: return "Protocol wrong type for socket."; + case WSAENOPROTOOPT: return "Bad protocol option."; + case WSAEPROTONOSUPPORT: return "Protocol not supported."; + case WSAESOCKTNOSUPPORT: return "Socket type not supported."; + case WSAEOPNOTSUPP: return "Operation not supported."; + case WSAEPFNOSUPPORT: return "Protocol family not supported."; + case WSAEAFNOSUPPORT: return "Address family not supported by protocol family."; + case WSAEADDRINUSE: return "Address already in use."; + case WSAEADDRNOTAVAIL: return "Cannot assign requested address."; + case WSAENETDOWN: return "Network is down."; + case WSAENETUNREACH: return "Network is unreachable."; + case WSAENETRESET: return "Network dropped connection on reset."; + case WSAECONNABORTED: return "Software caused connection abort."; + case WSAECONNRESET: return "Connection reset by peer."; + case WSAENOBUFS: return "No buffer space available."; + case WSAEISCONN: return "Socket is already connected."; + case WSAENOTCONN: return "Socket is not connected."; + case WSAESHUTDOWN: return "Cannot send after socket shutdown."; + case WSAETOOMANYREFS: return "Too many references."; + case WSAETIMEDOUT: return "Connection timed out."; + case WSAECONNREFUSED: return "Connection refused."; + case WSAELOOP: return "Cannot translate name."; + case WSAENAMETOOLONG: return "Name too long."; + case WSAEHOSTDOWN: return "Host is down."; + case WSAEHOSTUNREACH: return "No route to host."; + case WSAENOTEMPTY: return "Directory not empty."; + case WSAEPROCLIM: return "Too many processes."; + case WSAEUSERS: return "User quota exceeded."; + case WSAEDQUOT: return "Disk quota exceeded."; + case WSAESTALE: return "Stale file handle reference."; + case WSAEREMOTE: return "Item is remote."; + case WSASYSNOTREADY: return "Network subsystem is unavailable."; + case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range."; + case WSANOTINITIALISED: return "Successful WSAStartup not yet performed."; + case WSAEDISCON: return "Graceful shutdown in progress."; + case WSAENOMORE: return "No more results."; + case WSAECANCELLED: return "Call has been canceled."; + case WSAEINVALIDPROCTABLE: return "Procedure call table is invalid."; + case WSAEINVALIDPROVIDER: return "Service provider is invalid."; + case WSAEPROVIDERFAILEDINIT: return "Service provider failed to initialize."; + case WSASYSCALLFAILURE: return "System call failure."; + case WSASERVICE_NOT_FOUND: return "Service not found."; + case WSATYPE_NOT_FOUND: return "Class type not found."; + case WSA_E_NO_MORE: return "No more results."; + case WSA_E_CANCELLED: return "Call was canceled."; + case WSAEREFUSED: return "Database query was refused."; + case WSAHOST_NOT_FOUND: return "Host not found."; + case WSATRY_AGAIN: return "Nonauthoritative host not found."; + case WSANO_RECOVERY: return "This is a nonrecoverable error."; + case WSANO_DATA: return "Valid name, no data record of requested type."; + case WSA_QOS_RECEIVERS: return "QoS receivers."; + case WSA_QOS_SENDERS: return "QoS senders."; + case WSA_QOS_NO_SENDERS: return "No QoS senders."; + case WSA_QOS_NO_RECEIVERS: return "QoS no receivers."; + case WSA_QOS_REQUEST_CONFIRMED: return "QoS request confirmed."; + case WSA_QOS_ADMISSION_FAILURE: return "QoS admission error."; + case WSA_QOS_POLICY_FAILURE: return "QoS policy failure."; + case WSA_QOS_BAD_STYLE: return "QoS bad style."; + case WSA_QOS_BAD_OBJECT: return "QoS bad object."; + case WSA_QOS_TRAFFIC_CTRL_ERROR: return "QoS traffic control error."; + case WSA_QOS_GENERIC_ERROR: return "QoS generic error."; + case WSA_QOS_ESERVICETYPE: return "QoS service type error."; + case WSA_QOS_EFLOWSPEC: return "QoS flowspec error."; + case WSA_QOS_EPROVSPECBUF: return "Invalid QoS provider buffer."; + case WSA_QOS_EFILTERSTYLE: return "Invalid QoS filter style."; + case WSA_QOS_EFILTERTYPE: return "Invalid QoS filter type."; + case WSA_QOS_EFILTERCOUNT: return "Incorrect QoS filter count."; + case WSA_QOS_EOBJLENGTH: return "Invalid QoS object length."; + case WSA_QOS_EFLOWCOUNT: return "Incorrect QoS flow count."; + case WSA_QOS_EUNKOWNPSOBJ: return "Unrecognized QoS object."; + case WSA_QOS_EPOLICYOBJ: return "Invalid QoS policy object."; + case WSA_QOS_EFLOWDESC: return "Invalid QoS flow descriptor."; + case WSA_QOS_EPSFLOWSPEC: return "Invalid QoS provider-specific flowspec."; + case WSA_QOS_EPSFILTERSPEC: return "Invalid QoS provider-specific filterspec."; + case WSA_QOS_ESDMODEOBJ: return "Invalid QoS shape discard mode object."; + case WSA_QOS_ESHAPERATEOBJ: return "Invalid QoS shaping rate object."; + case WSA_QOS_RESERVED_PETYPE: return "Reserved policy QoS element type."; + } + + return "(nothing)"; +} + +#else + +static bool _posix_skInit() { + return true; +} + +static bool _posix_skCleanup() { + return true; +} + +static int _posix_skGetError() { + return errno; +} + +static const char *_posix_skGetErrorString() { + return strerror(errno); +} #endif \ No newline at end of file diff --git a/socket.h b/socket.h index b547536..52a345f 100644 --- a/socket.h +++ b/socket.h @@ -1,115 +1,115 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -#ifdef _WIN32 - #define SOCK_WINDOWS 1 -#else - #define SOCK_POSIX 1 -#endif - -#if SOCK_WINDOWS - #pragma warning(disable:4996) // _WINSOCK_DEPRECATED_NO_WARNINGS - #include - typedef SOCKET socket_t; - typedef int socket_len_t; -#elif SOCK_POSIX - #include - #include - #include - typedef int socket_t; - typedef uint32_t socket_len_t; - #define INVALID_SOCKET (-1) - #define SOCKET_ERROR (-1) -#endif - -typedef struct sockaddr sk_addr_t; -typedef struct sockaddr_in sk_addrin_t; - -typedef enum { - SOCK_TCP, - SOCK_UDP, -} skType; - -// == RAW SOCKETS ========================================== - -// Initialize sockets, returns true on success -bool skInit(void); -// Terminates sockets, returns true on success -bool skCleanup(void); - -// Opens a socket, check socket_t with skValid -socket_t skOpen(skType type); -// Opens a socket using 'protocol', options are -// ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp -// check socket_t with skValid -socket_t skOpenEx(const char *protocol); -// Opens a socket, check socket_t with skValid -socket_t skOpenPro(int af, int type, int protocol); - -// Fill out a sk_addrin_t structure with "ip" and "port" -sk_addrin_t skAddrinInit(const char *ip, uint16_t port); - -// Closes a socket, returns true on success -bool skClose(socket_t sock); - -// Associate a local address with a socket -bool skBind(socket_t sock, const char *ip, uint16_t port); -// Associate a local address with a socket -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 -bool skListen(socket_t sock); -// Place a socket in a state in which it is listening for an incoming connection -bool skListenPro(socket_t sock, int backlog); - -// Permits an incoming connection attempt on a socket -socket_t skAccept(socket_t sock); -// Permits an incoming connection attempt on a socket -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 -bool skConnect(socket_t sock, const char *server, unsigned short server_port); -// Connects to a server, returns true on success -bool skConnectPro(socket_t sock, const sk_addr_t *name, socket_len_t namelen); - -// Sends data on a socket, returns true on success -int skSend(socket_t sock, const char *buf, int len); -// Sends data on a socket, returns true on success -int skSendPro(socket_t sock, const char *buf, int len, int flags); -// Sends data to a specific destination -int skSendTo(socket_t sock, const char *buf, int len, const sk_addrin_t *to); -// 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); -// 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); -// 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); -// Receives a datagram and stores the source address. -int skReceiveFrom(socket_t sock, char *buf, int len, sk_addrin_t *from); -// Receives a datagram and stores the source address. -int skReceiveFromPro(socket_t sock, char *buf, int len, int flags, sk_addr_t *from, int *fromlen); - -// Checks that a opened socket is valid, returns true on success -bool skIsValid(socket_t sock); - -// Returns latest socket error, returns 0 if there is no error -int skGetError(void); -// Returns a human-readable string from a skGetError -const char *skGetErrorString(void); - -// == UDP SOCKETS ========================================== - -typedef socket_t udpsock_t; - - - - -#ifdef __cplusplus -} // extern "C" +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#ifdef _WIN32 + #define SOCK_WINDOWS 1 +#else + #define SOCK_POSIX 1 +#endif + +#if SOCK_WINDOWS + #pragma warning(disable:4996) // _WINSOCK_DEPRECATED_NO_WARNINGS + #include + typedef SOCKET socket_t; + typedef int socket_len_t; +#elif SOCK_POSIX + #include + #include + #include + typedef int socket_t; + typedef uint32_t socket_len_t; + #define INVALID_SOCKET (-1) + #define SOCKET_ERROR (-1) +#endif + +typedef struct sockaddr sk_addr_t; +typedef struct sockaddr_in sk_addrin_t; + +typedef enum { + SOCK_TCP, + SOCK_UDP, +} skType; + +// == RAW SOCKETS ========================================== + +// Initialize sockets, returns true on success +bool skInit(void); +// Terminates sockets, returns true on success +bool skCleanup(void); + +// Opens a socket, check socket_t with skValid +socket_t skOpen(skType type); +// Opens a socket using 'protocol', options are +// ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp +// check socket_t with skValid +socket_t skOpenEx(const char *protocol); +// Opens a socket, check socket_t with skValid +socket_t skOpenPro(int af, int type, int protocol); + +// Fill out a sk_addrin_t structure with "ip" and "port" +sk_addrin_t skAddrinInit(const char *ip, uint16_t port); + +// Closes a socket, returns true on success +bool skClose(socket_t sock); + +// Associate a local address with a socket +bool skBind(socket_t sock, const char *ip, uint16_t port); +// Associate a local address with a socket +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 +bool skListen(socket_t sock); +// Place a socket in a state in which it is listening for an incoming connection +bool skListenPro(socket_t sock, int backlog); + +// Permits an incoming connection attempt on a socket +socket_t skAccept(socket_t sock); +// Permits an incoming connection attempt on a socket +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 +bool skConnect(socket_t sock, const char *server, unsigned short server_port); +// Connects to a server, returns true on success +bool skConnectPro(socket_t sock, const sk_addr_t *name, socket_len_t namelen); + +// Sends data on a socket, returns true on success +int skSend(socket_t sock, const char *buf, int len); +// Sends data on a socket, returns true on success +int skSendPro(socket_t sock, const char *buf, int len, int flags); +// Sends data to a specific destination +int skSendTo(socket_t sock, const char *buf, int len, const sk_addrin_t *to); +// 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); +// 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); +// 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); +// Receives a datagram and stores the source address. +int skReceiveFrom(socket_t sock, char *buf, int len, sk_addrin_t *from); +// Receives a datagram and stores the source address. +int skReceiveFromPro(socket_t sock, char *buf, int len, int flags, sk_addr_t *from, socket_len_t *fromlen); + +// Checks that a opened socket is valid, returns true on success +bool skIsValid(socket_t sock); + +// Returns latest socket error, returns 0 if there is no error +int skGetError(void); +// Returns a human-readable string from a skGetError +const char *skGetErrorString(void); + +// == UDP SOCKETS ========================================== + +typedef socket_t udpsock_t; + + + + +#ifdef __cplusplus +} // extern "C" #endif \ No newline at end of file diff --git a/str.c b/str.c index b8e4369..9feb60a 100644 --- a/str.c +++ b/str.c @@ -1,267 +1,620 @@ -#include "str.h" - -#include -#include -#include - -str_t strInit(void) { - return (str_t) { - .buf = NULL, - .len = 0 - }; -} - -str_t strInitStr(const char *cstr) { - return strInitBuf(cstr, strlen(cstr)); -} - -str_t strInitView(strview_t view) { - return strInitBuf(view.buf, view.len); -} - -str_t strInitBuf(const char *buf, size_t len) { - str_t str; - str.len = len; - str.buf = malloc(len + 1); - memcpy(str.buf, buf, len); - str.buf[len] = '\0'; - return str; -} - -void strFree(str_t *ctx) { - free(ctx->buf); - ctx->buf = NULL; - ctx->len = 0; -} - -str_t strMove(str_t *ctx) { - str_t str = strInitBuf(ctx->buf, ctx->len); - ctx->buf = NULL; - ctx->len = 0; - return str; -} - -str_t strDup(str_t ctx) { - return strInitBuf(ctx.buf, ctx.len); -} - -strview_t strGetView(str_t *ctx) { - return (strview_t) { - .buf = ctx->buf, - .len = ctx->len - }; -} - -char *strBegin(str_t *ctx) { - return ctx->buf; -} - -char *strEnd(str_t *ctx) { - return ctx->buf ? ctx->buf + ctx->len : NULL; -} - -char strBack(str_t *ctx) { - return ctx->buf ? ctx->buf[ctx->len - 1] : '\0'; -} - -bool strIsEmpty(str_t *ctx) { - return ctx->len == 0; -} - -void strAppend(str_t *ctx, const char *str) { - strAppendBuf(ctx, str, strlen(str)); -} - -void strAppendStr(str_t *ctx, str_t str) { - strAppendBuf(ctx, str.buf, str.len); -} - -void strAppendView(str_t *ctx, strview_t view) { - strAppendBuf(ctx, view.buf, view.len); -} - -void strAppendBuf(str_t *ctx, const char *buf, size_t len) { - size_t oldlen = ctx->len; - ctx->len += len; - ctx->buf = realloc(ctx->buf, ctx->len + 1); - memcpy(ctx->buf + oldlen, buf, len); - ctx->buf[ctx->len] = '\0'; -} - -void strAppendChars(str_t *ctx, char c, size_t count) { - size_t oldlen = ctx->len; - ctx->len += count; - ctx->buf = realloc(ctx->buf, ctx->len + 1); - memset(ctx->buf + oldlen, c, count); - ctx->buf[ctx->len] = '\0'; -} - -void strPush(str_t *ctx, char c) { - strAppendChars(ctx, c, 1); -} - -char strPop(str_t *ctx) { - char c = strBack(ctx); - ctx->buf = realloc(ctx->buf, ctx->len); - ctx->len -= 1; - ctx->buf[ctx->len] = '\0'; - return c; -} - -void strSwap(str_t *ctx, str_t *other) { - char *buf = other->buf; - size_t len = other->len; - other->buf = ctx->buf; - other->len = ctx->len; - ctx->buf = buf; - ctx->len = len; -} - -#include "tracelog.h" - -str_t strSubstr(str_t *ctx, size_t pos, size_t len) { - if(strIsEmpty(ctx)) return strInit(); - if(len == SIZE_MAX || (pos + len) > ctx->len) len = ctx->len - pos; - return strInitBuf(ctx->buf + pos, len); -} - -strview_t strSubview(str_t *ctx, size_t pos, size_t len) { - if(strIsEmpty(ctx)) return strvInit(NULL); - if(len == SIZE_MAX || (pos + len) > ctx->len) len = ctx->len - pos; - return (strview_t) { - .buf = ctx->buf + pos, - .len = len - }; -} - -void strLower(str_t *ctx) { - for(size_t i = 0; i < ctx->len; ++i) { - ctx->buf[i] = tolower(ctx->buf[i]); - } -} - -str_t strToLower(str_t ctx) { - str_t str = strDup(ctx); - strLower(&str); - return str; -} - -#ifdef STR_TESTING -#include -#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); -} +#include "str.h" + +#include +#include +#include +#include +#include +#include + +#include "tracelog.h" + +#ifdef _WIN32 +#define VC_EXTRALEAN +#include +#else +#include +#endif + +#ifndef min +#define min(a, b) ((a) < (b) ? (a) : (b)) +#endif + +// == STR_T ======================================================== + +str_t strInit(void) { + return (str_t) { + .buf = NULL, + .len = 0 + }; +} + +str_t strInitStr(const char *cstr) { + return strInitBuf(cstr, strlen(cstr)); +} + +str_t strInitView(strview_t view) { + return strInitBuf(view.buf, view.len); +} + +str_t strInitBuf(const char *buf, size_t len) { + str_t str; + str.len = len; + str.buf = malloc(len + 1); + memcpy(str.buf, buf, len); + str.buf[len] = '\0'; + return str; +} + +void strFree(str_t *ctx) { + free(ctx->buf); + ctx->buf = NULL; + ctx->len = 0; +} + +str_t strFromWCHAR(const wchar_t *src, size_t len) { + if(len == 0) len = wcslen(src); + +#ifdef _WIN32 + // TODO CP_ACP should be CP_UTF8 but if i put CP_UTF8 it doesn't work?? + int result_len = WideCharToMultiByte( + CP_ACP, 0, + src, (int)len, + NULL, 0, + NULL, NULL + ); + char *buf = malloc(result_len + 1); + if(buf) { + WideCharToMultiByte( + CP_ACP, 0, + src, (int)len, + buf, result_len, + NULL, NULL + ); + buf[result_len] = '\0'; + } + return (str_t) { + .buf = buf, + .len = result_len + }; +#else + size_t actual_len = len * sizeof(wchar_t); + + size_t dest_len = len * 6; + char *dest = malloc(dest_len); + + iconv_t cd = iconv_open("UTF-8", "WCHAR_T"); + assert(cd); + + size_t dest_left = dest_len; + char *dest_temp = dest; + char *src_temp = (char*)src; + size_t lost = iconv(cd, &src_temp, &actual_len, &dest_temp, &dest_left); + assert(lost != ((size_t)-1)); + + dest_len -= dest_left; + dest = realloc(dest, dest_len + 1); + dest[dest_len] = '\0'; + + iconv_close(cd); + + return (str_t){ + .buf = dest, + .len = dest_len + }; +#endif +} + +wchar_t *strToWCHAR(str_t ctx) { +#ifdef _WIN32 + UINT codepage = CP_ACP; + int len = MultiByteToWideChar( + codepage, 0, + ctx.buf, (int)ctx.len, + NULL, 0 + ); + wchar_t *str = malloc(sizeof(wchar_t) * (len + 1)); + if(!str) return NULL; + len = MultiByteToWideChar( + codepage, 0, + ctx.buf, (int)ctx.len, + str, len + ); + str[len] = '\0'; + return str; +#else + size_t dest_len = ctx.len * sizeof(wchar_t); + char *dest = malloc(dest_len); + + iconv_t cd = iconv_open("WCHAR_T", "UTF-8"); + assert(cd); + + size_t dest_left = dest_len; + char *dest_temp = dest; + char *src_temp = ctx.buf; + size_t lost = iconv(cd, &src_temp, &ctx.len, &dest_temp, &dest_left); + assert(lost != ((size_t)-1)); + + dest_len -= dest_left; + dest = realloc(dest, dest_len + 1); + dest[dest_len] = '\0'; + + iconv_close(cd); + + return (wchar_t *)dest; +#endif +} + +str_t strMove(str_t *ctx) { + str_t str = strInitBuf(ctx->buf, ctx->len); + ctx->buf = NULL; + ctx->len = 0; + return str; +} + +str_t strDup(str_t ctx) { + return strInitBuf(ctx.buf, ctx.len); +} + +strview_t strGetView(str_t *ctx) { + return (strview_t) { + .buf = ctx->buf, + .len = ctx->len + }; +} + +char *strBegin(str_t *ctx) { + return ctx->buf; +} + +char *strEnd(str_t *ctx) { + return ctx->buf ? ctx->buf + ctx->len : NULL; +} + +char strBack(str_t *ctx) { + return ctx->buf ? ctx->buf[ctx->len - 1] : '\0'; +} + +bool strIsEmpty(str_t *ctx) { + return ctx->len == 0; +} + +void strAppend(str_t *ctx, const char *str) { + strAppendBuf(ctx, str, strlen(str)); +} + +void strAppendStr(str_t *ctx, str_t str) { + strAppendBuf(ctx, str.buf, str.len); +} + +void strAppendView(str_t *ctx, strview_t view) { + strAppendBuf(ctx, view.buf, view.len); +} + +void strAppendBuf(str_t *ctx, const char *buf, size_t len) { + size_t oldlen = ctx->len; + ctx->len += len; + ctx->buf = realloc(ctx->buf, ctx->len + 1); + memcpy(ctx->buf + oldlen, buf, len); + ctx->buf[ctx->len] = '\0'; +} + +void strAppendChars(str_t *ctx, char c, size_t count) { + size_t oldlen = ctx->len; + ctx->len += count; + ctx->buf = realloc(ctx->buf, ctx->len + 1); + memset(ctx->buf + oldlen, c, count); + ctx->buf[ctx->len] = '\0'; +} + +void strPush(str_t *ctx, char c) { + strAppendChars(ctx, c, 1); +} + +char strPop(str_t *ctx) { + char c = strBack(ctx); + ctx->buf = realloc(ctx->buf, ctx->len); + ctx->len -= 1; + ctx->buf[ctx->len] = '\0'; + return c; +} + +void strSwap(str_t *ctx, str_t *other) { + char *buf = other->buf; + size_t len = other->len; + other->buf = ctx->buf; + other->len = ctx->len; + ctx->buf = buf; + ctx->len = len; +} + +void strReplace(str_t *ctx, char from, char to) { + for(size_t i = 0; i < ctx->len; ++i) { + if(ctx->buf[i] == from) { + ctx->buf[i] = to; + } + } +} + +str_t strSubstr(str_t *ctx, size_t pos, size_t len) { + if(strIsEmpty(ctx)) return strInit(); + if(len == SIZE_MAX || (pos + len) > ctx->len) len = ctx->len - pos; + return strInitBuf(ctx->buf + pos, len); +} + +strview_t strSubview(str_t *ctx, size_t pos, size_t len) { + if(strIsEmpty(ctx)) return strvInit(NULL); + if(len == SIZE_MAX || (pos + len) > ctx->len) len = ctx->len - pos; + return (strview_t) { + .buf = ctx->buf + pos, + .len = len + }; +} + +void strLower(str_t *ctx) { + for(size_t i = 0; i < ctx->len; ++i) { + ctx->buf[i] = (char)tolower(ctx->buf[i]); + } +} + +str_t strToLower(str_t ctx) { + str_t str = strDup(ctx); + strLower(&str); + return str; +} + +// == STRVIEW_T ==================================================== + +strview_t strvInit(const char *cstr) { + return strvInitLen(cstr, cstr ? strlen(cstr) : 0); +} + +strview_t strvInitStr(str_t str) { + return strvInitLen(str.buf, str.len); +} + +strview_t strvInitLen(const char *buf, size_t size) { + return (strview_t) { + .buf = buf, + .len = size + }; +} + +char strvFront(strview_t ctx) { + return ctx.buf[0]; +} + +char strvBack(strview_t ctx) { + return ctx.buf[ctx.len - 1]; +} + +const char *strvBegin(strview_t *ctx) { + return ctx->buf; +} + +const char *strvEnd(strview_t *ctx) { + return ctx->buf + ctx->len; +} + +bool strvIsEmpty(strview_t ctx) { + return ctx.len == 0; +} + +void strvRemovePrefix(strview_t *ctx, size_t n) { + ctx->buf += n; + ctx->len -= n; +} + +void strvRemoveSuffix(strview_t *ctx, size_t n) { + ctx->len -= n; +} + +str_t strvCopy(strview_t ctx) { + return strInitView(ctx); +} + +str_t strvCopyN(strview_t ctx, size_t count, size_t from) { + size_t sz = ctx.len + 1 - from; + count = min(count, sz); + return strInitBuf(ctx.buf + from, count); +} + +size_t strvCopyBuf(strview_t ctx, char *buf, size_t len, size_t from) { + size_t sz = ctx.len + 1 - from; + len = min(len, sz); + memcpy(buf, ctx.buf + from, len); + buf[len - 1] = '\0'; + return len - 1; +} + +strview_t strvSubstr(strview_t ctx, size_t from, size_t len) { + if(from > ctx.len) from = ctx.len - len; + size_t sz = ctx.len - from; + return strvInitLen(ctx.buf + from, min(len, sz)); +} + +int strvCompare(strview_t ctx, strview_t other) { + if(ctx.len < other.len) return -1; + if(ctx.len > other.len) return 1; + return memcmp(ctx.buf, other.buf, ctx.len); +} + +int strvICompare(strview_t ctx, strview_t other) { + if(ctx.len < other.len) return -1; + if(ctx.len > other.len) return 1; + for(size_t i = 0; i < ctx.len; ++i) { + int a = tolower(ctx.buf[i]); + int b = tolower(other.buf[i]); + if(a != b) return a - b; + } + return 0; +} + +bool strvStartsWith(strview_t ctx, char c) { + return strvFront(ctx) == c; +} + +bool strvStartsWithView(strview_t ctx, strview_t view) { + if(ctx.len < view.len) return false; + return memcmp(ctx.buf, view.buf, view.len) == 0; +} + +bool strvEndsWith(strview_t ctx, char c) { + return strvBack(ctx) == c; +} + +bool strvEndsWithView(strview_t ctx, strview_t view) { + if(ctx.len < view.len) return false; + return memcmp(ctx.buf + ctx.len - view.len, view.buf, view.len) == 0; +} + +bool strvContains(strview_t ctx, char c) { + for(size_t i = 0; i < ctx.len; ++i) { + if(ctx.buf[i] == c) return true; + } + return false; +} + +bool strvContainsView(strview_t ctx, strview_t view) { + if(ctx.len < view.len) return false; + size_t end = ctx.len - view.len; + for(size_t i = 0; i < end; ++i) { + if(memcmp(ctx.buf + i, view.buf, view.len) == 0) return true; + } + return false; +} + +size_t strvFind(strview_t ctx, char c, size_t from) { + for(size_t i = from; i < ctx.len; ++i) { + if(ctx.buf[i] == c) return i; + } + return SIZE_MAX; +} + +size_t strvFindView(strview_t ctx, strview_t view, size_t from) { + if(ctx.len < view.len) return SIZE_MAX; + size_t end = ctx.len - view.len; + for(size_t i = from; i < end; ++i) { + if(memcmp(ctx.buf + i, view.buf, view.len) == 0) return i; + } + return SIZE_MAX; +} + +size_t strvRFind(strview_t ctx, char c, size_t from) { + if(from >= ctx.len) { + from = ctx.len - 1; + } + + const char *buf = ctx.buf + from; + for(; buf >= ctx.buf; --buf) { + if(*buf == c) return (buf - ctx.buf); + } + + return SIZE_MAX; +} + +size_t strvRFindView(strview_t ctx, strview_t view, size_t from) { + from = min(from, ctx.len); + + if(view.len > ctx.len) { + from -= view.len; + } + + const char *buf = ctx.buf + from; + for(; buf >= ctx.buf; --buf) { + if(memcmp(buf, view.buf, view.len) == 0) return (buf - ctx.buf); + } + return SIZE_MAX; +} + +size_t strvFindFirstOf(strview_t ctx, strview_t view, size_t from) { + if(ctx.len < view.len) return SIZE_MAX; + for(size_t i = from; i < ctx.len; ++i) { + for(size_t j = 0; j < view.len; ++j) { + if(ctx.buf[i] == view.buf[j]) return i; + } + } + return SIZE_MAX; +} + +size_t strvFindLastOf(strview_t ctx, strview_t view, size_t from) { + if(from >= ctx.len) { + from = ctx.len - 1; + } + + const char *buf = ctx.buf + from; + for(; buf >= ctx.buf; --buf) { + for(size_t j = 0; j < view.len; ++j) { + if(*buf == view.buf[j]) return (buf - ctx.buf); + } + } + + return SIZE_MAX; +} + +size_t strvFindFirstNot(strview_t ctx, char c, size_t from) { + size_t end = ctx.len - 1; + for(size_t i = from; i < end; ++i) { + if(ctx.buf[i] != c) return i; + } + return SIZE_MAX; +} + +size_t strvFindFirstNotOf(strview_t ctx, strview_t view, size_t from) { + for(size_t i = from; i < ctx.len; ++i) { + if(!strvContains(view, ctx.buf[i])) { + return i; + } + } + return SIZE_MAX; +} + +size_t strvFindLastNot(strview_t ctx, char c, size_t from) { + if(from >= ctx.len) { + from = ctx.len - 1; + } + + const char *buf = ctx.buf + from; + for(; buf >= ctx.buf; --buf) { + if(*buf != c) { + return buf - ctx.buf; + } + } + + return SIZE_MAX; +} + +size_t strvFindLastNotOf(strview_t ctx, strview_t view, size_t from) { + if(from >= ctx.len) { + from = ctx.len - 1; + } + + const char *buf = ctx.buf + from; + for(; buf >= ctx.buf; --buf) { + if(!strvContains(view, *buf)) { + return buf - ctx.buf; + } + } + + return SIZE_MAX; +} + +#ifdef STR_TESTING +#include +#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 \ No newline at end of file diff --git a/str.h b/str.h index 5f2de2c..305d637 100644 --- a/str.h +++ b/str.h @@ -1,63 +1,127 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include "slice.h" -#include "strview.h" - -#define STR_TESTING - -// typedef struct { -// char *buf; -// size_t len; -// } str_t; - -typedef slice_t(char *) str_t; - -str_t strInit(void); -str_t strInitStr(const char *cstr); -str_t strInitView(strview_t view); -str_t strInitBuf(const char *buf, size_t len); - -void strFree(str_t *ctx); - -str_t strMove(str_t *ctx); -str_t strDup(str_t ctx); - -strview_t strGetView(str_t *ctx); - -char *strBegin(str_t *ctx); -char *strEnd(str_t *ctx); - -char strBack(str_t *ctx); - -bool strIsEmpty(str_t *ctx); - -void strAppend(str_t *ctx, const char *str); -void strAppendStr(str_t *ctx, str_t str); -void strAppendView(str_t *ctx, strview_t view); -void strAppendBuf(str_t *ctx, const char *buf, size_t len); -void strAppendChars(str_t *ctx, char c, size_t count); - -void strPush(str_t *ctx, char c); -char strPop(str_t *ctx); - -void strSwap(str_t *ctx, str_t *other); - -// 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 - -#ifdef __cplusplus -} // extern "C" +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include +// #include "strview.h" + +#define STRV_NOT_FOUND SIZE_MAX + +typedef struct { + char *buf; + size_t len; +} str_t; + +typedef struct { + const char *buf; + size_t len; +} strview_t; + +// == STR_T ======================================================== + +str_t strInit(void); +str_t strInitStr(const char *cstr); +str_t strInitView(strview_t view); +str_t strInitBuf(const char *buf, size_t len); + +str_t strFromWCHAR(const wchar_t *src, size_t len); +wchar_t *strToWCHAR(str_t ctx); + +void strFree(str_t *ctx); + +str_t strMove(str_t *ctx); +str_t strDup(str_t ctx); + +strview_t strGetView(str_t *ctx); + +char *strBegin(str_t *ctx); +char *strEnd(str_t *ctx); + +char strBack(str_t *ctx); + +bool strIsEmpty(str_t *ctx); + +void strAppend(str_t *ctx, const char *str); +void strAppendStr(str_t *ctx, str_t str); +void strAppendView(str_t *ctx, strview_t view); +void strAppendBuf(str_t *ctx, const char *buf, size_t len); +void strAppendChars(str_t *ctx, char c, size_t count); + +void strPush(str_t *ctx, char c); +char strPop(str_t *ctx); + +void strSwap(str_t *ctx, str_t *other); + +void strReplace(str_t *ctx, char from, char to); + +// if len == SIZE_MAX, copies until end +str_t strSubstr(str_t *ctx, size_t pos, size_t len); +// if len == SIZE_MAX, returns until end +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 \ No newline at end of file diff --git a/strstream.c b/strstream.c index f238bda..76f56e8 100644 --- a/strstream.c +++ b/strstream.c @@ -1,513 +1,543 @@ -#include "strstream.h" - -#include -#include -#include -#include -#include -#include // HUGE_VALF -#include "tracelog.h" - -/* == INPUT STREAM ============================================ */ - -str_istream_t istrInit(const char *str) { - return istrInitLen(str, strlen(str)); -} - -str_istream_t istrInitLen(const char *str, size_t len) { - str_istream_t res; - res.start = res.cur = str; - res.size = len; - return res; -} - -char istrGet(str_istream_t *ctx) { - return *ctx->cur++; -} - -void istrIgnore(str_istream_t *ctx, char delim) { - size_t position = ctx->cur - ctx->start; - size_t i; - for(i = position; - i < ctx->size && *ctx->cur != delim; - ++i, ++ctx->cur); -} - -char istrPeek(str_istream_t *ctx) { - return *ctx->cur; -} - -void istrSkip(str_istream_t *ctx, size_t n) { - size_t remaining = ctx->size - (ctx->cur - ctx->start); - if(n > remaining) { - warn("skipping more then remaining: %zu -> %zu", n, remaining); - return; - } - ctx->cur += n; -} - -void istrRead(str_istream_t *ctx, char *buf, size_t len) { - size_t remaining = ctx->size - (ctx->cur - ctx->start); - if(len > remaining) { - warn("istrRead: trying to read len %zu from remaining %zu", len, remaining); - return; - } - memcpy(buf, ctx->cur, len); - ctx->cur += len; -} - -size_t istrReadMax(str_istream_t *ctx, char *buf, size_t len) { - size_t remaining = ctx->size - (ctx->cur - ctx->start); - len = remaining < len ? remaining : len; - memcpy(buf, ctx->cur, len); - ctx->cur += len; - return len; -} - -void istrRewind(str_istream_t *ctx) { - ctx->cur = ctx->start; -} - -size_t istrTell(str_istream_t *ctx) { - return ctx->cur - ctx->start; -} - -bool istrIsFinished(str_istream_t *ctx) { - return ctx->cur == (ctx->start + ctx->size + 1); -} - -bool istrGetbool(str_istream_t *ctx, bool *val) { - size_t remaining = ctx->size - (ctx->cur - ctx->start); - if(strncmp(ctx->cur, "true", remaining) == 0) { - *val = true; - return true; - } - if(strncmp(ctx->cur, "false", remaining) == 0) { - *val = false; - return true; - } - return false; -} - -bool istrGetu8(str_istream_t *ctx, uint8_t *val) { - char *end = NULL; - *val = (uint8_t) strtoul(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGetu8: no valid conversion could be performed"); - return false; - } - else if(*val == UINT8_MAX) { - warn("istrGetu8: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGetu16(str_istream_t *ctx, uint16_t *val) { - char *end = NULL; - *val = (uint16_t) strtoul(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGetu16: no valid conversion could be performed"); - return false; - } - else if(*val == UINT16_MAX) { - warn("istrGetu16: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGetu32(str_istream_t *ctx, uint32_t *val) { - char *end = NULL; - *val = (uint32_t) strtoul(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGetu32: no valid conversion could be performed"); - return false; - } - else if(*val == UINT32_MAX) { - warn("istrGetu32: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGetu64(str_istream_t *ctx, uint64_t *val) { - char *end = NULL; - *val = strtoull(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGetu64: no valid conversion could be performed"); - return false; - } - else if(*val == ULLONG_MAX) { - warn("istrGetu64: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGeti8(str_istream_t *ctx, int8_t *val) { - char *end = NULL; - *val = (int8_t) strtol(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGeti8: no valid conversion could be performed"); - return false; - } - else if(*val == INT8_MAX || *val == INT8_MIN) { - warn("istrGeti8: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGeti16(str_istream_t *ctx, int16_t *val) { - char *end = NULL; - *val = (int16_t) strtol(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGeti16: no valid conversion could be performed"); - return false; - } - else if(*val == INT16_MAX || *val == INT16_MIN) { - warn("istrGeti16: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGeti32(str_istream_t *ctx, int32_t *val) { - char *end = NULL; - *val = (int32_t) strtol(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGeti32: no valid conversion could be performed"); - return false; - } - else if(*val == INT32_MAX || *val == INT32_MIN) { - warn("istrGeti32: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGeti64(str_istream_t *ctx, int64_t *val) { - char *end = NULL; - *val = strtoll(ctx->cur, &end, 0); - - if(ctx->cur == end) { - warn("istrGeti64: no valid conversion could be performed"); - return false; - } - else if(*val == INT64_MAX || *val == INT64_MIN) { - warn("istrGeti64: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGetfloat(str_istream_t *ctx, float *val) { - char *end = NULL; - *val = strtof(ctx->cur, &end); - - if(ctx->cur == end) { - warn("istrGetfloat: no valid conversion could be performed"); - return false; - } - else if(*val == HUGE_VALF || *val == -HUGE_VALF) { - warn("istrGetfloat: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -bool istrGetdouble(str_istream_t *ctx, double *val) { - char *end = NULL; - *val = strtod(ctx->cur, &end); - - if(ctx->cur == end) { - warn("istrGetdouble: no valid conversion could be performed"); - return false; - } - else if(*val == HUGE_VAL || *val == -HUGE_VAL) { - warn("istrGetdouble: value read is out of the range of representable values"); - return false; - } - - ctx->cur = end; - return true; -} - -size_t istrGetstring(str_istream_t *ctx, char **val, char delim) { - const char *from = ctx->cur; - istrIgnore(ctx, delim); - // if it didn't actually find it, it just reached the end of the string - if(*ctx->cur != delim) { - *val = NULL; - return 0; - } - size_t len = ctx->cur - from; - *val = malloc(len + 1); - memcpy(*val, from, len); - (*val)[len] = '\0'; - return len; -} - -size_t istrGetstringBuf(str_istream_t *ctx, char *val, size_t len) { - size_t remaining = ctx->size - (ctx->cur - ctx->start); - len -= 1; - len = remaining < len ? remaining : len; - memcpy(val, ctx->cur, len); - val[len] = '\0'; - ctx->cur += len; - return len; -} - -strview_t istrGetview(str_istream_t *ctx, char delim) { - const char *from = ctx->cur; - istrIgnore(ctx, delim); - // if it didn't actually find it, it just reached the end of the string - if(*ctx->cur != delim) { - return strvInitLen(NULL, 0); - } - size_t len = ctx->cur - from; - return strvInitLen(from, len); -} - -strview_t istrGetviewLen(str_istream_t *ctx, size_t off, size_t len) { - size_t remaining = ctx->size - (ctx->cur - ctx->start) - off; - if(len > remaining) len = remaining; - return strvInitLen(ctx->cur + off, len); -} - -/* == OUTPUT STREAM =========================================== */ - -static void _ostrRealloc(str_ostream_t *ctx, size_t atlest) { - ctx->allocated = (ctx->allocated * 2) + atlest; - ctx->buf = realloc(ctx->buf, ctx->allocated); -} - -str_ostream_t ostrInit() { - return ostrInitLen(1); -} - -str_ostream_t ostrInitLen(size_t initial_alloc) { - str_ostream_t stream; - stream.buf = calloc(initial_alloc, 1); - stream.size = 0; - stream.allocated = initial_alloc; - return stream; -} - -str_ostream_t ostrInitStr(const char *cstr, size_t len) { - str_ostream_t stream; - stream.buf = malloc(len + 1); - memcpy(stream.buf, cstr, len); - stream.size = len; - stream.allocated = len + 1; - return stream; -} - -char ostrBack(str_ostream_t *ctx) { - return ctx->buf[ctx->size - 1]; -} - -void ostrFree(str_ostream_t *ctx) { - free(ctx->buf); - ctx->size = 0; - ctx->allocated = 0; -} - -size_t ostrMove(str_ostream_t *ctx, char **str) { - *str = ctx->buf; - ctx->buf = NULL; - size_t sz = ctx->size; - ostrFree(ctx); - return sz; -} - -void ostrPrintf(str_ostream_t *ctx, const char *fmt, ...) { - va_list va; - va_start(va, fmt); - - // vsnprintf returns the length of the formatted string, even if truncated - // we use this to get the actual length of the formatted string - char buf[1]; - va_list vtemp; - va_copy(vtemp, va); - int len = vsnprintf(buf, sizeof(buf), fmt, vtemp); - va_end(vtemp); - if(len < 0) { - err("couldn't format string \"%s\"", fmt); - goto error; - } - - size_t remaining = ctx->allocated - ctx->size; - if(remaining < (size_t)len) { - _ostrRealloc(ctx, len + 1); - remaining = ctx->allocated - ctx->size; - } - - // actual formatting here - len = vsnprintf(ctx->buf + ctx->size, remaining, fmt, va); - if(len < 0) { - err("couldn't format stringh \"%s\"", fmt); - goto error; - } - ctx->size += len; - -error: - va_end(va); -} - -#define APPEND_BUF_LEN 20 - -void ostrPutc(str_ostream_t *ctx, char c) { - if(ctx->size >= ctx->allocated) { - _ostrRealloc(ctx, 1); - } - ctx->buf[ctx->size++] = c; - ctx->buf[ctx->size] = '\0'; -} - -void ostrAppendbool(str_ostream_t *ctx, bool val) { - ostrAppendview(ctx, strvInit(val ? "true" : "false")); -} - -void ostrAppendu8(str_ostream_t *ctx, uint8_t val) { - char buf[APPEND_BUF_LEN]; - int len = snprintf(buf, sizeof(buf), "%hhu", val); - if(len <= 0) { - err("ostrAppendu8: couldn't write %hhu", val); - return; - } - ostrAppendview(ctx, strvInitLen(buf, len)); -} - -void ostrAppendu16(str_ostream_t *ctx, uint16_t val) { - char buf[APPEND_BUF_LEN]; - int len = snprintf(buf, sizeof(buf), "%hu", val); - if(len <= 0) { - err("ostrAppendu16: couldn't write %hu", val); - return; - } - ostrAppendview(ctx, strvInitLen(buf, len)); -} - -void ostrAppendu32(str_ostream_t *ctx, uint32_t val) { - char buf[APPEND_BUF_LEN]; - int len = snprintf(buf, sizeof(buf), "%u", val); - if(len <= 0) { - err("ostrAppendu32: couldn't write %u", val); - return; - } - ostrAppendview(ctx, strvInitLen(buf, len)); -} - -void ostrAppendu64(str_ostream_t *ctx, uint64_t val) { - char buf[APPEND_BUF_LEN]; -#if _WIN32 - int len = snprintf(buf, sizeof(buf), "%llu", val); -#else - int len = snprintf(buf, sizeof(buf), "%lu", val); -#endif - if(len <= 0) { - err("ostrAppendu64: couldn't write %lu", val); - return; - } - ostrAppendview(ctx, strvInitLen(buf, len)); -} - -void ostrAppendi8(str_ostream_t *ctx, int8_t val) { - char buf[APPEND_BUF_LEN]; - int len = snprintf(buf, sizeof(buf), "%hhi", val); - if(len <= 0) { - err("ostrAppendi8: couldn't write %hhi", val); - return; - } - ostrAppendview(ctx, strvInitLen(buf, len)); -} - -void ostrAppendi16(str_ostream_t *ctx, int16_t val) { - char buf[APPEND_BUF_LEN]; - int len = snprintf(buf, sizeof(buf), "%hi", val); - if(len <= 0) { - err("ostrAppendi16: couldn't write %hi", val); - return; - } - ostrAppendview(ctx, strvInitLen(buf, len)); -} - -void ostrAppendi32(str_ostream_t *ctx, int32_t val) { - char buf[APPEND_BUF_LEN]; - int len = snprintf(buf, sizeof(buf), "%i", val); - if(len <= 0) { - err("ostrAppendi32: couldn't write %i", val); - return; - } - ostrAppendview(ctx, strvInitLen(buf, len)); -} - -void ostrAppendi64(str_ostream_t *ctx, int64_t val) { - char buf[APPEND_BUF_LEN]; -#if _WIN32 - int len = snprintf(buf, sizeof(buf), "%lli", val); -#else - int len = snprintf(buf, sizeof(buf), "%li", val); -#endif - if(len <= 0) { - err("ostrAppendi64: couldn't write %li", val); - return; - } - ostrAppendview(ctx, strvInitLen(buf, len)); -} - -void ostrAppendfloat(str_ostream_t *ctx, float val) { - char buf[APPEND_BUF_LEN * 3]; - int len = snprintf(buf, sizeof(buf), "%g", (double)val); - if(len <= 0) { - err("ostrAppendfloat: couldn't write %g", (double)val); - return; - } - ostrAppendview(ctx, strvInitLen(buf, len)); -} - -void ostrAppenddouble(str_ostream_t *ctx, double val) { - char buf[APPEND_BUF_LEN * 3]; - int len = snprintf(buf, sizeof(buf), "%g", val); - if(len <= 0) { - err("ostrAppenddouble: couldn't write %g", val); - return; - } - ostrAppendview(ctx, strvInitLen(buf, len)); -} - -void ostrAppendview(str_ostream_t *ctx, strview_t view) { - if((ctx->allocated - ctx->size) < view.len) { - _ostrRealloc(ctx, view.len + 1); - } - memcpy(ctx->buf + ctx->size, view.buf, view.len); - ctx->size += view.len; - ctx->buf[ctx->size] = '\0'; +#include "strstream.h" + +#include +#include +#include +#include +#include +#include // HUGE_VALF +#include "tracelog.h" + +/* == INPUT STREAM ============================================ */ + +str_istream_t istrInit(const char *str) { + return istrInitLen(str, strlen(str)); +} + +str_istream_t istrInitLen(const char *str, size_t len) { + str_istream_t res; + res.start = res.cur = str; + res.size = len; + return res; +} + +char istrGet(str_istream_t *ctx) { + return *ctx->cur++; +} + +void istrIgnore(str_istream_t *ctx, char delim) { + size_t position = ctx->cur - ctx->start; + size_t i; + for(i = position; + i < ctx->size && *ctx->cur != delim; + ++i, ++ctx->cur); +} + +char istrPeek(str_istream_t *ctx) { + return *ctx->cur; +} + +void istrSkip(str_istream_t *ctx, size_t n) { + size_t remaining = ctx->size - (ctx->cur - ctx->start); + if(n > remaining) { + warn("skipping more then remaining: %zu -> %zu", n, remaining); + return; + } + ctx->cur += n; +} + +void istrRead(str_istream_t *ctx, char *buf, size_t len) { + size_t remaining = ctx->size - (ctx->cur - ctx->start); + if(len > remaining) { + warn("istrRead: trying to read len %zu from remaining %zu", len, remaining); + return; + } + memcpy(buf, ctx->cur, len); + ctx->cur += len; +} + +size_t istrReadMax(str_istream_t *ctx, char *buf, size_t len) { + size_t remaining = ctx->size - (ctx->cur - ctx->start); + len = remaining < len ? remaining : len; + memcpy(buf, ctx->cur, len); + ctx->cur += len; + return len; +} + +void istrRewind(str_istream_t *ctx) { + ctx->cur = ctx->start; +} + +size_t istrTell(str_istream_t *ctx) { + return ctx->cur - ctx->start; +} + +bool istrIsFinished(str_istream_t *ctx) { + return ctx->cur == (ctx->start + ctx->size + 1); +} + +bool istrGetbool(str_istream_t *ctx, bool *val) { + size_t remaining = ctx->size - (ctx->cur - ctx->start); + if(strncmp(ctx->cur, "true", remaining) == 0) { + *val = true; + return true; + } + if(strncmp(ctx->cur, "false", remaining) == 0) { + *val = false; + return true; + } + return false; +} + +bool istrGetu8(str_istream_t *ctx, uint8_t *val) { + char *end = NULL; + *val = (uint8_t) strtoul(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGetu8: no valid conversion could be performed"); + return false; + } + else if(*val == UINT8_MAX) { + warn("istrGetu8: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGetu16(str_istream_t *ctx, uint16_t *val) { + char *end = NULL; + *val = (uint16_t) strtoul(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGetu16: no valid conversion could be performed"); + return false; + } + else if(*val == UINT16_MAX) { + warn("istrGetu16: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGetu32(str_istream_t *ctx, uint32_t *val) { + char *end = NULL; + *val = (uint32_t) strtoul(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGetu32: no valid conversion could be performed"); + return false; + } + else if(*val == UINT32_MAX) { + warn("istrGetu32: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGetu64(str_istream_t *ctx, uint64_t *val) { + char *end = NULL; + *val = strtoull(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGetu64: no valid conversion could be performed"); + return false; + } + else if(*val == ULLONG_MAX) { + warn("istrGetu64: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGeti8(str_istream_t *ctx, int8_t *val) { + char *end = NULL; + *val = (int8_t) strtol(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGeti8: no valid conversion could be performed"); + return false; + } + else if(*val == INT8_MAX || *val == INT8_MIN) { + warn("istrGeti8: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGeti16(str_istream_t *ctx, int16_t *val) { + char *end = NULL; + *val = (int16_t) strtol(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGeti16: no valid conversion could be performed"); + return false; + } + else if(*val == INT16_MAX || *val == INT16_MIN) { + warn("istrGeti16: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGeti32(str_istream_t *ctx, int32_t *val) { + char *end = NULL; + *val = (int32_t) strtol(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGeti32: no valid conversion could be performed"); + return false; + } + else if(*val == INT32_MAX || *val == INT32_MIN) { + warn("istrGeti32: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGeti64(str_istream_t *ctx, int64_t *val) { + char *end = NULL; + *val = strtoll(ctx->cur, &end, 0); + + if(ctx->cur == end) { + warn("istrGeti64: no valid conversion could be performed"); + return false; + } + else if(*val == INT64_MAX || *val == INT64_MIN) { + warn("istrGeti64: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGetfloat(str_istream_t *ctx, float *val) { + char *end = NULL; + *val = strtof(ctx->cur, &end); + + if(ctx->cur == end) { + warn("istrGetfloat: no valid conversion could be performed"); + return false; + } + else if(*val == HUGE_VALF || *val == -HUGE_VALF) { + warn("istrGetfloat: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +bool istrGetdouble(str_istream_t *ctx, double *val) { + char *end = NULL; + *val = strtod(ctx->cur, &end); + + if(ctx->cur == end) { + warn("istrGetdouble: no valid conversion could be performed"); + return false; + } + else if(*val == HUGE_VAL || *val == -HUGE_VAL) { + warn("istrGetdouble: value read is out of the range of representable values"); + return false; + } + + ctx->cur = end; + return true; +} + +size_t istrGetstring(str_istream_t *ctx, char **val, char delim) { + const char *from = ctx->cur; + istrIgnore(ctx, delim); + // if it didn't actually find it, it just reached the end of the string + if(*ctx->cur != delim) { + *val = NULL; + return 0; + } + size_t len = ctx->cur - from; + *val = malloc(len + 1); + memcpy(*val, from, len); + (*val)[len] = '\0'; + return len; +} + +size_t istrGetstringBuf(str_istream_t *ctx, char *val, size_t len) { + size_t remaining = ctx->size - (ctx->cur - ctx->start); + len -= 1; + len = remaining < len ? remaining : len; + memcpy(val, ctx->cur, len); + val[len] = '\0'; + ctx->cur += len; + return len; +} + +strview_t istrGetview(str_istream_t *ctx, char delim) { + const char *from = ctx->cur; + istrIgnore(ctx, delim); + // if it didn't actually find it, it just reached the end of the string + if(*ctx->cur != delim) { + return strvInitLen(NULL, 0); + } + size_t len = ctx->cur - from; + return strvInitLen(from, len); +} + +strview_t istrGetviewLen(str_istream_t *ctx, size_t off, size_t len) { + size_t remaining = ctx->size - (ctx->cur - ctx->start) - off; + if(len > remaining) len = remaining; + return strvInitLen(ctx->cur + off, len); +} + +/* == OUTPUT STREAM =========================================== */ + +static void _ostrRealloc(str_ostream_t *ctx, size_t needed) { + ctx->allocated = (ctx->allocated * 2) + needed; + ctx->buf = realloc(ctx->buf, ctx->allocated); +} + +str_ostream_t ostrInit() { + return ostrInitLen(1); +} + +str_ostream_t ostrInitLen(size_t initial_alloc) { + str_ostream_t stream; + stream.buf = calloc(initial_alloc, 1); + stream.size = 0; + stream.allocated = initial_alloc; + return stream; +} + +str_ostream_t ostrInitStr(const char *cstr, size_t len) { + str_ostream_t stream; + stream.buf = malloc(len + 1); + memcpy(stream.buf, cstr, len); + stream.size = len; + stream.allocated = len + 1; + return stream; +} + +void ostrFree(str_ostream_t *ctx) { + free(ctx->buf); + ctx->buf = NULL; + ctx->size = 0; + ctx->allocated = 0; +} + +void ostrClear(str_ostream_t *ctx) { + ctx->size = 0; +} + +str_t ostrMove(str_ostream_t *ctx) { + str_t str = ostrAsStr(ctx); + *ctx = (str_ostream_t){0}; + return str; +} + +char ostrBack(str_ostream_t *ctx) { + if(ctx->size == 0) return '\0'; + return ctx->buf[ctx->size - 1]; +} + +str_t ostrAsStr(str_ostream_t *ctx) { + return (str_t) { + .buf = ctx->buf, + .len = ctx->size + }; +} + +strview_t ostrAsView(str_ostream_t *ctx) { + return (strview_t) { + .buf = ctx->buf, + .len = ctx->size + }; +} + +void ostrReplace(str_ostream_t *ctx, char from, char to) { + for(size_t i = 0; i < ctx->size; ++i) { + if(ctx->buf[i] == from) { + ctx->buf[i] = to; + } + } +} + +void ostrPrintf(str_ostream_t *ctx, const char *fmt, ...) { + va_list va; + va_start(va, fmt); + + // vsnprintf returns the length of the formatted string, even if truncated + // we use this to get the actual length of the formatted string + char buf[1]; + va_list vtemp; + va_copy(vtemp, va); + int len = vsnprintf(buf, sizeof(buf), fmt, vtemp); + va_end(vtemp); + if(len < 0) { + err("couldn't format string \"%s\"", fmt); + goto error; + } + + size_t remaining = ctx->allocated - ctx->size; + if(remaining < (size_t)len) { + _ostrRealloc(ctx, len + 1); + remaining = ctx->allocated - ctx->size; + } + + // actual formatting here + len = vsnprintf(ctx->buf + ctx->size, remaining, fmt, va); + if(len < 0) { + err("couldn't format stringh \"%s\"", fmt); + goto error; + } + ctx->size += len; + +error: + va_end(va); +} + +#define APPEND_BUF_LEN 20 + +void ostrPutc(str_ostream_t *ctx, char c) { + ostrAppendchar(ctx, c); +} + +void ostrAppendbool(str_ostream_t *ctx, bool val) { + ostrAppendview(ctx, strvInit(val ? "true" : "false")); +} + +void ostrAppendchar(str_ostream_t *ctx, char val) { + if(ctx->size >= ctx->allocated) { + _ostrRealloc(ctx, 1); + } + ctx->buf[ctx->size++] = val; + ctx->buf[ctx->size] = '\0'; +} + +void ostrAppendu8(str_ostream_t *ctx, uint8_t val) { + char buf[APPEND_BUF_LEN]; + int len = snprintf(buf, sizeof(buf), "%hhu", val); + if(len <= 0) { + err("ostrAppendu8: couldn't write %hhu", val); + return; + } + ostrAppendview(ctx, strvInitLen(buf, len)); +} + +void ostrAppendu16(str_ostream_t *ctx, uint16_t val) { + char buf[APPEND_BUF_LEN]; + int len = snprintf(buf, sizeof(buf), "%hu", val); + if(len <= 0) { + err("ostrAppendu16: couldn't write %hu", val); + return; + } + ostrAppendview(ctx, strvInitLen(buf, len)); +} + +void ostrAppendu32(str_ostream_t *ctx, uint32_t val) { + char buf[APPEND_BUF_LEN]; + int len = snprintf(buf, sizeof(buf), "%u", val); + if(len <= 0) { + err("ostrAppendu32: couldn't write %u", val); + return; + } + ostrAppendview(ctx, strvInitLen(buf, len)); +} + +void ostrAppendu64(str_ostream_t *ctx, uint64_t val) { + char buf[APPEND_BUF_LEN]; +#if _WIN32 + int len = snprintf(buf, sizeof(buf), "%llu", val); +#else + int len = snprintf(buf, sizeof(buf), "%lu", val); +#endif + if(len <= 0) { + err("ostrAppendu64: couldn't write %lu", val); + return; + } + ostrAppendview(ctx, strvInitLen(buf, len)); +} + +void ostrAppendi8(str_ostream_t *ctx, int8_t val) { + char buf[APPEND_BUF_LEN]; + int len = snprintf(buf, sizeof(buf), "%hhi", val); + if(len <= 0) { + err("ostrAppendi8: couldn't write %hhi", val); + return; + } + ostrAppendview(ctx, strvInitLen(buf, len)); +} + +void ostrAppendi16(str_ostream_t *ctx, int16_t val) { + char buf[APPEND_BUF_LEN]; + int len = snprintf(buf, sizeof(buf), "%hi", val); + if(len <= 0) { + err("ostrAppendi16: couldn't write %hi", val); + return; + } + ostrAppendview(ctx, strvInitLen(buf, len)); +} + +void ostrAppendi32(str_ostream_t *ctx, int32_t val) { + char buf[APPEND_BUF_LEN]; + int len = snprintf(buf, sizeof(buf), "%i", val); + if(len <= 0) { + err("ostrAppendi32: couldn't write %i", val); + return; + } + ostrAppendview(ctx, strvInitLen(buf, len)); +} + +void ostrAppendi64(str_ostream_t *ctx, int64_t val) { + char buf[APPEND_BUF_LEN]; +#if _WIN32 + int len = snprintf(buf, sizeof(buf), "%lli", val); +#else + int len = snprintf(buf, sizeof(buf), "%li", val); +#endif + if(len <= 0) { + err("ostrAppendi64: couldn't write %li", val); + return; + } + ostrAppendview(ctx, strvInitLen(buf, len)); +} + +void ostrAppendfloat(str_ostream_t *ctx, float val) { + char buf[APPEND_BUF_LEN * 3]; + int len = snprintf(buf, sizeof(buf), "%g", (double)val); + if(len <= 0) { + err("ostrAppendfloat: couldn't write %g", (double)val); + return; + } + ostrAppendview(ctx, strvInitLen(buf, len)); +} + +void ostrAppenddouble(str_ostream_t *ctx, double val) { + char buf[APPEND_BUF_LEN * 3]; + int len = snprintf(buf, sizeof(buf), "%g", val); + if(len <= 0) { + err("ostrAppenddouble: couldn't write %g", val); + return; + } + ostrAppendview(ctx, strvInitLen(buf, len)); +} + +void ostrAppendview(str_ostream_t *ctx, strview_t view) { + if((ctx->allocated - ctx->size) < view.len) { + _ostrRealloc(ctx, view.len + 1); + } + memcpy(ctx->buf + ctx->size, view.buf, view.len); + ctx->size += view.len; + ctx->buf[ctx->size] = '\0'; } \ No newline at end of file diff --git a/strstream.h b/strstream.h index cbb6552..b6ba1df 100644 --- a/strstream.h +++ b/strstream.h @@ -1,97 +1,104 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include - -#include "strview.h" - -/* == INPUT STREAM ============================================ */ - -typedef struct { - const char *start; - const char *cur; - size_t size; -} str_istream_t; - -// initialize with null-terminated string -str_istream_t istrInit(const char *str); -str_istream_t istrInitLen(const char *str, size_t len); - -// get the current character and advance -char istrGet(str_istream_t *ctx); -// get the current character but don't advance -char istrPeek(str_istream_t *ctx); -// ignore characters until the delimiter -void istrIgnore(str_istream_t *ctx, char delim); -// skip n characters -void istrSkip(str_istream_t *ctx, size_t n); -// read len bytes into buffer, the buffer will not be null terminated -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 -// returns the number of bytes read -size_t istrReadMax(str_istream_t *ctx, char *buf, size_t len); -// return to the beginning of the stream -void istrRewind(str_istream_t *ctx); -// return the number of bytes read from beginning of stream -size_t istrTell(str_istream_t *ctx); -// return true if the stream doesn't have any new bytes to read -bool istrIsFinished(str_istream_t *ctx); - -bool istrGetbool(str_istream_t *ctx, bool *val); -bool istrGetu8(str_istream_t *ctx, uint8_t *val); -bool istrGetu16(str_istream_t *ctx, uint16_t *val); -bool istrGetu32(str_istream_t *ctx, uint32_t *val); -bool istrGetu64(str_istream_t *ctx, uint64_t *val); -bool istrGeti8(str_istream_t *ctx, int8_t *val); -bool istrGeti16(str_istream_t *ctx, int16_t *val); -bool istrGeti32(str_istream_t *ctx, int32_t *val); -bool istrGeti64(str_istream_t *ctx, int64_t *val); -bool istrGetfloat(str_istream_t *ctx, float *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 -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 -size_t istrGetstringBuf(str_istream_t *ctx, char *val, size_t len); -strview_t istrGetview(str_istream_t *ctx, char delim); -strview_t istrGetviewLen(str_istream_t *ctx, size_t off, size_t len); - -/* == OUTPUT STREAM =========================================== */ - -typedef struct { - char *buf; - size_t size; - size_t allocated; -} str_ostream_t; - -str_ostream_t ostrInit(void); -str_ostream_t ostrInitLen(size_t initial_alloc); -str_ostream_t ostrInitStr(const char *buf, size_t len); - -void ostrFree(str_ostream_t *ctx); -size_t ostrMove(str_ostream_t *ctx, char **str); - -void ostrPrintf(str_ostream_t *ctx, const char *fmt, ...); - -void ostrPutc(str_ostream_t *ctx, char c); - -void ostrAppendbool(str_ostream_t *ctx, bool val); -void ostrAppendu8(str_ostream_t *ctx, uint8_t val); -void ostrAppendu16(str_ostream_t *ctx, uint16_t val); -void ostrAppendu32(str_ostream_t *ctx, uint32_t val); -void ostrAppendu64(str_ostream_t *ctx, uint64_t val); -void ostrAppendi8(str_ostream_t *ctx, int8_t val); -void ostrAppendi16(str_ostream_t *ctx, int16_t val); -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" +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include "str.h" + +/* == INPUT STREAM ============================================ */ + +typedef struct { + const char *start; + const char *cur; + size_t size; +} str_istream_t; + +// initialize with null-terminated string +str_istream_t istrInit(const char *str); +str_istream_t istrInitLen(const char *str, size_t len); + +// get the current character and advance +char istrGet(str_istream_t *ctx); +// get the current character but don't advance +char istrPeek(str_istream_t *ctx); +// ignore characters until the delimiter +void istrIgnore(str_istream_t *ctx, char delim); +// skip n characters +void istrSkip(str_istream_t *ctx, size_t n); +// read len bytes into buffer, the buffer will not be null terminated +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 +// returns the number of bytes read +size_t istrReadMax(str_istream_t *ctx, char *buf, size_t len); +// return to the beginning of the stream +void istrRewind(str_istream_t *ctx); +// return the number of bytes read from beginning of stream +size_t istrTell(str_istream_t *ctx); +// return true if the stream doesn't have any new bytes to read +bool istrIsFinished(str_istream_t *ctx); + +bool istrGetbool(str_istream_t *ctx, bool *val); +bool istrGetu8(str_istream_t *ctx, uint8_t *val); +bool istrGetu16(str_istream_t *ctx, uint16_t *val); +bool istrGetu32(str_istream_t *ctx, uint32_t *val); +bool istrGetu64(str_istream_t *ctx, uint64_t *val); +bool istrGeti8(str_istream_t *ctx, int8_t *val); +bool istrGeti16(str_istream_t *ctx, int16_t *val); +bool istrGeti32(str_istream_t *ctx, int32_t *val); +bool istrGeti64(str_istream_t *ctx, int64_t *val); +bool istrGetfloat(str_istream_t *ctx, float *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 +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 +size_t istrGetstringBuf(str_istream_t *ctx, char *val, size_t len); +strview_t istrGetview(str_istream_t *ctx, char delim); +strview_t istrGetviewLen(str_istream_t *ctx, size_t off, size_t len); + +/* == OUTPUT STREAM =========================================== */ + +typedef struct { + char *buf; + size_t size; + size_t allocated; +} str_ostream_t; + +str_ostream_t ostrInit(void); +str_ostream_t ostrInitLen(size_t initial_alloc); +str_ostream_t ostrInitStr(const char *buf, size_t len); + +void ostrFree(str_ostream_t *ctx); +void ostrClear(str_ostream_t *ctx); +str_t ostrMove(str_ostream_t *ctx); + +char ostrBack(str_ostream_t *ctx); +str_t ostrAsStr(str_ostream_t *ctx); +strview_t ostrAsView(str_ostream_t *ctx); + +void ostrReplace(str_ostream_t *ctx, char from, char to); + +void ostrPrintf(str_ostream_t *ctx, const char *fmt, ...); +void ostrPutc(str_ostream_t *ctx, char c); + +void ostrAppendbool(str_ostream_t *ctx, bool val); +void ostrAppendchar(str_ostream_t *ctx, char val); +void ostrAppendu8(str_ostream_t *ctx, uint8_t val); +void ostrAppendu16(str_ostream_t *ctx, uint16_t val); +void ostrAppendu32(str_ostream_t *ctx, uint32_t val); +void ostrAppendu64(str_ostream_t *ctx, uint64_t val); +void ostrAppendi8(str_ostream_t *ctx, int8_t val); +void ostrAppendi16(str_ostream_t *ctx, int16_t val); +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 \ No newline at end of file diff --git a/strview.c b/strview.c deleted file mode 100644 index ad8a261..0000000 --- a/strview.c +++ /dev/null @@ -1,250 +0,0 @@ -#include "strview.h" - -#include -#include -#include -#include - -#include "tracelog.h" -#include - -#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; -} diff --git a/strview.h b/strview.h deleted file mode 100644 index 8ca7def..0000000 --- a/strview.h +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include -#include - -#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 \ No newline at end of file diff --git a/tracelog.c b/tracelog.c index f358eaf..43a84f7 100644 --- a/tracelog.c +++ b/tracelog.c @@ -1,91 +1,91 @@ -#include "tracelog.h" - -#include -#include -#include -#include - -#ifdef _WIN32 - #pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS. -#endif - -#ifdef TLOG_VS - #ifdef _WIN32 - #ifndef TLOG_NO_COLOURS - #define TLOG_NO_COLOURS - #endif - - #define VC_EXTRALEAN - #include - #else - #error "can't use TLOG_VS if not on windows" - #endif -#endif - -#ifdef TLOG_NO_COLOURS - #define BLACK "" - #define RED "" - #define GREEN "" - #define YELLOW "" - #define BLUE "" - #define MAGENTA "" - #define CYAN "" - #define WHITE "" - #define RESET "" - #define BOLD "" -#else - #define BLACK "\033[30m" - #define RED "\033[31m" - #define GREEN "\033[32m" - #define YELLOW "\033[33m" - #define BLUE "\033[22;34m" - #define MAGENTA "\033[35m" - #define CYAN "\033[36m" - #define WHITE "\033[37m" - #define RESET "\033[0m" - #define BOLD "\033[1m" -#endif - -#define MAX_TRACELOG_MSG_LENGTH 1024 - -bool use_newline = true; - -void traceLog(LogLevel level, const char *fmt, ...) { - char buffer[MAX_TRACELOG_MSG_LENGTH]; - memset(buffer, 0, sizeof(buffer)); - - const char *beg; - switch (level) { - case LogTrace: beg = BOLD WHITE "[TRACE]: " RESET; break; - case LogDebug: beg = BOLD BLUE "[DEBUG]: " RESET; break; - case LogInfo: beg = BOLD GREEN "[INFO]: " RESET; break; - case LogWarning: beg = BOLD YELLOW "[WARNING]: " RESET; break; - case LogError: beg = BOLD RED "[ERROR]: " RESET; break; - case LogFatal: beg = BOLD RED "[FATAL]: " RESET; break; - default: break; - } - - size_t offset = strlen(beg); - strncpy(buffer, beg, sizeof(buffer)); - - va_list args; - va_start(args, fmt); - vsnprintf(buffer + offset, sizeof(buffer) - offset, fmt, args); - va_end(args); - -#ifdef TLOG_VS - OutputDebugStringA(buffer); - if(use_newline) OutputDebugStringA("\n"); -#else - printf("%s", buffer); - if(use_newline) puts(""); -#endif - -#ifndef TLOG_DONT_EXIT_ON_FATAL - if (level == LogFatal) exit(1); -#endif -} - -void traceUseNewline(bool newline) { - use_newline = newline; +#include "tracelog.h" + +#include +#include +#include +#include + +#ifdef _WIN32 + #pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS. +#endif + +#ifdef TLOG_VS + #ifdef _WIN32 + #ifndef TLOG_NO_COLOURS + #define TLOG_NO_COLOURS + #endif + + #define VC_EXTRALEAN + #include + #else + #error "can't use TLOG_VS if not on windows" + #endif +#endif + +#ifdef TLOG_NO_COLOURS + #define BLACK "" + #define RED "" + #define GREEN "" + #define YELLOW "" + #define BLUE "" + #define MAGENTA "" + #define CYAN "" + #define WHITE "" + #define RESET "" + #define BOLD "" +#else + #define BLACK "\033[30m" + #define RED "\033[31m" + #define GREEN "\033[32m" + #define YELLOW "\033[33m" + #define BLUE "\033[22;34m" + #define MAGENTA "\033[35m" + #define CYAN "\033[36m" + #define WHITE "\033[37m" + #define RESET "\033[0m" + #define BOLD "\033[1m" +#endif + +#define MAX_TRACELOG_MSG_LENGTH 1024 + +bool use_newline = true; + +void traceLog(LogLevel level, const char *fmt, ...) { + char buffer[MAX_TRACELOG_MSG_LENGTH]; + memset(buffer, 0, sizeof(buffer)); + + const char *beg; + switch (level) { + case LogTrace: beg = BOLD WHITE "[TRACE]: " RESET; break; + case LogDebug: beg = BOLD BLUE "[DEBUG]: " RESET; break; + case LogInfo: beg = BOLD GREEN "[INFO]: " RESET; break; + case LogWarning: beg = BOLD YELLOW "[WARNING]: " RESET; break; + case LogError: beg = BOLD RED "[ERROR]: " RESET; break; + case LogFatal: beg = BOLD RED "[FATAL]: " RESET; break; + default: beg = ""; break; + } + + size_t offset = strlen(beg); + strncpy(buffer, beg, sizeof(buffer)); + + va_list args; + va_start(args, fmt); + vsnprintf(buffer + offset, sizeof(buffer) - offset, fmt, args); + va_end(args); + +#ifdef TLOG_VS + OutputDebugStringA(buffer); + if(use_newline) OutputDebugStringA("\n"); +#else + printf("%s", buffer); + if(use_newline) puts(""); +#endif + +#ifndef TLOG_DONT_EXIT_ON_FATAL + if (level == LogFatal) exit(1); +#endif +} + +void traceUseNewline(bool newline) { + use_newline = newline; } \ No newline at end of file diff --git a/tracelog.h b/tracelog.h index 0fa35e4..554099f 100644 --- a/tracelog.h +++ b/tracelog.h @@ -1,31 +1,32 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -/* Define any of this to turn on the option - * -> TLOG_NO_COLOURS: print without using 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 -*/ - -#include - -typedef enum { - LogAll, LogTrace, LogDebug, LogInfo, LogWarning, LogError, LogFatal -} LogLevel; - -void traceLog(LogLevel level, const char *fmt, ...); -void traceUseNewline(bool use_newline); - -#define trace(...) traceLog(LogTrace, __VA_ARGS__) -#define debug(...) traceLog(LogDebug, __VA_ARGS__) -#define info(...) traceLog(LogInfo, __VA_ARGS__) -#define warn(...) traceLog(LogWarning, __VA_ARGS__) -#define err(...) traceLog(LogError, __VA_ARGS__) -#define fatal(...) traceLog(LogFatal, __VA_ARGS__) - -#ifdef __cplusplus -} // extern "C" +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/* Define any of this to turn on the option + * -> TLOG_NO_COLOURS: print without using 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 +*/ + +#include + +typedef enum { + LogAll, LogTrace, LogDebug, LogInfo, LogWarning, LogError, LogFatal +} LogLevel; + +void traceLog(LogLevel level, const char *fmt, ...); +void traceUseNewline(bool use_newline); + +#define tall(...) traceLog(LogAll, __VA_ARGS__) +#define trace(...) traceLog(LogTrace, __VA_ARGS__) +#define debug(...) traceLog(LogDebug, __VA_ARGS__) +#define info(...) traceLog(LogInfo, __VA_ARGS__) +#define warn(...) traceLog(LogWarning, __VA_ARGS__) +#define err(...) traceLog(LogError, __VA_ARGS__) +#define fatal(...) traceLog(LogFatal, __VA_ARGS__) + +#ifdef __cplusplus +} // extern "C" #endif \ No newline at end of file diff --git a/vec.h b/vec.h new file mode 100644 index 0000000..07493b2 --- /dev/null +++ b/vec.h @@ -0,0 +1,603 @@ +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +/* + * 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 // 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 +#define foreach(it, vec) vec_foreach(int, veci, it, vec) + +#define T char +#define VEC_SHORT_NAME vecc +#include +#include + + +#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 \ No newline at end of file