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