From ae59f269c2ce979bf36a26743473ac208cdce501 Mon Sep 17 00:00:00 2001 From: snarmph Date: Thu, 18 Apr 2024 14:29:36 +0100 Subject: [PATCH] use arena everywhere + bunch of cool new stuff --- CMakeLists.txt | 28 - build.bat | 1 + build.c | 17 + colla.h | 4325 ------------------------------- colla/arena.c | 198 ++ colla/arena.h | 57 + colla/base64.c | 100 + colla/base64.h | 9 + colla/colladefines.h | 79 + colla/collatypes.h | 16 + colla/cthreads.c | 33 +- colla/cthreads.h | 36 +- colla/file.c | 493 ++-- colla/file.h | 57 +- colla/format.c | 60 + colla/format.h | 11 + colla/http.c | 777 +++--- colla/http.h | 161 +- colla/ini.c | 520 ++-- colla/ini.h | 89 +- colla/json.c | 288 ++ colla/json.h | 47 + colla/server.c | 382 +++ colla/server.h | 36 + colla/socket.c | 239 +- colla/socket.h | 68 +- colla/stb/stb_sprintf.h | 1930 ++++++++++++++ colla/str.c | 757 ++---- colla/str.h | 129 +- colla/strstream.c | 434 +--- colla/strstream.h | 117 +- colla/tracelog.c | 93 +- colla/tracelog.h | 2 +- colla/vmem.c | 133 + colla/vmem.h | 12 + colla/warnings/colla_warn_beg.h | 7 + colla/warnings/colla_warn_end.h | 5 + colla/win32_slim.h | 15 - compile.ini | 8 - deprecated/coroutine.h | 2 +- {colla => deprecated}/dir.c | 408 +-- {colla => deprecated}/dir.h | 68 +- deprecated/dirwatch.c | 2 +- deprecated/dirwatch.h | 2 +- deprecated/fs.c | 2 +- deprecated/fs.h | 3 +- {colla => deprecated}/jobpool.c | 288 +- {colla => deprecated}/jobpool.h | 24 +- deprecated/os.c | 2 +- deprecated/os.h | 2 +- gen.lua | 104 - 51 files changed, 5443 insertions(+), 7233 deletions(-) delete mode 100644 CMakeLists.txt create mode 100644 build.bat create mode 100644 build.c delete mode 100644 colla.h create mode 100644 colla/arena.c create mode 100644 colla/arena.h create mode 100644 colla/base64.c create mode 100644 colla/base64.h create mode 100644 colla/colladefines.h create mode 100644 colla/format.c create mode 100644 colla/format.h create mode 100644 colla/json.c create mode 100644 colla/json.h create mode 100644 colla/server.c create mode 100644 colla/server.h create mode 100644 colla/stb/stb_sprintf.h create mode 100644 colla/vmem.c create mode 100644 colla/vmem.h create mode 100644 colla/warnings/colla_warn_beg.h create mode 100644 colla/warnings/colla_warn_end.h delete mode 100644 colla/win32_slim.h delete mode 100644 compile.ini rename {colla => deprecated}/dir.c (93%) rename {colla => deprecated}/dir.h (93%) rename {colla => deprecated}/jobpool.c (95%) rename {colla => deprecated}/jobpool.h (77%) delete mode 100644 gen.lua diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 54979f5..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,28 +0,0 @@ -add_library(colla STATIC - src/collatypes.h - src/vec.h - src/win32_slim.h - src/tracelog.h src/tracelog.c - src/str.h src/str.c - src/hashmap.h src/hashmap.c - src/utf8.h src/utf8.c - src/ini.h src/ini.c - src/strstream.h src/strstream.c - src/os.h src/os.c - src/file.h src/file.c - src/dir.h src/dir.c - src/socket.h src/socket.c - src/http.h src/http.c - src/cthreads.h src/cthreads.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() - -target_include_directories(colla PUBLIC src) \ No newline at end of file diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..ca66e4d --- /dev/null +++ b/build.bat @@ -0,0 +1 @@ +zig cc -o test.exe main.c -lWs2_32 -lWininet -Wall -Wpedantic -Wno-newline-eof \ No newline at end of file diff --git a/build.c b/build.c new file mode 100644 index 0000000..b38ec93 --- /dev/null +++ b/build.c @@ -0,0 +1,17 @@ +// remember to link Ws2_32 (sockets, server, http) and Wininet (https) if you want to do networking stuff! + +#include "colla/arena.c" +#include "colla/base64.c" +#include "colla/cthreads.c" +#include "colla/file.c" +#include "colla/format.c" +#include "colla/http.c" +#include "colla/ini.c" +#include "colla/json.c" +#include "colla/server.c" +#include "colla/socket.c" +#include "colla/str.c" +#include "colla/strstream.c" +#include "colla/tracelog.c" +#include "colla/utf8.c" +#include "colla/vmem.c" diff --git a/colla.h b/colla.h deleted file mode 100644 index 9b9de28..0000000 --- a/colla.h +++ /dev/null @@ -1,4325 +0,0 @@ -/* - colla.h -- All colla libraries in a single header - Do the following in *one* C file to create the implementation - #define COLLA_IMPL - Use the following in the same C file for options - #define COLLA_NO_THREADS // don't include the threads module - #define COLLA_NO_NET // don't include networking stuff -*/ -#pragma once - -#include -#include - -typedef unsigned char uchar; -typedef unsigned short ushort; -typedef unsigned int uint; - -typedef uint8_t uint8; -typedef uint16_t uint16; -typedef uint32_t uint32; -typedef uint64_t uint64; - -typedef int8_t int8; -typedef int16_t int16; -typedef int32_t int32; -typedef int64_t int64; - -typedef size_t usize; -typedef ptrdiff_t isize; - -#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 -#include - -enum { - LogAll, LogTrace, LogDebug, LogInfo, LogWarning, LogError, LogFatal -}; - -void traceLog(int level, const char *fmt, ...); -void traceLogVaList(int level, const char *fmt, va_list args); -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 - -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include -#include - -/* #include "collatypes.h" */ - -#define STRV_NOT_FOUND SIZE_MAX - -typedef struct str_t { - char *buf; - usize len; -} str_t; - -typedef struct { - const char *buf; - usize len; -} strview_t; - -// == STR_T ======================================================== - -str_t strInit(void); -str_t strFromStr(const char *cstr); -str_t strFromView(strview_t view); -str_t strFromBuf(const char *buf, usize len); -str_t strFromFmt(const char *fmt, ...); - -str_t strFromWCHAR(const wchar_t *src, usize len); -wchar_t *strToWCHAR(str_t ctx); - -void strFree(str_t ctx); -str_t strDup(str_t ctx); -str_t strMove(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 strReplace(str_t *ctx, char from, char to); - -// if len == SIZE_MAX, copies until end -str_t strSubstr(str_t ctx, usize from, usize to); -// if len == SIZE_MAX, returns until end -strview_t strSubview(str_t ctx, usize from, usize to); - -void strLower(str_t *ctx); -str_t strToLower(str_t ctx); - -void strUpper(str_t *ctx); -str_t strToUpper(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, usize 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 -strview_t strvRemovePrefix(strview_t ctx, usize n); -// move view backwards by n characters -strview_t strvRemoveSuffix(strview_t ctx, usize n); -// removes whitespace from the beginning and the end -strview_t strvTrim(strview_t ctx); -// removes whitespace from the beginning -strview_t strvTrimLeft(strview_t ctx); -// removes whitespace from the end -strview_t strvTrimRight(strview_t ctx); - -bool strvIsEmpty(strview_t ctx); - -str_t strvCopy(strview_t ctx); -str_t strvCopyN(strview_t ctx, usize count, usize from); -usize strvCopyBuf(strview_t ctx, char *buf, usize len, usize from); - -strview_t strvSub(strview_t ctx, usize from, usize to); -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); - -usize strvFind(strview_t ctx, char c, usize from); -usize strvFindView(strview_t ctx, strview_t view, usize from); - -usize strvRFind(strview_t ctx, char c, usize from); -usize strvRFindView(strview_t ctx, strview_t view, usize from); - -// Finds the first occurrence of any of the characters of 'view' in this view -usize strvFindFirstOf(strview_t ctx, strview_t view, usize from); -usize strvFindLastOf(strview_t ctx, strview_t view, usize from); - -usize strvFindFirstNot(strview_t ctx, char c, usize from); -usize strvFindFirstNotOf(strview_t ctx, strview_t view, usize from); -usize strvFindLastNot(strview_t ctx, char c, usize from); -usize strvFindLastNotOf(strview_t ctx, strview_t view, usize from); - -#ifdef __cplusplus -} // extern "C" -#endif - -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#define vec(T) T * - -#define vecFree(vec) ((vec) ? free(_vecheader(vec)),NULL : NULL) -#define vecCopy(src, dest) (vecFree(dest), vecAdd(dest, vecCount(src)), memcpy(dest, src, vecCount(src))) - -#define vecAppend(vec, val) (_vecmaygrow(vec, 1), (vec)[_veclen(vec)] = (val), _veclen(vec)++) -#define vecLen(vec) ((vec) ? _veclen(vec) : 0) -#define vecCap(vec) ((vec) ? _veccap(vec) : 0) - -#define vecBeg(vec) (vec) -#define vecEnd(vec) ((vec) ? (vec) + _veclen(vec) : NULL) -#define vecBack(vec) ((vec)[_veclen(vec) - 1]) - -#define vecAdd(vec, n) (_vecmaygrow(vec, (n)), _veclen(vec) += (size_type)(n), &(vec)[_veclen(vec)-(n)]) -#define vecReserve(vec, n) (_vecmaygrow(vec, (n))) -#define vecShrink(vec) (_vecshrink((void **)&(vec), _veclen(vec), sizeof(*(vec)))) - -#define vecClear(vec) ((vec) ? _veclen(vec) = 0 : 0) -#define vecPop(vec) ((vec)[--_veclen(vec)]) - -// == IMPLEMENTATION ========================================================================================== - -#include -#include -#include - -#ifndef size_type - #define size_type uint32_t -#endif - -#define _vecheader(vec) ((size_type *)(vec) - 2) -#define _veccap(vec) _vecheader(vec)[0] -#define _veclen(vec) _vecheader(vec)[1] - -#define _vecneedgrow(vec, n) ((vec) == NULL || _veclen(vec) + n >= _veccap(vec)) -#define _vecmaygrow(vec, n) (_vecneedgrow(vec, (n)) ? _vecgrow(vec, (size_type)(n)) : (void)0) -#define _vecgrow(vec, n) _vecgrowimpl((void **)&(vec), (n), sizeof(*(vec))) - -inline static void _vecgrowimpl(void **arr, size_type increment, size_type itemsize) { - int newcap = *arr ? 2 * _veccap(*arr) + increment : increment + 1; - void *ptr = realloc(*arr ? _vecheader(*arr) : 0, itemsize * newcap + sizeof(size_type) * 2); - assert(ptr); - if (ptr) { - if (!*arr) ((size_type *)ptr)[1] = 0; - *arr = (void *) ((size_type *)ptr + 2); - _veccap(*arr) = newcap; - } -} - -inline static void _vecshrink(void **arr, size_type newcap, size_t itemsize) { - if (newcap == _veccap(*arr) || !*arr) return; - void *ptr = realloc(_vecheader(*arr), itemsize * newcap + sizeof(size_type) * 2); - assert(ptr); - if (ptr) { - *arr = (void *) ((size_type *)ptr + 2); - if (_veclen(*arr) < newcap) _veclen(*arr) = newcap; - _veccap(*arr) = newcap; - } -} - -#ifdef __cplusplus -} // extern "C" -#endif - -#pragma once - -/* #include "collatypes.h" */ -/* #include "vec.h" */ -/* #include "str.h" */ - -/* -Example usage: -hashSetSeed(time(NULL)); -vec(const char *) strings = NULL; -hashmap_t map = hmInit(32); - -// mapGet returns 0 in case it doesn't find anything, this way we don't need -// to check its return value -vecAppend(strings, "nil"); - -hmSet(&map, hashCStr("english"), vecAppend(strings, "hello")); -hmSet(&map, hashCStr("french"), vecAppend(strings, "bonjour")); -hmSet(&map, hashCStr("italian"), vecAppend(strings, "ciao")); - -printf("english: %s\n", strings[hmGet(map, hashCStr("english"))]); -printf("french: %s\n", strings[hmGet(map, hashCStr("french"))]); -printf("italian: %s\n", strings[hmGet(map, hashCStr("italian"))]); - -mapFree(map); -vecFree(strings); -*/ - -typedef struct { - uint64 hash; - uint64 index; -} hashnode_t; - -typedef struct { - vec(hashnode_t) nodes; -} hashmap_t; - -hashmap_t hmInit(usize initial_cap); -void hmFree(hashmap_t map); - -void hmSet(hashmap_t *map, uint64 hash, uint64 index); -uint64 hmGet(hashmap_t map, uint64 hash); -void hmDelete(hashmap_t *map, uint64 hash); - -void hashSetSeed(uint64 new_seed); -uint64 hash(const void *data, usize len); -uint64 hashStr(str_t str); -uint64 hashView(strview_t view); -uint64 hashCStr(const char *cstr); - -#pragma once - -/* #include "collatypes.h" */ - -typedef uint32 rune; - -enum { - UTF8_MAX_SIZE = 4, - UTF8_INVALID = 0x80 -}; - -// grabs the next UTF-8 codepoint and advances string ptr -rune utf8Decode(const char **str); -// encodes a codepoint as UTF-8 and returns the length -usize utf8Encode(char *str, rune ch); -// returns the size of the next UTF-8 codepoint -int utf8Size(const char *str); -// returns the size of a UTF-8 codepoint -usize utf8CpSize(rune ch); - -#pragma once - -#include -/* #include "str.h" */ -/* #include "vec.h" */ -/* #include "utf8.h" */ - -#ifdef __cplusplus -extern "C" { -#endif - -// == INI READER ======================================================================== - -typedef struct { - strview_t key; - strview_t value; -} inivalue_t; - -typedef struct { - strview_t name; - vec(inivalue_t) values; -} initable_t; - -typedef struct { - str_t text; - vec(initable_t) tables; -} ini_t; - -typedef struct { - bool merge_duplicate_tables; - bool merge_duplicate_keys; -} iniopts_t; - -ini_t iniParse(const char *filename, const iniopts_t *options); -ini_t iniParseString(const char *inistr, const iniopts_t *options); -void iniFree(ini_t ctx); - -initable_t *iniGetTable(ini_t *ctx, const char *name); -inivalue_t *iniGet(initable_t *ctx, const char *key); - -vec(strview_t) iniAsArray(const inivalue_t *value, char delim); -// delim is expected to be a single utf8 character -vec(strview_t) iniAsArrayU8(const inivalue_t *value, const char *delim); -uint64 iniAsUInt(const inivalue_t *value); -int64 iniAsInt(const inivalue_t *value); -double iniAsNum(const inivalue_t *value); -bool iniAsBool(const inivalue_t *value); - -// == INI WRITER ======================================================================== - -typedef struct { - str_t key; - str_t value; -} winivalue_t; - -typedef struct { - str_t key; - vec(winivalue_t) values; -} winitable_t; - -typedef struct { - vec(winitable_t) tables; -} iniwriter_t; - -typedef struct { - bool no_discalimer; -} winiopts_t; - -iniwriter_t winiInit(); -void winiFree(iniwriter_t ctx); - -str_t winiToString(iniwriter_t ctx, const winiopts_t *options); -void winiToFile(iniwriter_t ctx, const char *filename, const winiopts_t *options); - -winivalue_t *winiAddValEmpty(winitable_t *table); -winivalue_t *winiAddVal(winitable_t *table, const char *key, const char *value); -winivalue_t *winiAddValStr(winitable_t *table, str_t key, str_t value); -winivalue_t *winiAddValView(winitable_t *table, strview_t key, strview_t value); - -winitable_t *winiAddTablEmpty(iniwriter_t *ctx); -winitable_t *winiAddTab(iniwriter_t *ctx, const char *name); -winitable_t *winiAddTabStr(iniwriter_t *ctx, str_t name); -winitable_t *winiAddTabView(iniwriter_t *ctx, strview_t name); - -#ifdef __cplusplus -} // extern "C" -#endif - -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include - -/* #include "collatypes.h" */ -/* #include "str.h" */ - -/* == INPUT STREAM ============================================ */ - -typedef struct { - const char *start; - const char *cur; - usize size; -} str_istream_t; - -// initialize with null-terminated string -str_istream_t istrInit(const char *str); -str_istream_t istrInitLen(const char *str, usize 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); -// ignore characters until the delimiter and skip it -void istrIgnoreAndSkip(str_istream_t *ctx, char delim); -// skip n characters -void istrSkip(str_istream_t *ctx, usize n); -// skips whitespace (' ', '\n', '\t', '\r') -void istrSkipWhitespace(str_istream_t *ctx); -// read len bytes into buffer, the buffer will not be null terminated -void istrRead(str_istream_t *ctx, char *buf, usize len); -// read a maximum of len bytes into buffer, the buffer will not be null terminated -// returns the number of bytes read -usize istrReadMax(str_istream_t *ctx, char *buf, usize len); -// returns to the beginning of the stream -void istrRewind(str_istream_t *ctx); -// returns back characters -void istrRewindN(str_istream_t *ctx, usize amount); -// returns the number of bytes read from beginning of stream -usize istrTell(str_istream_t ctx); -// returns the number of bytes left to read in the stream -usize istrRemaining(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 *val); -bool istrGetu16(str_istream_t *ctx, uint16 *val); -bool istrGetu32(str_istream_t *ctx, uint32 *val); -bool istrGetu64(str_istream_t *ctx, uint64 *val); -bool istrGeti8(str_istream_t *ctx, int8 *val); -bool istrGeti16(str_istream_t *ctx, int16 *val); -bool istrGeti32(str_istream_t *ctx, int32 *val); -bool istrGeti64(str_istream_t *ctx, int64 *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 -usize 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 -usize istrGetstringBuf(str_istream_t *ctx, char *val, usize len); -strview_t istrGetview(str_istream_t *ctx, char delim); -strview_t istrGetviewLen(str_istream_t *ctx, usize from, usize to); - -/* == OUTPUT STREAM =========================================== */ - -typedef struct { - char *buf; - usize len; - usize cap; -} str_ostream_t; - -str_ostream_t ostrInit(void); -str_ostream_t ostrInitLen(usize initial_alloc); -str_ostream_t ostrInitStr(const char *buf, usize len); - -void ostrFree(str_ostream_t ctx); -void ostrClear(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 ostrPrintfV(str_ostream_t *ctx, const char *fmt, va_list args); -void ostrPutc(str_ostream_t *ctx, char c); -void ostrPuts(str_ostream_t *ctx, const char *str); - -void ostrAppendbool(str_ostream_t *ctx, bool val); -void ostrAppendchar(str_ostream_t *ctx, char val); -void ostrAppendu8(str_ostream_t *ctx, uint8 val); -void ostrAppendu16(str_ostream_t *ctx, uint16 val); -void ostrAppendu32(str_ostream_t *ctx, uint32 val); -void ostrAppendu64(str_ostream_t *ctx, uint64 val); -void ostrAppendi8(str_ostream_t *ctx, int8 val); -void ostrAppendi16(str_ostream_t *ctx, int16 val); -void ostrAppendi32(str_ostream_t *ctx, int32 val); -void ostrAppendi64(str_ostream_t *ctx, int64 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 - -#pragma once - -#ifdef _WIN32 - -#ifndef WIN32_LEAN_AND_MEAN - #define WIN32_LEAN_AND_MEAN -#endif - -#ifndef WIN32_EXTRA_LEAN - #define WIN32_EXTRA_LEAN -#endif - -#include - -#endif // _WIN32 - -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -/* #include "collatypes.h" */ -/* #include "str.h" */ -/* #include "vec.h" */ - -typedef enum { - FILE_READ = 1 << 0, - FILE_WRITE = 1 << 1, - FILE_CLEAR = 1 << 2, - FILE_BOTH = 1 << 3 -} filemode_t; - -typedef uintptr_t file_t; - -typedef struct { - const uint8 *buf; - usize len; -} filebuf_t; - -bool fileExists(const char *fname); - -file_t fileOpen(const char *fname, filemode_t mode); -void fileClose(file_t ctx); - -bool fileIsValid(file_t ctx); - -bool filePutc(file_t ctx, char c); -bool filePuts(file_t ctx, const char *str); -bool filePutstr(file_t ctx, str_t str); -bool filePutview(file_t ctx, strview_t view); - -usize fileRead(file_t ctx, void *buf, usize len); -usize fileWrite(file_t ctx, const void *buf, usize len); - -bool fileSeekEnd(file_t ctx); -void fileRewind(file_t ctx); - -uint64 fileTell(file_t ctx); - -vec(uint8) fileReadWhole(const char *fname); -vec(uint8) fileReadWholeFP(file_t ctx); - -str_t fileReadWholeText(const char *fname); -str_t fileReadWholeTextFP(file_t ctx); - -bool fileWriteWhole(const char *fname, filebuf_t data); -bool fileWriteWholeFP(file_t ctx, filebuf_t data); - -bool fileWriteWholeText(const char *fname, strview_t string); -bool fileWriteWholeTextFP(file_t ctx, strview_t string); - -#ifdef __cplusplus -} // extern "C" -#endif - -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -/* #include "str.h" */ - -typedef void *dir_t; - -typedef enum { - FS_TYPE_UNKNOWN, - FS_TYPE_FILE, - FS_TYPE_DIR, -} fs_type_t; - -typedef struct { - fs_type_t type; - str_t name; -} dir_entry_t; - -dir_t dirOpen(const char *path); -void dirClose(dir_t ctx); - -bool dirValid(dir_t ctx); - -dir_entry_t *dirNext(dir_t ctx); - -void dirCreate(const char *path); - -#ifdef __cplusplus -} // extern "C" -#endif - -#ifndef COLLA_NO_NET -#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 "win32_slim.h" */ - #include - #include - typedef SOCKET socket_t; - typedef int sk_len_t; -#elif SOCK_POSIX - #include - #include - #include - typedef int socket_t; - typedef uint32_t sk_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_t; - -// == 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_t 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, sk_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, sk_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, sk_len_t namelen); - -// Sends data on a socket, returns true on success -int skSend(socket_t sock, const void *buf, int len); -// Sends data on a socket, returns true on success -int skSendPro(socket_t sock, const void *buf, int len, int flags); -// Sends data to a specific destination -int skSendTo(socket_t sock, const void *buf, int len, const sk_addrin_t *to); -// Sends data to a specific destination -int skSendToPro(socket_t sock, const void *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, void *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, void *buf, int len, int flags); -// Receives a datagram and stores the source address. -int skReceiveFrom(socket_t sock, void *buf, int len, sk_addrin_t *from); -// Receives a datagram and stores the source address. -int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, sk_addr_t *from, sk_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 - -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -/* #include "collatypes.h" */ -/* #include "str.h" */ -/* #include "strstream.h" */ -/* #include "socket.h" */ - -typedef enum { - REQ_GET, - REQ_POST, - REQ_HEAD, - REQ_PUT, - REQ_DELETE -} reqtype_t; - -typedef 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, -} resstatus_t; - -typedef struct { - uint8 major; - uint8 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; - -/* #include "vec.h" */ - -// == HTTP REQUEST ============================================================ - -typedef struct { - reqtype_t method; - http_version_t version; - vec(http_field_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, strview_t uri); - -str_ostream_t reqPrepare(http_request_t *ctx); -str_t reqString(http_request_t *ctx); - -// == HTTP RESPONSE =========================================================== - -typedef struct { - resstatus_t status_code; - vec(http_field_t) fields; - http_version_t version; - vec(uint8) 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 { - str_t host_name; - uint16 port; - socket_t socket; -} http_client_t; - -http_client_t hcliInit(void); -void hcliFree(http_client_t *ctx); - -void hcliSetHost(http_client_t *ctx, strview_t hostname); -http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *request); - -// == HTTP ==================================================================== - -http_response_t httpGet(strview_t hostname, strview_t uri); - -// == URL ===================================================================== - -typedef struct { - strview_t host; - strview_t uri; -} url_split_t; - -url_split_t urlSplit(strview_t uri); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // COLLA_NO_NET -#if !defined(__TINYC__) && !defined(COLLA_NO_THREADS) -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -// == THREAD =========================================== - -typedef uintptr_t cthread_t; - -typedef int (*cthread_func_t)(void *); - -cthread_t thrCreate(cthread_func_t func, void *arg); -bool thrValid(cthread_t ctx); -bool thrDetach(cthread_t ctx); - -cthread_t thrCurrent(void); -int thrCurrentId(void); -int thrGetId(cthread_t ctx); - -void thrExit(int code); -bool thrJoin(cthread_t ctx, int *code); - -// == MUTEX ============================================ - -typedef uintptr_t cmutex_t; - -cmutex_t mtxInit(void); -void mtxDestroy(cmutex_t ctx); - -bool mtxValid(cmutex_t ctx); - -bool mtxLock(cmutex_t ctx); -bool mtxTryLock(cmutex_t ctx); -bool mtxUnlock(cmutex_t ctx); - -#ifdef __cplusplus -// small c++ class to make mutexes easier to use -struct lock_t { - inline lock_t(cmutex_t mutex) - : mutex(mutex) { - if (mtxValid(mutex)) { - mtxLock(mutex); - } - } - - inline ~lock_t() { - unlock(); - } - - inline void unlock() { - if (mtxValid(mutex)) { - mtxUnlock(mutex); - } - mutex = 0; - } - - cmutex_t mutex; -}; -#endif - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // !defined(__TINYC__) && !defined(COLLA_NO_THREADS) -#ifdef COLLA_IMPL -/* #include "tracelog.h" */ - -#include -#include -#include - -#ifdef _WIN32 - #pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS. - /* #include "win32_slim.h" */ - #ifndef TLOG_VS - #define TLOG_WIN32_NO_VS - #ifndef TLOG_NO_COLOURS - #define TLOG_NO_COLOURS - #endif - #endif -#endif - -#ifdef TLOG_VS - #ifndef _WIN32 - #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; - -#ifdef TLOG_WIN32_NO_VS -static void setLevelColour(int level) { - WORD attribute = 15; - switch (level) { - case LogDebug: attribute = 1; break; - case LogInfo: attribute = 2; break; - case LogWarning: attribute = 6; break; - case LogError: attribute = 4; break; - case LogFatal: attribute = 4; break; - } - - HANDLE hc = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(hc, attribute); -} -#endif - -void traceLog(int level, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - traceLogVaList(level, fmt, args); - va_end(args); -} - -void traceLogVaList(int level, const char *fmt, va_list args) { - 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 = 0; - -#ifndef TLOG_WIN32_NO_VS - offset = strlen(beg); - strncpy(buffer, beg, sizeof(buffer)); -#endif - - vsnprintf(buffer + offset, sizeof(buffer) - offset, fmt, args); - -#if defined(TLOG_VS) - OutputDebugStringA(buffer); - if(use_newline) OutputDebugStringA("\n"); -#elif defined(TLOG_WIN32_NO_VS) - SetConsoleOutputCP(CP_UTF8); - setLevelColour(level); - printf("%s", beg); - // set back to white - setLevelColour(LogTrace); - printf("%s", buffer); - if(use_newline) puts(""); -#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 "strstream.h" */ - -#include -#include -#include -#include -#include -#include -#include // HUGE_VALF -/* #include "tracelog.h" */ - -#if defined(_WIN32) && defined(__TINYC__) -#define strtoull _strtoui64 -#define strtoll _strtoi64 -#define strtof strtod -#endif - -/* == INPUT STREAM ============================================ */ - -str_istream_t istrInit(const char *str) { - return istrInitLen(str, strlen(str)); -} - -str_istream_t istrInitLen(const char *str, usize 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) { - usize position = ctx->cur - ctx->start; - usize i; - for(i = position; - i < ctx->size && *ctx->cur != delim; - ++i, ++ctx->cur); -} - -void istrIgnoreAndSkip(str_istream_t *ctx, char delim) { - istrIgnore(ctx, delim); - istrSkip(ctx, 1); -} - -char istrPeek(str_istream_t *ctx) { - return *ctx->cur; -} - -void istrSkip(str_istream_t *ctx, usize n) { - usize remaining = ctx->size - (ctx->cur - ctx->start); - if(n > remaining) { - warn("skipping more then remaining: %zu -> %zu", n, remaining); - return; - } - ctx->cur += n; -} - -void istrSkipWhitespace(str_istream_t *ctx) { - while (*ctx->cur && isspace(*ctx->cur)) { - ++ctx->cur; - } -} - -void istrRead(str_istream_t *ctx, char *buf, usize len) { - usize 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; -} - -usize istrReadMax(str_istream_t *ctx, char *buf, usize len) { - usize 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; -} - -void istrRewindN(str_istream_t *ctx, usize amount) { - usize remaining = ctx->size - (ctx->cur - ctx->start); - if (amount > remaining) amount = remaining; - ctx->cur -= amount; -} - -usize istrTell(str_istream_t ctx) { - return ctx.cur - ctx.start; -} - -usize istrRemaining(str_istream_t ctx) { - return ctx.size - (ctx.cur - ctx.start); -} - -bool istrIsFinished(str_istream_t ctx) { - return (usize)(ctx.cur - ctx.start) >= ctx.size; -} - -bool istrGetbool(str_istream_t *ctx, bool *val) { - usize 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 *val) { - char *end = NULL; - *val = (uint8) 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 *val) { - char *end = NULL; - *val = (uint16) 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 *val) { - char *end = NULL; - *val = (uint32) 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 *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 *val) { - char *end = NULL; - *val = (int8) 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 *val) { - char *end = NULL; - *val = (int16) 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 *val) { - char *end = NULL; - *val = (int32) 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 *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; -} - -usize 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; - } - usize len = ctx->cur - from; - *val = (char *)malloc(len + 1); - memcpy(*val, from, len); - (*val)[len] = '\0'; - return len; -} - -usize istrGetstringBuf(str_istream_t *ctx, char *val, usize len) { - usize 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); - usize len = ctx->cur - from; - return strvInitLen(from, len); -} - -strview_t istrGetviewLen(str_istream_t *ctx, usize from, usize to) { - usize len = ctx->size - (ctx->cur - ctx->start) - from; - if (to > len) to = len; - if (from > to) from = to; - return strvInitLen(ctx->cur + from, to - from); -} - -/* == OUTPUT STREAM =========================================== */ - -static void _ostrRealloc(str_ostream_t *ctx, usize needed) { - ctx->cap = (ctx->cap * 2) + needed; - ctx->buf = (char *)realloc(ctx->buf, ctx->cap); -} - -str_ostream_t ostrInit() { - return ostrInitLen(1); -} - -str_ostream_t ostrInitLen(usize initial_alloc) { - str_ostream_t stream; - stream.buf = (char *)calloc(initial_alloc, 1); - stream.len = 0; - stream.cap = initial_alloc; - return stream; -} - -str_ostream_t ostrInitStr(const char *cstr, usize len) { - str_ostream_t stream; - stream.buf = (char *)malloc(len + 1); - memcpy(stream.buf, cstr, len); - stream.len = len; - stream.cap = len + 1; - return stream; -} - -void ostrFree(str_ostream_t ctx) { - free(ctx.buf); -} - -void ostrClear(str_ostream_t *ctx) { - ctx->len = 0; -} - -char ostrBack(str_ostream_t ctx) { - if(ctx.len == 0) return '\0'; - return ctx.buf[ctx.len - 1]; -} - -str_t ostrAsStr(str_ostream_t ctx) { - return (str_t) { - .buf = ctx.buf, - .len = ctx.len - }; -} - -strview_t ostrAsView(str_ostream_t ctx) { - return (strview_t) { - .buf = ctx.buf, - .len = ctx.len - }; -} - -void ostrReplace(str_ostream_t *ctx, char from, char to) { - for(usize i = 0; i < ctx->len; ++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); - ostrPrintfV(ctx, fmt, va); - va_end(va); -} - -void ostrPrintfV(str_ostream_t *ctx, const char *fmt, va_list args) { - va_list vtemp; - int len; - usize remaining; - - // vsnprintf returns the length of the formatted string, even if truncated - // we use this to get the actual length of the formatted string - va_copy(vtemp, args); - len = vsnprintf(NULL, 0, fmt, vtemp); - va_end(vtemp); - if(len < 0) { - err("couldn't format string \"%s\"", fmt); - goto error; - } - - remaining = ctx->cap - ctx->len; - if(remaining < (usize)len) { - _ostrRealloc(ctx, len + 1); - remaining = ctx->cap - ctx->len; - } - - // actual formatting here - va_copy(vtemp, args); - len = vsnprintf(ctx->buf + ctx->len, remaining, fmt, vtemp); - va_end(vtemp); - if(len < 0) { - err("couldn't format stringh \"%s\"", fmt); - goto error; - } - ctx->len += len; - -error: - return; -} - - -#define APPEND_BUF_LEN 20 - -void ostrPutc(str_ostream_t *ctx, char c) { - ostrAppendchar(ctx, c); -} - -void ostrPuts(str_ostream_t *ctx, const char *str) { - ostrAppendview(ctx, strvInit(str)); -} - -void ostrAppendbool(str_ostream_t *ctx, bool val) { - ostrAppendview(ctx, strvInit(val ? "true" : "false")); -} - -void ostrAppendchar(str_ostream_t *ctx, char val) { - if(ctx->len >= ctx->cap) { - _ostrRealloc(ctx, 1); - } - ctx->buf[ctx->len++] = val; - ctx->buf[ctx->len] = '\0'; -} - -void ostrAppendu8(str_ostream_t *ctx, uint8 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 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 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 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 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 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 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 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->cap - ctx->len) <= view.len) { - _ostrRealloc(ctx, view.len + 1); - } - memcpy(ctx->buf + ctx->len, view.buf, view.len); - ctx->len += view.len; - ctx->buf[ctx->len] = '\0'; -} - -/* #include "str.h" */ - -#include -#include -#include -#include -#include -#include - -/* #include "tracelog.h" */ -/* #include "strstream.h" */ - -#ifdef _WIN32 -/* #include "win32_slim.h" */ -#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 strFromStr(const char *cstr) { - return cstr ? strFromBuf(cstr, strlen(cstr)) : strInit(); -} - -str_t strFromView(strview_t view) { - return strFromBuf(view.buf, view.len); -} - -str_t strFromBuf(const char *buf, usize len) { - if (!buf) return strInit(); - str_t str; - str.len = len; - str.buf = (char *)malloc(len + 1); - memcpy(str.buf, buf, len); - str.buf[len] = '\0'; - return str; -} - -str_t strFromFmt(const char *fmt, ...) { - str_ostream_t out = ostrInit(); - va_list va; - va_start(va, fmt); - ostrPrintfV(&out, fmt, va); - va_end(va); - return ostrAsStr(out); -} - -void strFree(str_t ctx) { - free(ctx.buf); -} - -str_t strFromWCHAR(const wchar_t *src, usize 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 = (char *)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 - usize actual_len = len * sizeof(wchar_t); - - usize dest_len = len * 6; - char *dest = (char *)malloc(dest_len); - - iconv_t cd = iconv_open("UTF-8", "WCHAR_T"); - assert(cd); - - usize dest_left = dest_len; - char *dest_temp = dest; - char *src_temp = (char*)src; - usize lost = iconv(cd, &src_temp, &actual_len, &dest_temp, &dest_left); - assert(lost != ((usize)-1)); - (void)lost; - - dest_len -= dest_left; - dest = (char *)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 = (wchar_t *)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 - usize dest_len = ctx.len * sizeof(wchar_t); - char *dest = (char *)malloc(dest_len); - - iconv_t cd = iconv_open("WCHAR_T", "UTF-8"); - assert(cd); - - usize dest_left = dest_len; - char *dest_temp = dest; - char *src_temp = ctx.buf; - usize lost = iconv(cd, &src_temp, &ctx.len, &dest_temp, &dest_left); - assert(lost != ((usize)-1)); - (void)lost; - - dest_len -= dest_left; - dest = (char *)realloc(dest, dest_len + 1); - dest[dest_len] = '\0'; - - iconv_close(cd); - - return (wchar_t *)dest; -#endif -} - -str_t strDup(str_t ctx) { - return strFromBuf(ctx.buf, ctx.len); -} - -str_t strMove(str_t *ctx) { - str_t out = *ctx; - ctx->buf = NULL; - ctx->len = 0; - return out; -} - -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 strSwap(str_t *ctx, str_t *other) { - char *buf = other->buf; - usize 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(usize i = 0; i < ctx->len; ++i) { - if(ctx->buf[i] == from) { - ctx->buf[i] = to; - } - } -} - -str_t strSubstr(str_t ctx, usize from, usize to) { - if(strIsEmpty(ctx)) return strInit(); - if (to > ctx.len) to = ctx.len; - if (from > to) from = to; - return strFromBuf(ctx.buf + from, to - from); -} - -strview_t strSubview(str_t ctx, usize from, usize to) { - if(strIsEmpty(ctx)) return strvInit(NULL); - if (to > ctx.len) to = ctx.len; - if (from > to) from = to; - return strvInitLen(ctx.buf + from, to - from); -} - -void strLower(str_t *ctx) { - for(usize 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; -} - -void strUpper(str_t *ctx) { - for(usize i = 0; i < ctx->len; ++i) { - ctx->buf[i] = (char)toupper(ctx->buf[i]); - } -} - -str_t strToUpper(str_t ctx) { - str_t str = strDup(ctx); - strUpper(&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, usize 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; -} - -strview_t strvRemovePrefix(strview_t ctx, usize n) { - if (n > ctx.len) n = ctx.len; - return (strview_t){ - .buf = ctx.buf + n, - .len = ctx.len - n - }; -} - -strview_t strvRemoveSuffix(strview_t ctx, usize n) { - if (n > ctx.len) n = ctx.len; - return (strview_t){ - .buf = ctx.buf, - .len = ctx.len - n - }; -} - -strview_t strvTrim(strview_t ctx) { - return strvTrimLeft(strvTrimRight(ctx)); -} - -strview_t strvTrimLeft(strview_t ctx) { - strview_t out = ctx; - for (usize i = 0; i < ctx.len && isspace(ctx.buf[i]); ++i) { - ++out.buf; - --out.len; - } - return out; -} - -strview_t strvTrimRight(strview_t ctx) { - strview_t out = ctx; - for (isize i = ctx.len - 1; i >= 0 && isspace(ctx.buf[i]); --i) { - --out.len; - } - return out; -} - -str_t strvCopy(strview_t ctx) { - return strFromView(ctx); -} - -str_t strvCopyN(strview_t ctx, usize count, usize from) { - usize sz = ctx.len + 1 - from; - count = min(count, sz); - return strFromBuf(ctx.buf + from, count); -} - -usize strvCopyBuf(strview_t ctx, char *buf, usize len, usize from) { - usize sz = ctx.len + 1 - from; - len = min(len, sz); - memcpy(buf, ctx.buf + from, len); - buf[len - 1] = '\0'; - return len - 1; -} - -strview_t strvSub(strview_t ctx, usize from, usize to) { - if (to > ctx.len) to = ctx.len; - if (from > to) from = to; - return strvInitLen(ctx.buf + from, to - from); -} - -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(usize 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(usize 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; - usize end = ctx.len - view.len; - for(usize i = 0; i < end; ++i) { - if(memcmp(ctx.buf + i, view.buf, view.len) == 0) return true; - } - return false; -} - -usize strvFind(strview_t ctx, char c, usize from) { - for(usize i = from; i < ctx.len; ++i) { - if(ctx.buf[i] == c) return i; - } - return SIZE_MAX; -} - -usize strvFindView(strview_t ctx, strview_t view, usize from) { - if(ctx.len < view.len) return SIZE_MAX; - usize end = ctx.len - view.len; - for(usize i = from; i < end; ++i) { - if(memcmp(ctx.buf + i, view.buf, view.len) == 0) return i; - } - return SIZE_MAX; -} - -usize strvRFind(strview_t ctx, char c, usize 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; -} - -usize strvRFindView(strview_t ctx, strview_t view, usize 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; -} - -usize strvFindFirstOf(strview_t ctx, strview_t view, usize from) { - if(ctx.len < view.len) return SIZE_MAX; - for(usize i = from; i < ctx.len; ++i) { - for(usize j = 0; j < view.len; ++j) { - if(ctx.buf[i] == view.buf[j]) return i; - } - } - return SIZE_MAX; -} - -usize strvFindLastOf(strview_t ctx, strview_t view, usize from) { - if(from >= ctx.len) { - from = ctx.len - 1; - } - - const char *buf = ctx.buf + from; - for(; buf >= ctx.buf; --buf) { - for(usize j = 0; j < view.len; ++j) { - if(*buf == view.buf[j]) return (buf - ctx.buf); - } - } - - return SIZE_MAX; -} - -usize strvFindFirstNot(strview_t ctx, char c, usize from) { - usize end = ctx.len - 1; - for(usize i = from; i < end; ++i) { - if(ctx.buf[i] != c) return i; - } - return SIZE_MAX; -} - -usize strvFindFirstNotOf(strview_t ctx, strview_t view, usize from) { - for(usize i = from; i < ctx.len; ++i) { - if(!strvContains(view, ctx.buf[i])) { - return i; - } - } - return SIZE_MAX; -} - -usize strvFindLastNot(strview_t ctx, char c, usize 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; -} - -usize strvFindLastNotOf(strview_t ctx, strview_t view, usize 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 buf[] = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' }; - s = strFromBuf((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 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 - -/* #include "hashmap.h" */ - -#include - -static uint64 hash_seed = 0; - -hashmap_t hmInit(usize initial_cap) { - hashmap_t map = {0}; - if (!initial_cap) initial_cap = 512; - vecReserve(map.nodes, initial_cap); - memset(map.nodes, 0, sizeof(hashnode_t) * initial_cap); - return map; -} - -void hmFree(hashmap_t map) { - vecFree(map.nodes); -} - -void hmSet(hashmap_t *map, uint64 hash, uint64 index) { - uint32 hm_index = hash % vecCap(map->nodes); - - while (map->nodes[hm_index].hash) { - hashnode_t *node = &map->nodes[hm_index]; - if (node->hash == hash) { - node->index = index; - return; - } - hm_index = (hm_index + 1) % vecCap(map->nodes); - } - - map->nodes[hm_index].hash = hash; - map->nodes[hm_index].index = index; - _veclen(map->nodes)++; - - float load_factor = (float)vecLen(map->nodes) / (float)vecCap(map->nodes); - if (load_factor > 0.75f) { - uint32 old_cap = vecCap(map->nodes); - vecReserve(map->nodes, old_cap); - for (usize i = old_cap; i < vecCap(map->nodes); ++i) { - map->nodes[i].hash = 0; - map->nodes[i].index = 0; - } - } -} - -uint64 hmGet(hashmap_t map, uint64 hash) { - uint32 hm_index = hash % vecCap(map.nodes); - - do { - hashnode_t *node = &map.nodes[hm_index]; - if (node->hash == hash) { - return node->index; - } - hm_index = (hm_index + 1) % vecCap(map.nodes); - } while (map.nodes[hm_index].hash); - - return 0; -} - -void hmDelete(hashmap_t *map, uint64 hash) { - uint32 hm_index = hash % vecCap(map->nodes); - - do { - hashnode_t *node = &map->nodes[hm_index]; - if (node->hash == hash) { - node->hash = 0; - node->index = 0; - break; - } - hm_index = (hm_index + 1) % vecCap(map->nodes); - } while (map->nodes[hm_index].hash); - - if(vecLen(map->nodes)) _veclen(map->nodes)--; -} - -void hashSetSeed(uint64 new_seed) { - hash_seed = new_seed; -} - -uint64 hash(const void *ptr, usize len) { - const uint64 m = 0xC6A4A7935BD1E995LLU; - const int r = 47; - - uint64 h = hash_seed ^ (len * m); - - const uint64 *data = (const uint64 *)ptr; - const uint64 *end = (len >> 3) + data; - - while (data != end) { - uint64 k = *data++; - - k *= m; - k ^= k >> r; - k *= m; - - h ^= k; - h *= m; - } - - const unsigned char * data2 = (const unsigned char *)data; - - switch(len & 7) { - case 7: h ^= (uint64_t)(data2[6]) << 48; - case 6: h ^= (uint64_t)(data2[5]) << 40; - case 5: h ^= (uint64_t)(data2[4]) << 32; - case 4: h ^= (uint64_t)(data2[3]) << 24; - case 3: h ^= (uint64_t)(data2[2]) << 16; - case 2: h ^= (uint64_t)(data2[1]) << 8; - case 1: h ^= (uint64_t)(data2[0]); - h *= m; - }; - - h ^= h >> r; - h *= m; - h ^= h >> r; - - return h; -} - -uint64 hashStr(str_t str) { - return hash(str.buf, str.len); -} - -uint64 hashView(strview_t view) { - return hash(view.buf, view.len); -} - -uint64 hashCStr(const char *cstr) { - return hash(cstr, strlen(cstr)); -} - -/* #include "utf8.h" */ - -static const uint8 masks[] = { - 0x7f, // 0111-1111 - 0x1f, // 0001-1111 - 0x0f, // 0000-1111 - 0x07, // 0000-0111 - 0x03, // 0000-0011 - 0x01 // 0000-0001 -}; - -struct { - uint8 mask; - uint8 result; - int octets; -} sizes[] = { - { 0x80, 0x00, 1 }, // 1000-0000, 0000-0000 - { 0xE0, 0xC0, 2 }, // 1110-0000, 1100-0000 - { 0xF0, 0xE0, 3 }, // 1111-0000, 1110-0000 - { 0xF8, 0xF0, 4 }, // 1111-1000, 1111-0000 - { 0xFC, 0xF8, 5 }, // 1111-1100, 1111-1000 - { 0xFE, 0xF8, 6 }, // 1111-1110, 1111-1000 - { 0x80, 0x80, -1 }, // 1000-0000, 1000-0000 -}; - - -/* -UTF-8 codepoints are encoded using the first bits of the first character - -byte 1 | byte 2 | byte 3 | byte 4 -0xxx xxxx | | | -110x xxxx | 10xx xxxx | | -1110 xxxx | 10xx xxxx | 10xx xxxx | -1111 0xxx | 10xx xxxx | 10xx xxxx | 10xx xxxx - -so when we decode it we first find the size of the codepoint (from 1 to 4) -then we apply the mask to the first byte to get the first character -then we keep shifting the rune left 6 and applying the next byte to the mask -until the codepoint is finished (size is 0) - -## EXAMPLE - -utf8 string (€) = 1110-0010 1000-0010 1010-1100 - -cp = 0000-0000 0000-0000 0000-0000 0000-0000 -size = 3 -mask = 0x0f -> 0000-1111 -cp = *s & mask = 1110-0010 & 0000-1111 = 0000-0000 0000-0000 0000-0000 0000-0010 -++s = 1000-0010 - ---size = 2 -cp <<= 6 = 0000-0000 0000-0000 0000-0000 1000-0000 -cp |= *s & 0x3f = 1000-0010 & 0011-1111 = 0000-0000 0000-0000 0000-0000 1000-0010 -++s = 1010-1100 - ---size = 1 -cp <<= 6 = 0000-0000 0000-0000 0010-0000 1000-0000 -cp |= *s & 0x3f = 1010-1100 & 0011-1111 = 0000-0000 0000-0000 0010-0000 1010-1100 -++s = ---------- - -final codepoint = 0010-0000 1010-1100 -€ codepoint = 0010-0000 1010-1100 -*/ - -rune utf8Decode(const char **char_str) { - uint8 **s = (uint8 **)char_str; - - rune ch = 0; - // if is ascii - if (**s < 128) { - ch = **s; - ++*s; - return ch; - } - int size = utf8Size((char *)*s); - if (size == -1) { - ++*s; - return UTF8_INVALID; - } - uint8 mask = masks[size - 1]; - ch = **s & mask; - ++*s; - while(--size) { - ch <<= 6; - ch |= **s & 0x3f; // 0011-1111 - ++*s; - } - return ch; -} - - -/* -to encode a codepoint in a utf8 string we first need to find -the length of the codepoint -then we start from the rightmost byte and loop for each byte of the codepoint -using the length we got before until the first byte (which we skip) -> and (&) with 0x3f so we ignore the first to bits of the codepoint -> or (|) with 0x80 so we make sure that the first two bits are 10 -> bitshift the codepoint right 6 - -finally, we apply the correct length-mask to the first byte - -## EXAMPLE - -ch € = 0010-0000 1010-1100 -ch < 0x10000 - first = 0xe0 = 1110-0000 - len = 3 - -str[2] = (ch & 0x3f) | 0x80 = 1010-1100 & 0011-1111 | 1000-0000 - = 1010-1100 -ch >>= 6 = 0010-0000 1010-1100 >> 6 = 1000-0010 - -str[1] = (ch & 0x3f) | 0x80 = 1000-0010 & 0011-1111 | 1000-000 - = 1000-0010 -ch >>= 6 = 1000-0010 >> 6 = 0000-0010 - -str[0] = ch | first_mask = 0000-0010 | 1111-0000 - = 1111-0010 - -str = 1111-0010 1000-0010 1010-1100 -utf8 € = 1110-0010 1000-0010 1010-1100 -*/ - -usize utf8Encode(char *str, rune codepoint) { - usize len = 0; - uint8 first; - - if (codepoint < 0x80) { // 0000-0000 0000-0000 0000-0000 1000-0000 - first = 0; - len = 1; - } - else if (codepoint < 0x800) { // 0000-0000 0000-0000 0000-1000 0000-0000 - first = 0xc0; // 1100-0000 - len = 2; - } - else if (codepoint < 0x10000) { // 0000-0000 0000-0001 0000-0000 0000-0000 - first = 0xe0; // 1110-0000 - len = 3; - } - else { - first = 0xf0; // 1111-0000 - len = 4; - } - - for (usize i = len - 1; i > 0; --i) { - // 0x3f -> 0011-1111 - // 0x80 -> 1000-0000 - str[i] = (codepoint & 0x3f) | 0x80; - codepoint >>= 6; - } - - str[0] = (char)(codepoint | first); - return len; -} - -int utf8Size(const char *str) { - uint8 c = (uint8)*str; - for(usize i = 0; i < (sizeof(sizes) / sizeof(*sizes)); ++i) { - if ((c & sizes[i].mask) == sizes[i].result) { - return sizes[i].octets; - } - } - return -1; -} - -usize utf8CpSize(rune ch) { - if (ch < 0x80) return 1; - else if (ch < 0x800) return 2; - else if (ch < 0x10000) return 3; - return 4; -} - -/* #include "ini.h" */ - -/* #include "strstream.h" */ -/* #include "file.h" */ -/* #include "tracelog.h" */ - -// == INI READER ======================================================================== - -static const iniopts_t default_opts = {0}; - -static initable_t *findTable(ini_t *ctx, strview_t name); -static inivalue_t *findValue(vec(inivalue_t) values, strview_t key); -static void addTable(ini_t *ctx, str_istream_t *in, const iniopts_t *options); -static void addValue(initable_t *table, str_istream_t *in, const iniopts_t *options); - -void _iniParseInternal(ini_t *ini, const iniopts_t *options) { - // add root table - vecAppend(ini->tables, (initable_t){0}); - str_istream_t in = istrInitLen(ini->text.buf, ini->text.len); - istrSkipWhitespace(&in); - while (!istrIsFinished(in)) { - switch(*in.cur) { - case '[': - addTable(ini, &in, options); - break; - case '#': case ';': - istrIgnore(&in, '\n'); - break; - default: - addValue(&ini->tables[0], &in, options); - break; - } - istrSkipWhitespace(&in); - } - -} - -ini_t iniParse(const char *filename, const iniopts_t *options) { - ini_t ini = { .text = fileReadWholeText(filename) }; - if (strIsEmpty(ini.text)) return ini; - if (!options) options = &default_opts; - _iniParseInternal(&ini, options); - return ini; -} - -ini_t iniParseString(const char *inistr, const iniopts_t *options) { - ini_t ini = { .text = strFromStr(inistr) }; - if (!options) options = &default_opts; - _iniParseInternal(&ini, options); - return ini; -} - -void iniFree(ini_t ctx) { - strFree(ctx.text); - for (uint32 i = 0; i < vecLen(ctx.tables); ++i) { - vecFree(ctx.tables[i].values); - } - vecFree(ctx.tables); -} - -initable_t *iniGetTable(ini_t *ctx, const char *name) { - if (!name) { - return &ctx->tables[0]; - } - else { - return findTable(ctx, strvInit(name)); - } -} - -inivalue_t *iniGet(initable_t *ctx, const char *key) { - return ctx ? findValue(ctx->values, strvInit(key)) : NULL; -} - -vec(strview_t) iniAsArray(const inivalue_t *value, char delim) { - if (!value) return NULL; - if (!delim) delim = ' '; - - vec(strview_t) out = NULL; - strview_t v = value->value; - - usize start = 0; - for (usize i = 0; i < v.len; ++i) { - if (v.buf[i] == delim) { - strview_t arr_val = strvTrim(strvSub(v, start, i)); - if (!strvIsEmpty(arr_val)) vecAppend(out, arr_val); - start = i + 1; - } - } - strview_t last = strvTrim(strvSub(v, start, SIZE_MAX)); - if (!strvIsEmpty(last)) vecAppend(out, last); - return out; -} - -vec(strview_t) iniAsArrayU8(const inivalue_t *value, const char *delim) { - if (!value || !delim) return NULL; - - rune cpdelim = utf8Decode(&delim); - vec(strview_t) out = NULL; - strview_t v = value->value; - - const char *start = v.buf; - const char *buf = v.buf; - const char *prevbuf = buf; - - for(rune cp = utf8Decode(&buf); - buf != (v.buf + v.len); - cp = utf8Decode(&buf) - ) { - if (cp == cpdelim) { - usize start_pos = start - v.buf; - usize end_pos = prevbuf - v.buf; - strview_t arr_val = strvTrim(strvSub(v, start_pos, end_pos)); - if (!strvIsEmpty(arr_val)) vecAppend(out, arr_val); - // buf has already gone to the next codepoint, skipping the delimiter - start = buf; - } - prevbuf = buf; - } - - strview_t last = strvTrim(strvSub(v, start - v.buf, SIZE_MAX)); - if (!strvIsEmpty(last)) vecAppend(out, last); - return out; -} - -uint64 iniAsUInt(const inivalue_t *value) { - if (!value) return 0; - str_istream_t in = istrInitLen(value->value.buf, value->value.len); - uint64 val = 0; - if (!istrGetu64(&in, &val)) val = 0; - return val; -} - -int64 iniAsInt(const inivalue_t *value) { - if (!value) return 0; - str_istream_t in = istrInitLen(value->value.buf, value->value.len); - int64 val = 0; - if (!istrGeti64(&in, &val)) val = 0; - return val; -} - -double iniAsNum(const inivalue_t *value) { - if (!value) return 0.f; - str_istream_t in = istrInitLen(value->value.buf, value->value.len); - double val = 0; - if (!istrGetdouble(&in, &val)) val = 0; - return val; -} - -bool iniAsBool(const inivalue_t *value) { - if (!value) return false; - return strvCompare(value->value, strvInit("true")) == 0; -} - -// == INI WRITER ======================================================================== - -/* #include "strstream.h" */ -/* #include "file.h" */ - -static const winiopts_t default_wopts = {0}; - -iniwriter_t winiInit() { - iniwriter_t out = {0}; - vecAppend(out.tables, (winitable_t){0}); - return out; -} - -void winiFree(iniwriter_t ctx) { - for (winitable_t *tab = ctx.tables; tab != vecEnd(ctx.tables); ++tab) { - strFree(tab->key); - for (winivalue_t *val = tab->values; val != vecEnd(tab->values); ++val) { - strFree(val->key); - strFree(val->value); - } - vecFree(tab->values); - } - vecFree(ctx.tables); -} - -str_t winiToString(iniwriter_t ctx, const winiopts_t *options) { - if (!options) options = &default_wopts; - - str_ostream_t out = ostrInitLen(1024 * 20); - if (!options->no_discalimer) ostrPuts(&out, "# auto-generated by colla's ini.h, do not modify!\n"); - // add root values - winitable_t *root = &ctx.tables[0]; - for (winivalue_t *val = root->values; val != vecEnd(root->values); ++val) { - ostrPrintf(&out, "%s = %s\n", val->key.buf, val->value.buf); - } - if (root->values) ostrPuts(&out, "\n"); - // add each table - for (usize i = 1; i < vecLen(ctx.tables); ++i) { - winitable_t *tab = &ctx.tables[i]; - ostrPrintf(&out, "[%s]\n", tab->key.buf); - for (winivalue_t *val = tab->values; val != vecEnd(tab->values); ++val) { - ostrPrintf(&out, "%s = %s\n", val->key.buf, val->value.buf); - } - if ((i + 1) < vecLen(ctx.tables)) ostrPuts(&out, "\n"); - } - return ostrAsStr(out); -} - -void winiToFile(iniwriter_t ctx, const char *filename, const winiopts_t *options) { - if (!options) options = &default_wopts; - - file_t fp = fileOpen(filename, FILE_WRITE); - if (!fileIsValid(fp)) { - err("couldn't write ini to file %s", filename); - return; - } - str_t string = winiToString(ctx, options); - fileWriteWholeTextFP(fp, strvInitStr(string)); - strFree(string); - fileClose(fp); -} - -winivalue_t *winiAddValEmpty(winitable_t *table) { - if (!table) return NULL; - vecAppend(table->values, (winivalue_t){0}); - return &vecBack(table->values); -} - -winivalue_t *winiAddVal(winitable_t *table, const char *key, const char *value) { - if (!table) return NULL; - winivalue_t val = { .key = strFromStr(key), .value = strFromStr(value) }; - vecAppend(table->values, val); - return &vecBack(table->values); -} - -winivalue_t *winiAddValStr(winitable_t *table, str_t key, str_t value) { - if (!table) return NULL; - winivalue_t val = { .key = key, .value = value }; - vecAppend(table->values, val); - return &vecBack(table->values); -} - -winivalue_t *winiAddValView(winitable_t *table, strview_t key, strview_t value) { - if (!table) return NULL; - winivalue_t val = { .key = strFromView(key), .value = strFromView(value) }; - vecAppend(table->values, val); - return &vecBack(table->values); -} - -winitable_t *winiAddTablEmpty(iniwriter_t *ctx) { - vecAppend(ctx->tables, (winitable_t){0}); - return &vecBack(ctx->tables); -} - -winitable_t *winiAddTab(iniwriter_t *ctx, const char *name) { - winitable_t tab = { .key = strFromStr(name) }; - vecAppend(ctx->tables, tab); - return &vecBack(ctx->tables); -} - -winitable_t *winiAddTabStr(iniwriter_t *ctx, str_t name) { - winitable_t tab = { .key = name }; - vecAppend(ctx->tables, tab); - return &vecBack(ctx->tables); -} - -winitable_t *winiAddTabView(iniwriter_t *ctx, strview_t name) { - winitable_t tab = { .key = strFromView(name) }; - vecAppend(ctx->tables, tab); - return &vecBack(ctx->tables); -} - -// == PRIVATE FUNCTIONS ======================================================== - -static initable_t *findTable(ini_t *ctx, strview_t name) { - if (strvIsEmpty(name)) return NULL; - for (uint32 i = 1; i < vecLen(ctx->tables); ++i) { - if (strvCompare(ctx->tables[i].name, name) == 0) { - return &ctx->tables[i]; - } - } - return NULL; -} - -static inivalue_t *findValue(vec(inivalue_t) values, strview_t key) { - if (strvIsEmpty(key)) return NULL; - for (uint32 i = 0; i < vecLen(values); ++i) { - if (strvCompare(values[i].key, key) == 0) { - return &values[i]; - } - } - return NULL; -} - -static void addTable(ini_t *ctx, str_istream_t *in, const iniopts_t *options) { - istrSkip(in, 1); // skip [ - strview_t name = istrGetview(in, ']'); - istrSkip(in, 1); // skip ] - initable_t *table = options->merge_duplicate_tables ? findTable(ctx, name) : NULL; - if (!table) { - vecAppend(ctx->tables, (initable_t){ name }); - table = &vecBack(ctx->tables); - } - istrIgnore(in, '\n'); istrSkip(in, 1); - while (!istrIsFinished(*in)) { - switch (*in->cur) { - case '\n': case '\r': - return; - case '#': case ';': - istrIgnore(in, '\n'); - break; - default: - addValue(table, in, options); - break; - } - } -} - -static void addValue(initable_t *table, str_istream_t *in, const iniopts_t *options) { - if (!table) fatal("table is null"); - - strview_t key = strvTrim(istrGetview(in, '=')); - istrSkip(in, 1); - strview_t value = strvTrim(istrGetview(in, '\n')); - // value might be until EOF, in that case no use in skipping - if (!istrIsFinished(*in)) istrSkip(in, 1); // skip newline - inivalue_t *new_value = options->merge_duplicate_keys ? findValue(table->values, key) : NULL; - if (!new_value) { - inivalue_t ini_val = (inivalue_t){ key, value }; - vecAppend(table->values, ini_val); - } - else { - new_value->value = value; - } -} - -/* #include "file.h" */ - -/* #include "tracelog.h" */ - -#ifdef _WIN32 -/* #include "win32_slim.h" */ -#include - -static DWORD _toWin32Access(int mode) { - if(mode & FILE_READ) return GENERIC_READ; - if(mode & FILE_WRITE) return GENERIC_WRITE; - if(mode & FILE_BOTH) return GENERIC_READ | GENERIC_WRITE; - fatal("unrecognized access mode: %d", mode); - return 0; -} - -static DWORD _toWin32Creation(filemode_t mode) { - if(mode & FILE_READ) return OPEN_EXISTING; - if(mode == (FILE_WRITE | FILE_CLEAR)) return CREATE_ALWAYS; - if(mode & FILE_WRITE) return OPEN_ALWAYS; - if(mode & FILE_BOTH) return OPEN_ALWAYS; - fatal("unrecognized creation mode: %d", mode); - return 0; -} - -bool fileExists(const char *fname) { - DWORD dwAttrib = GetFileAttributesA(fname); - - return (dwAttrib != INVALID_FILE_ATTRIBUTES && - !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); -} - -file_t fileOpen(const char *fname, filemode_t mode) { - return (file_t)CreateFileA( - fname, - _toWin32Access(mode), - 0, - NULL, - _toWin32Creation(mode), - FILE_ATTRIBUTE_NORMAL, - NULL - ); -} - -void fileClose(file_t ctx) { - if (ctx) { - CloseHandle((HANDLE)ctx); - } -} - -bool fileIsValid(file_t ctx) { - return (HANDLE)ctx != INVALID_HANDLE_VALUE; -} - -bool filePutc(file_t ctx, char c) { - return fileWrite(ctx, &c, 1) == 1; -} - -bool filePuts(file_t ctx, const char *str) { - usize len = strlen(str); - return fileWrite(ctx, str, len) == len; -} - -bool filePutstr(file_t ctx, str_t str) { - return fileWrite(ctx, str.buf, str.len) == str.len; -} - -bool filePutview(file_t ctx, strview_t view) { - return fileWrite(ctx, view.buf, view.len) == view.len; -} - -usize fileRead(file_t ctx, void *buf, usize len) { - DWORD bytes_read = 0; - BOOL result = ReadFile((HANDLE)ctx, buf, (DWORD)len, &bytes_read, NULL); - return result == TRUE ? (usize)bytes_read : 0; -} - -usize fileWrite(file_t ctx, const void *buf, usize len) { - DWORD bytes_read = 0; - BOOL result = WriteFile((HANDLE)ctx, buf, (DWORD)len, &bytes_read, NULL); - return result == TRUE ? (usize)bytes_read : 0; -} - -bool fileSeekEnd(file_t ctx) { - return SetFilePointerEx((HANDLE)ctx, (LARGE_INTEGER){0}, NULL, FILE_END) == TRUE; -} - -void fileRewind(file_t ctx) { - SetFilePointerEx((HANDLE)ctx, (LARGE_INTEGER){0}, NULL, FILE_BEGIN); -} - -uint64 fileTell(file_t ctx) { - LARGE_INTEGER tell; - BOOL result = SetFilePointerEx((HANDLE)ctx, (LARGE_INTEGER){0}, &tell, FILE_CURRENT); - return result == TRUE ? (uint64)tell.QuadPart : 0; -} - -#else -#include -#include -#include -#include -#include - -const char *_toStdioMode(filemode_t mode) { - switch(mode) { - case FILE_READ: return "rb"; - case FILE_BOTH: return "r+b"; - case FILE_WRITE: return "wb"; - default: fatal("mode not recognized: %d", mode); return ""; - } -} - -bool fileExists(const char *fname) { - return access(fname, F_OK) == 0; -} - -file_t fileOpen(const char *fname, filemode_t mode) { - return (file_t)(void*) fopen(fname, _toStdioMode(mode)); -} - -void fileClose(file_t ctx) { - if(ctx) { - fclose((FILE*)ctx); - } -} - -bool fileIsValid(file_t ctx) { - return (FILE *)ctx != NULL; -} - -bool filePutc(file_t ctx, char c) { - return fputc(c, (FILE*)ctx) == c; -} - -bool filePuts(file_t ctx, const char *str) { - return fputs(str, (FILE*)ctx) != EOF; -} - -bool filePutstr(file_t ctx, str_t str) { - return fileWrite(ctx, str.buf, str.len) == str.len; -} - -bool filePutview(file_t ctx, strview_t view) { - return fileWrite(ctx, view.buf, view.len) == view.len; -} - -usize fileRead(file_t ctx, void *buf, usize len) { - return fread(buf, 1, len, (FILE*)ctx); -} - -usize fileWrite(file_t ctx, const void *buf, usize len) { - return fwrite(buf, 1, len, (FILE*)ctx); -} - -bool fileSeekEnd(file_t ctx) { - return fseek((FILE*)ctx, 0, SEEK_END) == 0; -} - -void fileRewind(file_t ctx) { - rewind((FILE*)ctx); -} - -uint64 fileTell(file_t ctx) { - return (uint64)ftell((FILE*)ctx); -} -#endif - -static str_t _readWholeInternalStr(file_t ctx) { - str_t contents = strInit(); - uint64 fsize = 0; - usize read = 0; - - if(!fileSeekEnd(ctx)) { - err("file: couldn't read until end"); - goto failed; - } - - fsize = fileTell(ctx); - fileRewind(ctx); - - contents.buf = (char *)malloc(fsize + 1); - contents.len = fsize; - if(!contents.buf) { - err("file: couldn't allocate buffer"); - goto failed; - } - - read = fileRead(ctx, contents.buf, fsize); - if(read != fsize) { - err("file: read wrong amount of bytes: %zu instead of %zu", read, fsize); - goto failed_free; - } - - contents.buf[contents.len] = '\0'; - -failed: - return contents; -failed_free: - strFree(contents); - return strInit(); -} - -static vec(uint8) _readWholeInternalVec(file_t ctx) { - vec(uint8) contents = NULL; - uint64 fsize = 0; - usize read = 0; - - if(!fileSeekEnd(ctx)) { - err("file: couldn't read until end"); - goto failed; - } - - fsize = fileTell(ctx); - fileRewind(ctx); - - vecReserve(contents, fsize); - if(!contents) { - err("file: couldn't allocate buffer"); - goto failed; - } - - read = fileRead(ctx, contents, fsize); - if(read != fsize) { - err("file: read wrong amount of bytes: %zu instead of %zu", read, fsize); - goto failed_free; - } - - _veclen(contents) = read; - -failed: - return contents; -failed_free: - vecFree(contents); - return contents; -} - -vec(uint8) fileReadWhole(const char *fname) { - file_t fp = fileOpen(fname, FILE_READ); - if (!fileIsValid(fp)) return NULL; - vec(uint8) contents = fileReadWholeFP(fp); - fileClose(fp); - return contents; -} - -vec(uint8) fileReadWholeFP(file_t ctx) { - return _readWholeInternalVec(ctx); -} - -str_t fileReadWholeText(const char *fname) { - file_t fp = fileOpen(fname, FILE_READ); - if(!fileIsValid(fp)) { - err("couldn't open file %s", fname); - return strInit(); - } - str_t contents = fileReadWholeTextFP(fp); - fileClose(fp); - return contents; -} - -str_t fileReadWholeTextFP(file_t ctx) { - return _readWholeInternalStr(ctx); -} - -bool fileWriteWhole(const char *fname, filebuf_t data) { - file_t fp = fileOpen(fname, FILE_WRITE); - if (!fileIsValid(fp)) { - err("couldn't open file %s", fname); - return false; - } - bool res = fileWriteWholeFP(fp, data); - fileClose(fp); - return res; -} - -bool fileWriteWholeFP(file_t ctx, filebuf_t data) { - usize written = fileWrite(ctx, data.buf, data.len); - return written == data.len; -} - -bool fileWriteWholeText(const char *fname, strview_t string) { - file_t fp = fileOpen(fname, FILE_WRITE); - if (!fileIsValid(fp)) { - err("couldn't open file %s", fname); - return false; - } - bool res = fileWriteWholeTextFP(fp, string); - fileClose(fp); - return res; -} - -bool fileWriteWholeTextFP(file_t ctx, strview_t string) { - return fileWriteWholeFP(ctx, (filebuf_t){ (uint8 *)string.buf, string.len }); -} - -/* #include "dir.h" */ -/* #include "tracelog.h" */ - -#ifdef _WIN32 -/* #include "win32_slim.h" */ -#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.len += 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; -} - -void dirClose(dir_t ctx) { - free(ctx); -} - -bool dirValid(dir_t ctx) { - _dir_internal_t *dir = (_dir_internal_t*)ctx; - return dir->handle != INVALID_HANDLE_VALUE; -} - -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 dirCreate(const char *path) { - CreateDirectoryA(path, NULL); -} - -#else - -#include -#include -#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 = (_dir_internal_t *)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); - } -} - -bool dirValid(dir_t ctx) { - _dir_internal_t *dir = (_dir_internal_t*)ctx; - return dir->dir != NULL; -} - -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 = strFromStr(dp->d_name); - return &in->next; -} - -void dirCreate(const char *path) { - mkdir(path, 0700); -} - -#endif - -#ifndef COLLA_NO_NET -/* #include "socket.h" */ - -#include -/* #include "tracelog.h" */ - -#ifndef NDEBUG -// VERY MUCH NOT THREAD SAFE -static int initialize_count = 0; -#endif - -#if SOCK_WINDOWS -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_t 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, sk_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; - sk_len_t addr_size = (sk_len_t)sizeof(addr); - return skAcceptPro(sock, (sk_addr_t *) &addr, &addr_size); -} - -socket_t skAcceptPro(socket_t sock, sk_addr_t *addr, sk_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, sk_len_t namelen) { - return connect(sock, name, namelen) != SOCKET_ERROR; -} - -int skSend(socket_t sock, const void *buf, int len) { - return skSendPro(sock, buf, len, 0); -} - -int skSendPro(socket_t sock, const void *buf, int len, int flags) { - return send(sock, buf, len, flags); -} - -int skSendTo(socket_t sock, const void *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 void *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, void *buf, int len) { - return skReceivePro(sock, buf, len, 0); -} - -int skReceivePro(socket_t sock, void *buf, int len, int flags) { - return recv(sock, buf, len, flags); -} - -int skReceiveFrom(socket_t sock, void *buf, int len, sk_addrin_t *from) { - sk_len_t fromlen = sizeof(sk_addr_t); - return skReceiveFromPro(sock, buf, len, 0, (sk_addr_t*)from, &fromlen); -} - -int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, sk_addr_t *from, sk_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 - -/* #include "http.h" */ - -#include -#include -#include - -/* #include "os.h" */ -/* #include "tracelog.h" */ - -/* #include "vec.h" */ - -// == INTERNAL ================================================================ - -static void _setField(vec(http_field_t) *fields_vec, const char *key, const char *value) { - vec(http_field_t) fields = *fields_vec; - - for (uint32 i = 0; i < vecLen(fields); ++i) { - if (stricmp(fields[i].key, key) == 0) { - char **curval = &fields[i].value; - usize curlen = strlen(*curval); - usize newlen = strlen(value); - if(newlen > curlen) { - *curval = (char *)realloc(*curval, newlen + 1); - } - memcpy(*curval, value, newlen); - (*curval)[newlen] = '\0'; - return; - } - } - - // otherwise, add it to the list - http_field_t field; - usize klen = strlen(key); - usize vlen = strlen(value); - field.key = (char *)malloc(klen + 1); - field.value = (char *)malloc(vlen + 1); - memcpy(field.key, key, klen); - memcpy(field.value, value, vlen); - field.key[klen] = field.value[vlen] = '\0'; - - vecAppend(*fields_vec, 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, strvInit("/")); - req.version = (http_version_t){1, 1}; - return req; -} - -void reqFree(http_request_t *ctx) { - for (uint32 i = 0; i < vecLen(ctx->fields); ++i) { - free(ctx->fields[i].key); - free(ctx->fields[i].value); - } - 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(uint32 i = 0; i < vecLen(ctx->fields); ++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, key, value); -} - -void reqSetUri(http_request_t *ctx, strview_t uri) { - if (strvIsEmpty(uri)) return; - free(ctx->uri); - if (uri.buf[0] != '/') { - ctx->uri = (char *)realloc(ctx->uri, uri.len + 1); - ctx->uri[0] = '/'; - memcpy(ctx->uri + 1, uri.buf, uri.len); - ctx->uri[uri.len] = '\0'; - } - else { - ctx->uri = strvCopy(uri).buf; - } -} - -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(uint32 i = 0; i < vecLen(ctx->fields); ++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; -} - -str_t reqString(http_request_t *ctx) { - str_ostream_t out = reqPrepare(ctx); - return ostrAsStr(out); -} - -// == HTTP RESPONSE =========================================================== - -http_response_t resInit() { - return (http_response_t) {0}; -} - -void resFree(http_response_t *ctx) { - for(uint32 i = 0; i < vecLen(ctx->fields); ++i) { - free(ctx->fields[i].key); - free(ctx->fields[i].value); - } - vecFree(ctx->fields); - vecFree(ctx->body); - memset(ctx, 0, sizeof(http_response_t)); -} - -bool resHasField(http_response_t *ctx, const char *key) { - for(uint32 i = 0; i < vecLen(ctx->fields); ++i) { - if(stricmp(ctx->fields[i].key, key) == 0) { - return true; - } - } - return false; -} - -const char *resGetField(http_response_t *ctx, const char *field) { - for(uint32 i = 0; i < vecLen(ctx->fields); ++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, (int32*)&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); - vecClear(ctx->body); - vecReserve(ctx->body, body.len); - memcpy(ctx->body, body.buf, body.len); - } - 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'); - - usize pos = strvFind(line, ':', 0); - if(pos != STRV_NOT_FOUND) { - strview_t key = strvSub(line, 0, pos); - strview_t value = strvSub(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() { - return (http_client_t) { - .port = 80, - }; -} - -void hcliFree(http_client_t *ctx) { - strFree(ctx->host_name); - memset(ctx, 0, sizeof(http_client_t)); -} - -void hcliSetHost(http_client_t *ctx, strview_t hostname) { - // if the hostname starts with http:// (case insensitive) - if(strvICompare(strvSub(hostname, 0, 7), strvInit("http://")) == 0) { - ctx->host_name = strvCopy(strvSub(hostname, 7, SIZE_MAX)); - } - else if(strvICompare(strvSub(hostname, 0, 8), strvInit("https://")) == 0) { - err("HTTPS protocol not yet supported"); - return; - } - else { - // undefined protocol, use HTTP - ctx->host_name = strvCopy(hostname); - } -} - -http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *req) { - if (strBack(ctx->host_name) == '/') { - ctx->host_name.buf[--ctx->host_name.len] = '\0'; - } - if(!reqHasField(req, "Host")) { - reqSetField(req, "Host", ctx->host_name.buf); - } - 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.buf, 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.len) != '\0') { - ostrPutc(&received, '\0'); - received.len--; - } - - resParse(&res, received.buf); - } - else { - err("Couldn't connect to host %s -> %s", ctx->host_name, skGetErrorString()); - } - - 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(strview_t hostname, strview_t 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; -} - -url_split_t urlSplit(strview_t uri) { - url_split_t out = {0}; - - if (strvStartsWithView(uri, strvInit("https://"))) { - uri = strvRemovePrefix(uri, 8); - } - else if (strvStartsWithView(uri, strvInit("http://"))) { - uri = strvRemovePrefix(uri, 7); - } - - out.host = strvSub(uri, 0, strvFind(uri, '/', 0)); - out.uri = strvSub(uri, out.host.len, SIZE_MAX); - return out; -} - -#endif // COLLA_NO_NET -#if !defined(__TINYC__) && !defined(COLLA_NO_THREADS) -/* #include "cthreads.h" */ - -typedef struct { - cthread_func_t func; - void *arg; -} _thr_internal_t; - -#ifdef _WIN32 -/* #include "win32_slim.h" */ -#include - -// == THREAD =========================================== - -static DWORD _thrFuncInternal(void *arg) { - _thr_internal_t *params = (_thr_internal_t *)arg; - cthread_func_t func = params->func; - void *argument = params->arg; - free(params); - return (DWORD)func(argument); -} - -cthread_t thrCreate(cthread_func_t func, void *arg) { - HANDLE thread = INVALID_HANDLE_VALUE; - _thr_internal_t *params = malloc(sizeof(_thr_internal_t)); - - if(params) { - params->func = func; - params->arg = arg; - - thread = CreateThread(NULL, 0, _thrFuncInternal, params, 0, NULL); - } - - return (cthread_t)thread; -} - -bool thrValid(cthread_t ctx) { - return (HANDLE)ctx != INVALID_HANDLE_VALUE; -} - -bool thrDetach(cthread_t ctx) { - return CloseHandle((HANDLE)ctx); -} - -cthread_t thrCurrent(void) { - return (cthread_t)GetCurrentThread(); -} - -int thrCurrentId(void) { - return GetCurrentThreadId(); -} - -int thrGetId(cthread_t ctx) { - return GetThreadId((HANDLE)ctx); -} - -void thrExit(int code) { - ExitThread(code); -} - -bool thrJoin(cthread_t ctx, int *code) { - if(!ctx) return false; - int return_code = WaitForSingleObject((HANDLE)ctx, INFINITE); - if(code) *code = return_code; - BOOL success = CloseHandle((HANDLE)ctx); - return return_code != WAIT_FAILED && success; -} - -// == MUTEX ============================================ - -cmutex_t mtxInit(void) { - CRITICAL_SECTION *crit_sec = malloc(sizeof(CRITICAL_SECTION)); - if(crit_sec) { - InitializeCriticalSection(crit_sec); - } - return (cmutex_t)crit_sec; -} - -void mtxDestroy(cmutex_t ctx) { - DeleteCriticalSection((CRITICAL_SECTION *)ctx); -} - -bool mtxValid(cmutex_t ctx) { - return (void *)ctx != NULL; -} - -bool mtxLock(cmutex_t ctx) { - EnterCriticalSection((CRITICAL_SECTION *)ctx); - return true; -} - -bool mtxTryLock(cmutex_t ctx) { - return TryEnterCriticalSection((CRITICAL_SECTION *)ctx); -} - -bool mtxUnlock(cmutex_t ctx) { - LeaveCriticalSection((CRITICAL_SECTION *)ctx); - return true; -} - - -#else -#include -#include -#include -#include -#include - -// == THREAD =========================================== - -#define INT_TO_VOIDP(a) ((void *)((uintptr_t)(a))) - -static void *_thrFuncInternal(void *arg) { - _thr_internal_t *params = (_thr_internal_t *)arg; - cthread_func_t func = params->func; - void *argument = params->arg; - free(params); - return INT_TO_VOIDP(func(argument)); -} - -cthread_t thrCreate(cthread_func_t func, void *arg) { - pthread_t handle = (pthread_t)NULL; - - _thr_internal_t *params = malloc(sizeof(_thr_internal_t)); - - if(params) { - params->func = func; - params->arg = arg; - - int result = pthread_create(&handle, NULL, _thrFuncInternal, params); - if(result) handle = (pthread_t)NULL; - } - - return (cthread_t)handle; -} - -bool thrValid(cthread_t ctx) { - return (void *)ctx != NULL; -} - -bool thrDetach(cthread_t ctx) { - return pthread_detach((pthread_t)ctx) == 0; -} - -cthread_t thrCurrent(void) { - return (cthread_t)pthread_self(); -} - -int thrCurrentId(void) { - return (int)pthread_self(); -} - -int thrGetId(cthread_t ctx) { - return (int)ctx; -} - -void thrExit(int code) { - pthread_exit(INT_TO_VOIDP(code)); -} - -bool thrJoin(cthread_t ctx, int *code) { - void *result = code; - return pthread_join((pthread_t)ctx, &result) != 0; -} - -// == MUTEX ============================================ - -cmutex_t mtxInit(void) { - pthread_mutex_t *mutex = malloc(sizeof(pthread_mutex_t)); - - if(mutex) { - int res = pthread_mutex_init(mutex, NULL); - if(res != 0) mutex = NULL; - } - - return (cmutex_t)mutex; -} - -void mtxDestroy(cmutex_t ctx) { - pthread_mutex_destroy((pthread_mutex_t *)ctx); -} - -bool mtxValid(cmutex_t ctx) { - return (void *)ctx != NULL; -} - -bool mtxLock(cmutex_t ctx) { - return pthread_mutex_lock((pthread_mutex_t *)ctx) == 0; -} - -bool mtxTryLock(cmutex_t ctx) { - return pthread_mutex_trylock((pthread_mutex_t *)ctx) == 0; -} - -bool mtxUnlock(cmutex_t ctx) { - return pthread_mutex_unlock((pthread_mutex_t *)ctx) == 0; -} - -#endif - -#endif // !defined(__TINYC__) && !defined(COLLA_NO_THREADS) -#endif /* COLLA_IMPL */ -/* - MIT License - Copyright (c) 1994-2019 Lua.org, PUC-Rio. - Copyright (c) 2020-2022 snarmph. - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -*/ diff --git a/colla/arena.c b/colla/arena.c new file mode 100644 index 0000000..14a1982 --- /dev/null +++ b/colla/arena.c @@ -0,0 +1,198 @@ +#include "arena.h" + +#include +#include +#include +#include + +#include "vmem.h" +#include "tracelog.h" + +static uintptr_t arena__align(uintptr_t ptr, usize align) { + return (ptr + (align - 1)) & ~(align - 1); +} + +static arena_t arena__make_virtual(usize size); +static arena_t arena__make_malloc(usize size); +static arena_t arena__make_static(byte *buf, usize len); + +static void arena__free_virtual(arena_t *arena); +static void arena__free_malloc(arena_t *arena); + +arena_t arenaInit(const arena_desc_t *desc) { + if (desc) { + switch (desc->type) { + case ARENA_VIRTUAL: return arena__make_virtual(desc->allocation); + case ARENA_MALLOC: return arena__make_malloc(desc->allocation); + case ARENA_STATIC: return arena__make_static(desc->static_buffer, desc->allocation); + } + } + + debug("couldn't init arena: %p %d\n", desc, desc ? desc->type : 0); + return (arena_t){0}; +} + +void arenaCleanup(arena_t *arena) { + if (!arena) { + return; + } + + switch (arena->type) { + case ARENA_VIRTUAL: arena__free_virtual(arena); break; + case ARENA_MALLOC: arena__free_malloc(arena); break; + // ARENA_STATIC does not need to be freed + case ARENA_STATIC: break; + } + + memset(arena, 0, sizeof(arena_t)); +} + +arena_t arenaScratch(arena_t *arena) { + if (!arena) { + return (arena_t){0}; + } + + return (arena_t) { + .start = arena->current, + .current = arena->current, + .end = arena->end, + .type = arena->type, + }; +} + +void *arenaAlloc(const arena_alloc_desc_t *desc) { + if (!desc || !desc->arena) { + return NULL; + } + + usize total = desc->size * desc->count; + arena_t *arena = desc->arena; + + arena->current = (byte *)arena__align((uintptr_t)arena->current, desc->align); + + if (total > arenaRemaining(arena)) { + if (desc->flags & ALLOC_SOFT_FAIL) { + return NULL; + } + printf("finished space in arena, tried to allocate %zu bytes out of %zu\n", total, arenaRemaining(arena)); + abort(); + } + + if (arena->type == ARENA_VIRTUAL) { + usize allocated = arenaTell(arena); + usize page_end = vmemPadToPage(allocated); + usize new_cur = allocated + total; + + if (new_cur > page_end) { + usize extra_mem = vmemPadToPage(new_cur - page_end); + usize page_size = vmemGetPageSize(); + // TODO is this really correct? + usize num_of_pages = (extra_mem / page_size) + 1; + + assert(num_of_pages > 0); + + if (!vmemCommit(arena->current, num_of_pages + 1)) { + if (desc->flags & ALLOC_SOFT_FAIL) { + return NULL; + } + printf("failed to commit memory for virtual arena, tried to commit %zu pages\n", num_of_pages); + exit(1); + } + } + } + + byte *ptr = arena->current; + arena->current += total; + + if (desc->flags & ALLOC_NOZERO) return ptr; + + memset(ptr, 0, total); + + return ptr; + + return desc->flags & ALLOC_NOZERO ? ptr : memset(ptr, 0, total); +} + +usize arenaTell(arena_t *arena) { + return arena ? arena->current - arena->start : 0; +} + +usize arenaRemaining(arena_t *arena) { + return arena && (arena->current < arena->end) ? arena->end - arena->current : 0; +} + +void arenaRewind(arena_t *arena, usize from_start) { + if (!arena) { + return; + } + + assert(arenaTell(arena) >= from_start); + + arena->current = arena->start + from_start; +} + +void arenaPop(arena_t *arena, usize amount) { + if (!arena) { + return; + } + usize position = arenaTell(arena); + if (!position) { + return; + } + arenaRewind(arena, position - amount); +} + +// == VIRTUAL ARENA ==================================================================================================== + +static arena_t arena__make_virtual(usize size) { + usize alloc_size = 0; + byte *ptr = vmemInit(size, &alloc_size); + if (!vmemCommit(ptr, 1)) { + vmemRelease(ptr); + ptr = NULL; + } + + return (arena_t){ + .start = ptr, + .current = ptr, + .end = ptr ? ptr + alloc_size : NULL, + .type = ARENA_VIRTUAL, + }; +} + +static void arena__free_virtual(arena_t *arena) { + if (!arena->start) { + return; + } + + bool success = vmemRelease(arena->start); + assert(success && "Failed arena free"); +} + +// == MALLOC ARENA ===================================================================================================== + +static arena_t arena__make_malloc(usize size) { + byte *ptr = malloc(size); + assert(ptr); + return (arena_t) { + .start = ptr, + .current = ptr, + .end = ptr ? ptr + size : NULL, + .type = ARENA_MALLOC, + }; +} + +static void arena__free_malloc(arena_t *arena) { + free(arena->start); +} + +// == STATIC ARENA ===================================================================================================== + +static arena_t arena__make_static(byte *buf, usize len) { + return (arena_t) { + .start = buf, + .current = buf, + .end = buf ? buf + len : NULL, + .type = ARENA_STATIC, + }; +} diff --git a/colla/arena.h b/colla/arena.h new file mode 100644 index 0000000..fdd1511 --- /dev/null +++ b/colla/arena.h @@ -0,0 +1,57 @@ +#pragma once + +#include "collatypes.h" + +#define alignof _Alignof + +typedef enum { + ARENA_VIRTUAL, + ARENA_MALLOC, + ARENA_STATIC, +} arena_type_e; + +typedef enum { + ALLOC_FLAGS_NONE = 0, + ALLOC_NOZERO = 1 << 0, + ALLOC_SOFT_FAIL = 1 << 1, +} alloc_flags_e; + +typedef struct arena_t { + uint8 *start; + uint8 *current; + uint8 *end; + arena_type_e type; +} arena_t; + +typedef struct { + arena_type_e type; + usize allocation; + byte *static_buffer; +} arena_desc_t; + +typedef struct { + arena_t *arena; + usize count; + alloc_flags_e flags; + usize size; + usize align; +} arena_alloc_desc_t; + +#define KB(count) ( (count) * 1024) +#define MB(count) (KB(count) * 1024) +#define GB(count) (MB(count) * 1024) + +// arena_type_e type, usize allocation, [ byte *static_buffer ] +#define arenaMake(...) arenaInit(&(arena_desc_t){ __VA_ARGS__ }) + +// arena_t *arena, T type, [ usize count, alloc_flags_e flags, usize size, usize align ] +#define alloc(arenaptr, type, ...) arenaAlloc(&(arena_alloc_desc_t){ .size = sizeof(type), .count = 1, .align = alignof(type), .arena = arenaptr, __VA_ARGS__ }) + +arena_t arenaInit(const arena_desc_t *desc); +void arenaCleanup(arena_t *arena); + +void *arenaAlloc(const arena_alloc_desc_t *desc); +usize arenaTell(arena_t *arena); +usize arenaRemaining(arena_t *arena); +void arenaRewind(arena_t *arena, usize from_start); +void arenaPop(arena_t *arena, usize amount); diff --git a/colla/base64.c b/colla/base64.c new file mode 100644 index 0000000..eaccb62 --- /dev/null +++ b/colla/base64.c @@ -0,0 +1,100 @@ +#include "base64.h" + +#include "warnings/colla_warn_beg.h" + +#include "arena.h" + +static char encoding_table[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' +}; + +static uint8 decoding_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +buffer_t base64Encode(arena_t *arena, buffer_t buffer) { + usize outlen = ((buffer.len + 2) / 3) * 4; + uint8 *out = alloc(arena, uint8, outlen); + + for (usize i = 0, j = 0; i < buffer.len;) { + uint32 a = i < buffer.len ? buffer.data[i++] : 0; + uint32 b = i < buffer.len ? buffer.data[i++] : 0; + uint32 c = i < buffer.len ? buffer.data[i++] : 0; + + uint32 triple = (a << 16) | (b << 8) | c; + + out[j++] = encoding_table[(triple >> 18) & 0x3F]; + out[j++] = encoding_table[(triple >> 12) & 0x3F]; + out[j++] = encoding_table[(triple >> 6) & 0x3F]; + out[j++] = encoding_table[(triple >> 0) & 0x3F]; + } + + usize mod = buffer.len % 3; + if (mod) { + mod = 3 - mod; + for (usize i = 0; i < mod; ++i) { + out[outlen - 1 - i] = '='; + } + } + + return (buffer_t){ + .data = out, + .len = outlen + }; +} + +buffer_t base64Decode(arena_t *arena, buffer_t buffer) { + uint8 *out = arena->current; + usize start = arenaTell(arena); + + for (usize i = 0; i < buffer.len; i += 4) { + uint8 a = decoding_table[buffer.data[i + 0]]; + uint8 b = decoding_table[buffer.data[i + 1]]; + uint8 c = decoding_table[buffer.data[i + 2]]; + uint8 d = decoding_table[buffer.data[i + 3]]; + + uint32 triple = + ((uint32)a << 18) | + ((uint32)b << 12) | + ((uint32)c << 6) | + ((uint32)d); + + uint8 *bytes = alloc(arena, uint8, 3); + + bytes[0] = (triple >> 16) & 0xFF; + bytes[1] = (triple >> 8) & 0xFF; + bytes[2] = (triple >> 0) & 0xFF; + } + + usize outlen = arenaTell(arena) - start; + + return (buffer_t){ + .data = out, + .len = outlen, + }; +} + +#include "warnings/colla_warn_end.h" diff --git a/colla/base64.h b/colla/base64.h new file mode 100644 index 0000000..de4ef10 --- /dev/null +++ b/colla/base64.h @@ -0,0 +1,9 @@ +#pragma once + +#include "collatypes.h" +#include "str.h" + +typedef struct arena_t arena_t; + +buffer_t base64Encode(arena_t *arena, buffer_t buffer); +buffer_t base64Decode(arena_t *arena, buffer_t buffer); \ No newline at end of file diff --git a/colla/colladefines.h b/colla/colladefines.h new file mode 100644 index 0000000..7e30b7a --- /dev/null +++ b/colla/colladefines.h @@ -0,0 +1,79 @@ +#pragma once + +#define arrlen(a) (sizeof(a) / sizeof((a)[0])) + +#if defined(_DEBUG) || !defined(NDEBUG) +#define COLLA_DEBUG 1 +#define COLLA_RELEASE 0 +#else +#define COLLA_DEBUG 0 +#define COLLA_RELEASE 1 +#endif + +#if defined(_WIN32) + +#define COLLA_WIN 1 +#define COLLA_OSX 0 +#define COLLA_LIN 0 + +#elif defined(__APPLE__) + +#define COLLA_WIN 0 +#define COLLA_OSX 1 +#define COLLA_LIN 0 + +#elif defined(__linux__) + +#define COLLA_WIN 0 +#define COLLA_OSX 0 +#define COLLA_LIN 1 + +#endif + +#if defined(__clang__) + +#define COLLA_CLANG 1 +#define COLLA_MSVC 0 +#define COLLA_TCC 0 +#define COLLA_GCC 0 + +#elif defined(_MSC_VER) + +#define COLLA_CLANG 0 +#define COLLA_MSVC 1 +#define COLLA_TCC 0 +#define COLLA_GCC 0 + +#elif defined(__TINYC__) + +#define COLLA_CLANG 0 +#define COLLA_MSVC 0 +#define COLLA_TCC 1 +#define COLLA_GCC 0 + +#elif defined(__GNUC__) + +#define COLLA_CLANG 0 +#define COLLA_MSVC 0 +#define COLLA_TCC 0 +#define COLLA_GCC 1 + +#endif + +#if COLLA_CLANG + +#define COLLA_CMT_LIB 0 + +#elif COLLA_MSVC + +#define COLLA_CMT_LIB 1 + +#elif COLLA_TCC + +#define COLLA_CMT_LIB 1 + +#elif COLLA_GCC + +#define COLLA_CMT_LIB 0 + +#endif diff --git a/colla/collatypes.h b/colla/collatypes.h index f8d5401..b62af8a 100644 --- a/colla/collatypes.h +++ b/colla/collatypes.h @@ -2,6 +2,9 @@ #include #include +#include + +#include "colladefines.h" typedef unsigned char uchar; typedef unsigned short ushort; @@ -19,3 +22,16 @@ typedef int64_t int64; typedef size_t usize; typedef ptrdiff_t isize; + +typedef uint8 byte; + +typedef struct { + uint8 *data; + usize len; +} buffer_t; + +#if COLLA_WIN && defined(UNICODE) +typedef wchar_t TCHAR; +#else +typedef char TCHAR; +#endif diff --git a/colla/cthreads.c b/colla/cthreads.c index 923c394..92227f6 100644 --- a/colla/cthreads.c +++ b/colla/cthreads.c @@ -1,13 +1,23 @@ #include "cthreads.h" +#include + typedef struct { cthread_func_t func; void *arg; } _thr_internal_t; -#ifdef _WIN32 -#include "win32_slim.h" -#include +#if COLLA_WIN +#define WIN32_LEAN_AND_MEAN +#include +#include +#include + +#undef INFINITE +#undef WAIT_FAILED +// couple of defines to avoid including windows.h +#define INFINITE 0xFFFFFFFF // Infinite timeout +#define WAIT_FAILED ((DWORD)0xFFFFFFFF) // == THREAD =========================================== @@ -21,7 +31,7 @@ static DWORD _thrFuncInternal(void *arg) { cthread_t thrCreate(cthread_func_t func, void *arg) { HANDLE thread = INVALID_HANDLE_VALUE; - _thr_internal_t *params = malloc(sizeof(_thr_internal_t)); + _thr_internal_t *params = calloc(1, sizeof(_thr_internal_t)); if(params) { params->func = func; @@ -68,7 +78,7 @@ bool thrJoin(cthread_t ctx, int *code) { // == MUTEX ============================================ cmutex_t mtxInit(void) { - CRITICAL_SECTION *crit_sec = malloc(sizeof(CRITICAL_SECTION)); + CRITICAL_SECTION *crit_sec = calloc(1, sizeof(CRITICAL_SECTION)); if(crit_sec) { InitializeCriticalSection(crit_sec); } @@ -100,10 +110,10 @@ bool mtxUnlock(cmutex_t ctx) { // == CONDITION VARIABLE =============================== -#include +#include "tracelog.h" condvar_t condInit(void) { - CONDITION_VARIABLE *cond = malloc(sizeof(CONDITION_VARIABLE)); + CONDITION_VARIABLE *cond = calloc(1, sizeof(CONDITION_VARIABLE)); InitializeConditionVariable(cond); return (condvar_t)cond; } @@ -121,7 +131,7 @@ void condWakeAll(condvar_t cond) { } void condWait(condvar_t cond, cmutex_t mtx) { - BOOL res = SleepConditionVariableCS((CONDITION_VARIABLE *)cond, (CRITICAL_SECTION *)mtx, INFINITE); + SleepConditionVariableCS((CONDITION_VARIABLE *)cond, (CRITICAL_SECTION *)mtx, INFINITE); } void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) { @@ -130,7 +140,6 @@ void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) { #else #include -#include #include #include #include @@ -150,7 +159,7 @@ static void *_thrFuncInternal(void *arg) { cthread_t thrCreate(cthread_func_t func, void *arg) { pthread_t handle = (pthread_t)NULL; - _thr_internal_t *params = malloc(sizeof(_thr_internal_t)); + _thr_internal_t *params = calloc(1, sizeof(_thr_internal_t)); if(params) { params->func = func; @@ -195,7 +204,7 @@ bool thrJoin(cthread_t ctx, int *code) { // == MUTEX ============================================ cmutex_t mtxInit(void) { - pthread_mutex_t *mutex = malloc(sizeof(pthread_mutex_t)); + pthread_mutex_t *mutex = calloc(1, sizeof(pthread_mutex_t)); if(mutex) { if(pthread_mutex_init(mutex, NULL)) { @@ -230,7 +239,7 @@ bool mtxUnlock(cmutex_t ctx) { // == CONDITION VARIABLE =============================== condvar_t condInit(void) { - pthread_cond_t *cond = malloc(sizeof(pthread_cond_t)); + pthread_cond_t *cond = calloc(1, sizeof(pthread_cond_t)); if(cond) { if(pthread_cond_init(cond, NULL)) { diff --git a/colla/cthreads.h b/colla/cthreads.h index 944b3cd..e7e6a01 100644 --- a/colla/cthreads.h +++ b/colla/cthreads.h @@ -1,11 +1,6 @@ #pragma once -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include +#include "collatypes.h" // == THREAD =========================================== @@ -37,31 +32,6 @@ bool mtxLock(cmutex_t ctx); bool mtxTryLock(cmutex_t ctx); bool mtxUnlock(cmutex_t ctx); -#ifdef __cplusplus -// small c++ class to make mutexes easier to use -struct lock_t { - inline lock_t(cmutex_t mutex) - : mutex(mutex) { - if (mtxValid(mutex)) { - mtxLock(mutex); - } - } - - inline ~lock_t() { - unlock(); - } - - inline void unlock() { - if (mtxValid(mutex)) { - mtxUnlock(mutex); - } - mutex = 0; - } - - cmutex_t mutex; -}; -#endif - // == CONDITION VARIABLE =============================== typedef uintptr_t condvar_t; @@ -76,7 +46,3 @@ void condWakeAll(condvar_t cond); void condWait(condvar_t cond, cmutex_t mtx); void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds); - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/colla/file.c b/colla/file.c index 13a028c..1c7391b 100644 --- a/colla/file.c +++ b/colla/file.c @@ -1,311 +1,288 @@ #include "file.h" +#include "warnings/colla_warn_beg.h" + #include "tracelog.h" +#include "format.h" -#ifdef _WIN32 -#include "win32_slim.h" -#include +#if COLLA_WIN -static DWORD _toWin32Access(int mode) { - if(mode & FILE_READ) return GENERIC_READ; - if(mode & FILE_WRITE) return GENERIC_WRITE; - if(mode & FILE_BOTH) return GENERIC_READ | GENERIC_WRITE; - fatal("unrecognized access mode: %d", mode); - return 0; +#define WIN32_LEAN_AND_MEAN +#include +#include + +#undef FILE_BEGIN +#undef FILE_CURRENT +#undef FILE_END + +#define FILE_BEGIN 0 +#define FILE_CURRENT 1 +#define FILE_END 2 + +static DWORD file__mode_to_access(filemode_e mode) { + if (mode & FILE_APPEND) return FILE_APPEND_DATA; + + DWORD out = 0; + if (mode & FILE_READ) out |= GENERIC_READ; + if (mode & FILE_WRITE) out |= GENERIC_WRITE; + return out; } -static DWORD _toWin32Creation(filemode_t mode) { - if(mode & FILE_READ) return OPEN_EXISTING; - if(mode == (FILE_WRITE | FILE_CLEAR)) return CREATE_ALWAYS; - if(mode & FILE_WRITE) return OPEN_ALWAYS; - if(mode & FILE_BOTH) return OPEN_ALWAYS; - fatal("unrecognized creation mode: %d", mode); - return 0; +static DWORD file__mode_to_creation(filemode_e mode) { + if (mode == FILE_READ) return OPEN_EXISTING; + if (mode == FILE_WRITE) return CREATE_ALWAYS; + return OPEN_ALWAYS; } -bool fileExists(const char *fname) { - return GetFileAttributesA(fname) != INVALID_FILE_ATTRIBUTES; +bool fileExists(const char *name) { + return GetFileAttributesA(name) != INVALID_FILE_ATTRIBUTES; } -file_t fileOpen(const char *fname, filemode_t mode) { - return (file_t)CreateFileA( - fname, - _toWin32Access(mode), - 0, - NULL, - _toWin32Creation(mode), - FILE_ATTRIBUTE_NORMAL, +file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode) { + TCHAR long_path_prefix[] = TEXT("\\\\?\\"); + const usize prefix_len = arrlen(long_path_prefix) - 1; + + TCHAR *rel_path = strvToTChar(&scratch, name); + DWORD pathlen = GetFullPathName(rel_path, 0, NULL, NULL); + + TCHAR *full_path = alloc(&scratch, TCHAR, pathlen + prefix_len + 1); + memcpy(full_path, long_path_prefix, prefix_len * sizeof(TCHAR)); + + GetFullPathName(rel_path, pathlen + 1, full_path + prefix_len, NULL); + + HANDLE handle = CreateFile( + full_path, + file__mode_to_access(mode), + 0, + NULL, + file__mode_to_creation(mode), + FILE_ATTRIBUTE_NORMAL, NULL ); + + return (file_t){ + .handle = (uintptr_t)handle, + }; } void fileClose(file_t ctx) { - if (ctx) { - CloseHandle((HANDLE)ctx); + if (!fileIsValid(ctx)) return; + CloseHandle((HANDLE)ctx.handle); +} + +bool fileIsValid(file_t ctx) { + return (HANDLE)ctx.handle != 0 && + (HANDLE)ctx.handle != INVALID_HANDLE_VALUE; +} + +usize fileRead(file_t ctx, void *buf, usize len) { + if (!fileIsValid(ctx)) return 0; + DWORD read = 0; + ReadFile((HANDLE)ctx.handle, buf, len, &read, NULL); + return (usize)read; +} + +usize fileWrite(file_t ctx, const void *buf, usize len) { + if (!fileIsValid(ctx)) return 0; + DWORD written = 0; + WriteFile((HANDLE)ctx.handle, buf, len, &written, NULL); + return (usize)written; +} + +bool fileSeekEnd(file_t ctx) { + if (!fileIsValid(ctx)) return false; + DWORD result = SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_END); + return result != INVALID_SET_FILE_POINTER; +} + +void fileRewind(file_t ctx) { + if (!fileIsValid(ctx)) return; + SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_BEGIN); +} + +usize fileTell(file_t ctx) { + if (!fileIsValid(ctx)) return 0; + LARGE_INTEGER tell = {0}; + BOOL result = SetFilePointerEx((HANDLE)ctx.handle, (LARGE_INTEGER){0}, &tell, FILE_CURRENT); + return result == TRUE ? (usize)tell.QuadPart : 0; +} + +usize fileSize(file_t ctx) { + if (!fileIsValid(ctx)) return 0; + LARGE_INTEGER size = {0}; + BOOL result = GetFileSizeEx((HANDLE)ctx.handle, &size); + return result == TRUE ? (usize)size.QuadPart : 0; +} + +uint64 fileGetTimeFP(file_t ctx) { + if (!fileIsValid(ctx)) return 0; + FILETIME time = {0}; + GetFileTime((HANDLE)ctx.handle, NULL, NULL, &time); + ULARGE_INTEGER utime = { + .HighPart = time.dwHighDateTime, + .LowPart = time.dwLowDateTime, + }; + return (uint64)utime.QuadPart; +} + +#else + +#include + +static const char *file__mode_to_stdio(filemode_e mode) { + if (mode == FILE_READ) return "rb"; + if (mode == FILE_WRITE) return "wb"; + if (mode == FILE_APPEND) return "ab"; + if (mode == (FILE_READ | FILE_WRITE)) return "rb+"; + + return "ab+"; +} + +bool fileExists(const char *name) { + FILE *fp = fopen(name, "rb"); + bool exists = fp != NULL; + fclose(fp); + return exists; +} + +file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode) { + str_t filename = str(&scratch, name); + return (file_t) { + .handle = (uintptr_t)fopen(filename.buf, file__mode_to_stdio(mode)) + }; +} + +void fileClose(file_t ctx) { + FILE *fp = (FILE *)ctx.handle; + if (fp) { + fclose(fp); } } bool fileIsValid(file_t ctx) { - return (HANDLE)ctx != INVALID_HANDLE_VALUE; + return (FILE *)ctx.handle != NULL; } +usize fileRead(file_t ctx, void *buf, usize len) { + if (!fileIsValid(ctx)) return 0; + return fread(buf, 1, len, (FILE *)ctx.handle); +} + +usize fileWrite(file_t ctx, const void *buf, usize len) { + if (!fileIsValid(ctx)) return 0; + return fwrite(buf, 1, len, (FILE *)ctx.handle); +} + +bool fileSeekEnd(file_t ctx) { + if (!fileIsValid(ctx)) return false; + return fseek((FILE *)ctx.handle, 0, SEEK_END) == 0; +} + +void fileRewind(file_t ctx) { + if (!fileIsValid(ctx)) return; + return fseek((FILE *)ctx.handle, 0, SEEK_SET) == 0; +} + +usize fileTell(file_t ctx) { + if (!fileIsValid(ctx)) return 0; + return ftell((FILE *)ctx.handle); +} + +usize fileSize(file_t ctx) { + if (!fileIsValid(ctx)) return 0; + FILE *fp = (FILE *)ctx.handle; + fseek(fp, 0, SEEK_END); + long len = ftell(fp); + fseek(fp, 0, SEEK_SET); + return (usize)len; +} + +uint64 fileGetTimeFP(file_t ctx) { +#if COLLA_LIN +#else + fatal("fileGetTime not implemented yet outside of linux and windows"); + return 0; +#endif +} + +#endif + bool filePutc(file_t ctx, char c) { return fileWrite(ctx, &c, 1) == 1; } -bool filePuts(file_t ctx, const char *str) { - usize len = strlen(str); - return fileWrite(ctx, str, len) == len; +bool filePuts(file_t ctx, strview_t v) { + return fileWrite(ctx, v.buf, v.len) == v.len; } -bool filePutstr(file_t ctx, str_t str) { - return fileWrite(ctx, str.buf, str.len) == str.len; +bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + bool result = filePrintfv(scratch, ctx, fmt, args); + va_end(args); + return result; } -bool filePutview(file_t ctx, strview_t view) { - return fileWrite(ctx, view.buf, view.len) == view.len; +bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args) { + str_t string = strFmtv(&scratch, fmt, args); + return fileWrite(ctx, string.buf, string.len) == string.len; } -usize fileRead(file_t ctx, void *buf, usize len) { - DWORD bytes_read = 0; - BOOL result = ReadFile((HANDLE)ctx, buf, (DWORD)len, &bytes_read, NULL); - return result == TRUE ? (usize)bytes_read : 0; +buffer_t fileReadWhole(arena_t *arena, arena_t scratch, strview_t name) { + return fileReadWholeFP(arena, fileOpen(scratch, name, FILE_READ)); } -usize fileWrite(file_t ctx, const void *buf, usize len) { - DWORD bytes_read = 0; - BOOL result = WriteFile((HANDLE)ctx, buf, (DWORD)len, &bytes_read, NULL); - return result == TRUE ? (usize)bytes_read : 0; -} +buffer_t fileReadWholeFP(arena_t *arena, file_t ctx) { + if (!fileIsValid(ctx)) return (buffer_t){0}; + buffer_t out = {0}; -bool fileSeekEnd(file_t ctx) { - return SetFilePointerEx((HANDLE)ctx, (LARGE_INTEGER){0}, NULL, FILE_END) == TRUE; -} + out.len = fileSize(ctx); + out.data = alloc(arena, uint8, out.len); + usize read = fileRead(ctx, out.data, out.len); -void fileRewind(file_t ctx) { - SetFilePointerEx((HANDLE)ctx, (LARGE_INTEGER){0}, NULL, FILE_BEGIN); -} - -uint64 fileTell(file_t ctx) { - LARGE_INTEGER tell; - BOOL result = SetFilePointerEx((HANDLE)ctx, (LARGE_INTEGER){0}, &tell, FILE_CURRENT); - return result == TRUE ? (uint64)tell.QuadPart : 0; -} - -uint64 fileGetTime(file_t ctx) { - uint64 fp_time = 0; - GetFileTime((HANDLE)ctx, NULL, NULL, (FILETIME *)&fp_time); - return fp_time; -} - -#else -#include -#include -#include -#include -#include - -const char *_toStdioMode(filemode_t mode) { - switch(mode) { - case FILE_READ: return "rb"; - case FILE_BOTH: return "r+b"; - case FILE_WRITE: return "wb"; - default: fatal("mode not recognized: %d", mode); return ""; - } -} - -bool fileExists(const char *fname) { - return access(fname, F_OK) == 0; -} - -file_t fileOpen(const char *fname, filemode_t mode) { - return (file_t)(void*) fopen(fname, _toStdioMode(mode)); -} - -void fileClose(file_t ctx) { - if(ctx) { - fclose((FILE*)ctx); - } -} - -bool fileIsValid(file_t ctx) { - return (FILE *)ctx != NULL; -} - -bool filePutc(file_t ctx, char c) { - return fputc(c, (FILE*)ctx) == c; -} - -bool filePuts(file_t ctx, const char *str) { - return fputs(str, (FILE*)ctx) != EOF; -} - -bool filePutstr(file_t ctx, str_t str) { - return fileWrite(ctx, str.buf, str.len) == str.len; -} - -bool filePutview(file_t ctx, strview_t view) { - return fileWrite(ctx, view.buf, view.len) == view.len; -} - -usize fileRead(file_t ctx, void *buf, usize len) { - return fread(buf, 1, len, (FILE*)ctx); -} - -usize fileWrite(file_t ctx, const void *buf, usize len) { - return fwrite(buf, 1, len, (FILE*)ctx); -} - -bool fileSeekEnd(file_t ctx) { - return fseek((FILE*)ctx, 0, SEEK_END) == 0; -} - -void fileRewind(file_t ctx) { - rewind((FILE*)ctx); -} - -uint64 fileTell(file_t ctx) { - return (uint64)ftell((FILE*)ctx); -} - -uint64 fileGetTime(file_t ctx) { - -} -#endif - -static str_t _readWholeInternalStr(file_t ctx) { - str_t contents = strInit(); - uint64 fsize = 0; - usize read = 0; - - if(!fileSeekEnd(ctx)) { - err("file: couldn't read until end"); - goto failed; + if (read != out.len) { + err("fileReadWholeFP: fileRead failed, should be %zu but is %zu", out.len, read); + arenaPop(arena, out.len); + return (buffer_t){0}; } - fsize = fileTell(ctx); - fileRewind(ctx); - - contents.buf = (char *)malloc(fsize + 1); - contents.len = fsize; - if(!contents.buf) { - err("file: couldn't allocate buffer"); - goto failed; - } - - read = fileRead(ctx, contents.buf, fsize); - if(read != fsize) { - err("file: read wrong amount of bytes: %zu instead of %zu", read, fsize); - goto failed_free; - } - - contents.buf[contents.len] = '\0'; - -failed: - return contents; -failed_free: - strFree(contents); - return strInit(); + return out; } -static vec(uint8) _readWholeInternalVec(file_t ctx) { - vec(uint8) contents = NULL; - uint64 fsize = 0; - usize read = 0; +str_t fileReadWholeStr(arena_t *arena, arena_t scratch, strview_t name) { + return fileReadWholeStrFP(arena, fileOpen(scratch, name, FILE_READ)); +} - if(!fileSeekEnd(ctx)) { - err("file: couldn't read until end"); - goto failed; +str_t fileReadWholeStrFP(arena_t *arena, file_t ctx) { + if (!fileIsValid(ctx)) return (str_t){0}; + + str_t out = {0}; + + out.len = fileSize(ctx); + out.buf = alloc(arena, uint8, out.len + 1); + usize read = fileRead(ctx, out.buf, out.len); + + if (read != out.len) { + err("fileReadWholeFP: fileRead failed, should be %zu but is %zu", out.len, read); + arenaPop(arena, out.len + 1); + return (str_t){0}; } - fsize = fileTell(ctx); - fileRewind(ctx); - - vecReserve(contents, fsize); - if(!contents) { - err("file: couldn't allocate buffer"); - goto failed; - } - - read = fileRead(ctx, contents, fsize); - if(read != fsize) { - err("file: read wrong amount of bytes: %zu instead of %zu", read, fsize); - goto failed_free; - } - - _veclen(contents) = read; - -failed: - return contents; -failed_free: - vecFree(contents); - return contents; + return out; } -vec(uint8) fileReadWhole(const char *fname) { - file_t fp = fileOpen(fname, FILE_READ); - if (!fileIsValid(fp)) return NULL; - vec(uint8) contents = fileReadWholeFP(fp); - fileClose(fp); - return contents; -} - -vec(uint8) fileReadWholeFP(file_t ctx) { - return _readWholeInternalVec(ctx); -} - -str_t fileReadWholeText(const char *fname) { - file_t fp = fileOpen(fname, FILE_READ); - if(!fileIsValid(fp)) { - err("couldn't open file %s", fname); - return strInit(); - } - str_t contents = fileReadWholeTextFP(fp); - fileClose(fp); - return contents; -} - -str_t fileReadWholeTextFP(file_t ctx) { - return _readWholeInternalStr(ctx); -} - -bool fileWriteWhole(const char *fname, filebuf_t data) { - file_t fp = fileOpen(fname, FILE_WRITE); +bool fileWriteWhole(arena_t scratch, strview_t name, const void *buf, usize len) { + file_t fp = fileOpen(scratch, name, FILE_WRITE); if (!fileIsValid(fp)) { - err("couldn't open file %s", fname); return false; } - bool res = fileWriteWholeFP(fp, data); + usize written = fileWrite(fp, buf, len); fileClose(fp); - return res; + return written == len; } -bool fileWriteWholeFP(file_t ctx, filebuf_t data) { - usize written = fileWrite(ctx, data.buf, data.len); - return written == data.len; +uint64 fileGetTime(arena_t scratch, strview_t name) { + return fileGetTimeFP(fileOpen(scratch, name, FILE_READ)); } -bool fileWriteWholeText(const char *fname, strview_t string) { - file_t fp = fileOpen(fname, FILE_WRITE); - if (!fileIsValid(fp)) { - err("couldn't open file %s", fname); - return false; - } - bool res = fileWriteWholeTextFP(fp, string); - fileClose(fp); - return res; -} - -bool fileWriteWholeTextFP(file_t ctx, strview_t string) { - return fileWriteWholeFP(ctx, (filebuf_t){ (uint8 *)string.buf, string.len }); -} - -uint64 fileGetTimePath(const char *path) { - file_t fp = fileOpen(path, FILE_READ); - if (!fileIsValid(fp)) { - return 0; - } - uint64 fp_time = fileGetTime(fp); - fileClose(fp); - return fp_time; -} \ No newline at end of file +#include "warnings/colla_warn_end.h" diff --git a/colla/file.h b/colla/file.h index 9cc1fa5..6e34167 100644 --- a/colla/file.h +++ b/colla/file.h @@ -1,38 +1,32 @@ #pragma once -#ifdef __cplusplus -extern "C" { -#endif +#include #include "collatypes.h" #include "str.h" -#include "vec.h" +#include "arena.h" typedef enum { - FILE_READ = 1 << 0, - FILE_WRITE = 1 << 1, - FILE_CLEAR = 1 << 2, - FILE_BOTH = 1 << 3 -} filemode_t; - -typedef uintptr_t file_t; + FILE_READ = 1 << 0, + FILE_WRITE = 1 << 1, + FILE_APPEND = 1 << 2, +} filemode_e; typedef struct { - const uint8 *buf; - usize len; -} filebuf_t; + uintptr_t handle; +} file_t; -bool fileExists(const char *fname); +bool fileExists(const char *name); -file_t fileOpen(const char *fname, filemode_t mode); +file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode); void fileClose(file_t ctx); bool fileIsValid(file_t ctx); bool filePutc(file_t ctx, char c); -bool filePuts(file_t ctx, const char *str); -bool filePutstr(file_t ctx, str_t str); -bool filePutview(file_t ctx, strview_t view); +bool filePuts(file_t ctx, strview_t v); +bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...); +bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args); usize fileRead(file_t ctx, void *buf, usize len); usize fileWrite(file_t ctx, const void *buf, usize len); @@ -40,23 +34,16 @@ usize fileWrite(file_t ctx, const void *buf, usize len); bool fileSeekEnd(file_t ctx); void fileRewind(file_t ctx); -uint64 fileTell(file_t ctx); +usize fileTell(file_t ctx); +usize fileSize(file_t ctx); -vec(uint8) fileReadWhole(const char *fname); -vec(uint8) fileReadWholeFP(file_t ctx); +buffer_t fileReadWhole(arena_t *arena, arena_t scratch, strview_t name); +buffer_t fileReadWholeFP(arena_t *arena, file_t ctx); -str_t fileReadWholeText(const char *fname); -str_t fileReadWholeTextFP(file_t ctx); +str_t fileReadWholeStr(arena_t *arena, arena_t scratch, strview_t name); +str_t fileReadWholeStrFP(arena_t *arena, file_t ctx); -bool fileWriteWhole(const char *fname, filebuf_t data); -bool fileWriteWholeFP(file_t ctx, filebuf_t data); +bool fileWriteWhole(arena_t scratch, strview_t name, const void *buf, usize len); -bool fileWriteWholeText(const char *fname, strview_t string); -bool fileWriteWholeTextFP(file_t ctx, strview_t string); - -uint64 fileGetTime(file_t ctx); -uint64 fileGetTimePath(const char *path); - -#ifdef __cplusplus -} // extern "C" -#endif +uint64 fileGetTime(arena_t scratch, strview_t name); +uint64 fileGetTimeFP(file_t ctx); \ No newline at end of file diff --git a/colla/format.c b/colla/format.c new file mode 100644 index 0000000..a45a8f6 --- /dev/null +++ b/colla/format.c @@ -0,0 +1,60 @@ +#include "format.h" + +#define STB_SPRINTF_DECORATE(name) stb_##name +#define STB_SPRINTF_NOUNALIGNED +#define STB_SPRINTF_IMPLEMENTATION +#include "stb/stb_sprintf.h" + +#include "arena.h" + +static char *fmt__stb_callback(const char *buf, void *ud, int len) { + printf("%s", buf); + return (char *)ud; +} + +int fmtPrint(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + int out = fmtPrintv(fmt, args); + va_end(args); + return out; +} + +int fmtPrintv(const char *fmt, va_list args) { + char buffer[STB_SPRINTF_MIN]; + return stb_vsprintfcb(fmt__stb_callback, buffer, buffer, fmt, args); +} + +int fmtBuffer(char *buffer, usize buflen, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + int out = fmtBufferv(buffer, buflen, fmt, args); + va_end(args); + return out; +} + +int fmtBufferv(char *buffer, usize buflen, const char *fmt, va_list args) { + return stb_vsnprintf(buffer, (int)buflen, fmt, args); +} + +#if 0 +str_t fmtStr(Arena *arena, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + str_t out = fmtStrv(arena, fmt, args); + va_end(args); + return out; +} + +str_t fmtStrv(Arena *arena, const char *fmt, va_list args) { + va_list vcopy; + va_copy(vcopy, args); + int len = stb_vsnprintf(NULL, 0, fmt, vcopy); + va_end(vcopy); + + char *buffer = alloc(arena, char, len + 1); + stb_vsnprintf(buffer, len + 1, fmt, args); + + return (str_t){ .buf = buffer, .len = (usize)len }; +} +#endif \ No newline at end of file diff --git a/colla/format.h b/colla/format.h new file mode 100644 index 0000000..ade889a --- /dev/null +++ b/colla/format.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +typedef struct arena_t arena_t; + +int fmtPrint(const char *fmt, ...); +int fmtPrintv(const char *fmt, va_list args); + +int fmtBuffer(char *buffer, usize buflen, const char *fmt, ...); +int fmtBufferv(char *buffer, usize buflen, const char *fmt, va_list args); diff --git a/colla/http.c b/colla/http.c index 4767eef..28c3a5e 100644 --- a/colla/http.c +++ b/colla/http.c @@ -1,474 +1,507 @@ #include "http.h" -#include -#include -#include +#include "warnings/colla_warn_beg.h" -// #include "os.h" +#include "arena.h" +#include "strstream.h" +#include "format.h" +#include "socket.h" #include "tracelog.h" -#include "vec.h" +#if COLLA_WIN + #if COLLA_CMT_LIB + #pragma comment(lib, "Wininet") + #endif -#ifdef _WIN32 - #define stricmp _stricmp -#else - #include // strcasecmp - #define stricmp strcasecmp + #include + #include #endif -// == INTERNAL ================================================================ +static const TCHAR *https__get_method_str(http_method_e method); -static void _setField(vec(http_field_t) *fields_vec, const char *key, const char *value) { - vec(http_field_t) fields = *fields_vec; - - for (uint32 i = 0; i < vecLen(fields); ++i) { - if (stricmp(fields[i].key, key) == 0) { - char **curval = &fields[i].value; - usize curlen = strlen(*curval); - usize newlen = strlen(value); - if(newlen > curlen) { - *curval = (char *)realloc(*curval, newlen + 1); - } - memcpy(*curval, value, newlen); - (*curval)[newlen] = '\0'; - return; - } - } - - // otherwise, add it to the list - http_field_t field; - usize klen = strlen(key); - usize vlen = strlen(value); - field.key = (char *)malloc(klen + 1); - field.value = (char *)malloc(vlen + 1); - memcpy(field.key, key, klen); - memcpy(field.value, value, vlen); - field.key[klen] = field.value[vlen] = '\0'; - - vecAppend(*fields_vec, field); -} - -static void _parseFields(vec(http_field_t) *fields, str_istream_t *in) { - strview_t line; +static http_header_t *http__parse_headers(arena_t *arena, instream_t *in) { + http_header_t *head = NULL; + strview_t line = (strview_t){0}; do { - line = istrGetview(in, '\r'); + line = istrGetView(in, '\r'); usize pos = strvFind(line, ':', 0); - if(pos != STRV_NOT_FOUND) { - strview_t key = strvSub(line, 0, pos); - strview_t value = strvSub(line, pos + 2, SIZE_MAX); + if (pos != STR_NONE) { + http_header_t *h = alloc(arena, http_header_t); - char *key_str = NULL; - char *value_str = NULL; - - key_str = strvCopy(key).buf; - value_str = strvCopy(value).buf; - - _setField(fields, key_str, value_str); - - free(key_str); - free(value_str); + h->key = strvSub(line, 0, pos); + h->value = strvSub(line, pos + 2, SIZE_MAX); + + h->next = head; + head = h; } istrSkip(in, 2); // skip \r\n - } while(line.len > 2); + } while (line.len > 2); // while line != "\r\n" + + return head; } -// == HTTP STATUS ============================================================= - -const char *httpGetStatusString(resstatus_t status) { +const char *httpGetStatusString(int status) { switch (status) { - case STATUS_OK: return "OK"; - case STATUS_CREATED: return "CREATED"; - case STATUS_ACCEPTED: return "ACCEPTED"; - case STATUS_NO_CONTENT: return "NO CONTENT"; - case STATUS_RESET_CONTENT: return "RESET CONTENT"; - case STATUS_PARTIAL_CONTENT: return "PARTIAL CONTENT"; - case STATUS_MULTIPLE_CHOICES: return "MULTIPLE CHOICES"; - case STATUS_MOVED_PERMANENTLY: return "MOVED PERMANENTLY"; - case STATUS_MOVED_TEMPORARILY: return "MOVED TEMPORARILY"; - case STATUS_NOT_MODIFIED: return "NOT MODIFIED"; - case STATUS_BAD_REQUEST: return "BAD REQUEST"; - case STATUS_UNAUTHORIZED: return "UNAUTHORIZED"; - case STATUS_FORBIDDEN: return "FORBIDDEN"; - case STATUS_NOT_FOUND: return "NOT FOUND"; - case STATUS_RANGE_NOT_SATISFIABLE: return "RANGE NOT SATISFIABLE"; - case STATUS_INTERNAL_SERVER_ERROR: return "INTERNAL SERVER_ERROR"; - case STATUS_NOT_IMPLEMENTED: return "NOT IMPLEMENTED"; - case STATUS_BAD_GATEWAY: return "BAD GATEWAY"; - case STATUS_SERVICE_NOT_AVAILABLE: return "SERVICE NOT AVAILABLE"; - case STATUS_GATEWAY_TIMEOUT: return "GATEWAY TIMEOUT"; - case STATUS_VERSION_NOT_SUPPORTED: return "VERSION NOT SUPPORTED"; + case 200: return "OK"; + case 201: return "CREATED"; + case 202: return "ACCEPTED"; + case 204: return "NO CONTENT"; + case 205: return "RESET CONTENT"; + case 206: return "PARTIAL CONTENT"; + + case 300: return "MULTIPLE CHOICES"; + case 301: return "MOVED PERMANENTLY"; + case 302: return "MOVED TEMPORARILY"; + case 304: return "NOT MODIFIED"; + + case 400: return "BAD REQUEST"; + case 401: return "UNAUTHORIZED"; + case 403: return "FORBIDDEN"; + case 404: return "NOT FOUND"; + case 407: return "RANGE NOT SATISFIABLE"; + + case 500: return "INTERNAL SERVER_ERROR"; + case 501: return "NOT IMPLEMENTED"; + case 502: return "BAD GATEWAY"; + case 503: return "SERVICE NOT AVAILABLE"; + case 504: return "GATEWAY TIMEOUT"; + case 505: return "VERSION NOT SUPPORTED"; } + return "UNKNOWN"; } -// == HTTP VERSION ============================================================ - int httpVerNumber(http_version_t ver) { return (ver.major * 10) + ver.minor; } -// == HTTP REQUEST ============================================================ +http_req_t httpParseReq(arena_t *arena, strview_t request) { + http_req_t req = {0}; + instream_t in = istrInitLen(request.buf, request.len); -http_request_t reqInit() { - http_request_t req = {0}; - reqSetUri(&req, strvInit("")); - req.version = (http_version_t){1, 1}; - return req; -} - -http_request_t reqParse(const char *request) { - http_request_t req = {0}; - str_istream_t in = istrInit(request); - - // get data - - strview_t method = strvTrim(istrGetview(&in, '/')); + strview_t method = strvTrim(istrGetView(&in, '/')); istrSkip(&in, 1); // skip / - strview_t page = strvTrim(istrGetview(&in, ' ')); - strview_t http = strvTrim(istrGetview(&in, '\n')); + req.url = strvTrim(istrGetView(&in, ' ')); + strview_t http = strvTrim(istrGetView(&in, '\n')); istrSkip(&in, 1); // skip \n - - _parseFields(&req.fields, &in); - strview_t body = strvTrim(istrGetviewLen(&in, 0, SIZE_MAX)); + req.headers = http__parse_headers(arena, &in); - // parse data + req.body = strvTrim(istrGetViewLen(&in, SIZE_MAX)); - // -- method - const char *methods[] = { "GET", "POST", "HEAD", "PUT", "DELETE" }; - const int methods_count = sizeof(methods) / sizeof(*methods); + strview_t methods[] = { strv("GET"), strv("POST"), strv("HEAD"), strv("PUT"), strv("DELETE") }; + usize methods_count = arrlen(methods); - for (int i = 0; i < methods_count; ++i) { - if (strvCompare(method, strvInit(methods[i])) == 0) { - req.method = (reqtype_t)i; + for (usize i = 0; i < methods_count; ++i) { + if (strvEquals(method, methods[i])) { + req.method = (http_method_e)i; + break; } } - // -- page - req.uri = strvCopy(page).buf; - - // -- http in = istrInitLen(http.buf, http.len); istrIgnoreAndSkip(&in, '/'); // skip HTTP/ - istrGetu8(&in, &req.version.major); + istrGetU8(&in, &req.version.major); istrSkip(&in, 1); // skip . - istrGetu8(&in, &req.version.minor); - - // -- body - req.body = strvCopy(body).buf; + istrGetU8(&in, &req.version.minor); return req; } -void reqFree(http_request_t ctx) { - for (http_field_t *it = ctx.fields; it != vecEnd(ctx.fields); ++it) { - free(it->key); - free(it->value); - } - vecFree(ctx.fields); - free(ctx.uri); - free(ctx.body); -} +http_res_t httpParseRes(arena_t *arena, strview_t response) { + http_res_t res = {0}; + instream_t in = istrInitLen(response.buf, response.len); -bool reqHasField(http_request_t *ctx, const char *key) { - for(uint32 i = 0; i < vecLen(ctx->fields); ++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, key, value); -} - -void reqSetUri(http_request_t *ctx, strview_t uri) { - if (strvIsEmpty(uri)) return; - free(ctx->uri); - if (uri.buf[0] == '/') { - strvRemovePrefix(uri, 1); - } - ctx->uri = strvCopy(uri).buf; -} - -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(uint32 i = 0; i < vecLen(ctx->fields); ++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; -} - -str_t reqString(http_request_t *ctx) { - str_ostream_t out = reqPrepare(ctx); - return ostrAsStr(out); -} - -// == HTTP RESPONSE =========================================================== - -http_response_t resParse(const char *data) { - http_response_t ctx = {0}; - 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 ctx; + strview_t http = istrGetViewLen(&in, 5); + if (!strvEquals(http, strv("HTTP"))) { + err("response doesn't start with 'HTTP', instead with %v", http); + return (http_res_t){0}; } istrSkip(&in, 1); // skip / - istrGetu8(&in, &ctx.version.major); + istrGetU8(&in, &res.version.major); istrSkip(&in, 1); // skip . - istrGetu8(&in, &ctx.version.minor); - istrGeti32(&in, (int32*)&ctx.status_code); + istrGetU8(&in, &res.version.minor); + istrGetI32(&in, (int32*)&res.status_code); istrIgnore(&in, '\n'); istrSkip(&in, 1); // skip \n - resParseFields(&ctx, &in); + res.headers = http__parse_headers(arena, &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); - vecClear(ctx.body); - vecReserve(ctx.body, body.len); - memcpy(ctx.body, body.buf, body.len); + strview_t encoding = httpGetHeader(res.headers, strv("transfer-encoding")); + if (!strvEquals(encoding, strv("chunked"))) { + res.body = istrGetViewLen(&in, SIZE_MAX); } else { - // fatal("chunked encoding not implemented yet"); - err("chunked encoding not implemented yet"); + err("chunked encoding not implemented yet! body ignored"); } - return ctx; -} - -void resFree(http_response_t ctx) { - for (http_field_t *it = ctx.fields; it != vecEnd(ctx.fields); ++it) { - free(it->key); - free(it->value); - } - vecFree(ctx.fields); - vecFree(ctx.body); + return res; } -bool resHasField(http_response_t *ctx, const char *key) { - for(uint32 i = 0; i < vecLen(ctx->fields); ++i) { - if(stricmp(ctx->fields[i].key, key) == 0) { +str_t httpReqToStr(arena_t *arena, http_req_t *req) { + outstream_t out = ostrInit(arena); + + const char *method = NULL; + switch (req->method) { + case HTTP_GET: method = "GET"; break; + case HTTP_POST: method = "POST"; break; + case HTTP_HEAD: method = "HEAD"; break; + case HTTP_PUT: method = "PUT"; break; + case HTTP_DELETE: method = "DELETE"; break; + default: err("unrecognised method: %d", method); return (str_t){0}; + } + + ostrPrintf( + &out, + "%s /%v HTTP/%hhu.%hhu\r\n", + method, req->url, req->version.major, req->version.minor + ); + + http_header_t *h = req->headers; + while (h) { + ostrPrintf(&out, "%v: %v\r\n", h->key, h->value); + h = h->next; + } + + ostrPuts(&out, strv("\r\n")); + ostrPuts(&out, req->body); + + return ostrAsStr(&out); +} + +str_t httpResToStr(arena_t *arena, http_res_t *res) { + outstream_t out = ostrInit(arena); + + ostrPrintf( + &out, + "HTTP/%hhu.%hhu %d %s\r\n", + res->version.major, + res->version.minor, + res->status_code, + httpGetStatusString(res->status_code) + ); + ostrPuts(&out, strv("\r\n")); + ostrPuts(&out, res->body); + + return ostrAsStr(&out); +} + +bool httpHasHeader(http_header_t *headers, strview_t key) { + http_header_t *h = headers; + while (h) { + if (strvEquals(h->key, key)) { return true; } + h = h->next; } return false; } -void resSetField(http_response_t *ctx, const char *key, const char *value) { - _setField(&ctx->fields, key, value); +void httpSetHeader(http_header_t *headers, strview_t key, strview_t value) { + http_header_t *h = headers; + while (h) { + if (strvEquals(h->key, key)) { + h->value = value; + break; + } + h = h->next; + } } -const char *resGetField(http_response_t *ctx, const char *field) { - for(uint32 i = 0; i < vecLen(ctx->fields); ++i) { - if(stricmp(ctx->fields[i].key, field) == 0) { - return ctx->fields[i].value; +strview_t httpGetHeader(http_header_t *headers, strview_t key) { + http_header_t *h = headers; + while (h) { + if (strvEquals(h->key, key)) { + return h->value; + } + h = h->next; + } + return (strview_t){0}; +} + +str_t httpMakeUrlSafe(arena_t *arena, strview_t string) { + strview_t chars = strv(" !\"#$%%&'()*+,/:;=?@[]"); + usize final_len = string.len; + + // find final string length first + for (usize i = 0; i < string.len; ++i) { + if (strvContains(chars, string.buf[i])) { + final_len += 2; } } - return NULL; -} - -void resParseFields(http_response_t *ctx, str_istream_t *in) { - _parseFields(&ctx->fields, in); -} - -str_ostream_t resPrepare(http_response_t *ctx) { - str_ostream_t out = ostrInitLen(1024); - - ostrPrintf( - &out, "HTTP/%hhu.%hhu %d %s\r\n", - ctx->version.major, ctx->version.minor, - ctx->status_code, httpGetStatusString(ctx->status_code) - ); - for (http_field_t *field = ctx->fields; field != vecEnd(ctx->fields); ++field) { - ostrPrintf(&out, "%s: %s\r\n", field->key, field->value); + + str_t out = { + .buf = alloc(arena, char, final_len + 1), + .len = final_len + }; + usize cur = 0; + // substitute characters + for (usize i = 0; i < string.len; ++i) { + if (strvContains(chars, string.buf[i])) { + fmtBuffer(out.buf + cur, 4, "%%%X", string.buf[i]); + cur += 3; + } + else { + out.buf[cur++] = string.buf[i]; + } } - ostrPuts(&out, "\r\n"); - ostrAppendview(&out, strvInitLen(ctx->body, vecLen(ctx->body))); return out; } -str_t resString(http_response_t *ctx) { - str_ostream_t out = resPrepare(ctx); - return ostrAsStr(out); +http_url_t httpSplitUrl(strview_t url) { + http_url_t out = {0}; + + if (strvStartsWithView(url, strv("https://"))) { + url = strvRemovePrefix(url, 8); + } + else if (strvStartsWithView(url, strv("http://"))) { + url = strvRemovePrefix(url, 7); + } + + out.host = strvSub(url, 0, strvFind(url, '/', 0)); + out.uri = strvSub(url, out.host.len, SIZE_MAX); + + return out; } -// == HTTP CLIENT ============================================================= +http_res_t httpRequest(http_request_desc_t *request) { + usize arena_begin = arenaTell(request->arena); -http_client_t hcliInit() { - return (http_client_t) { - .port = 80, + http_req_t req = { + .version = (http_version_t){ 1, 1 }, + .url = request->url, + .body = request->body, + .method = request->request_type, }; -} -void hcliFree(http_client_t ctx) { - strFree(ctx.host_name); -} + http_header_t *h = NULL; -void hcliSetHost(http_client_t *ctx, strview_t hostname) { - // if the hostname starts with http:// (case insensitive) - if(strvICompare(strvSub(hostname, 0, 7), strvInit("http://")) == 0) { - ctx->host_name = strvCopy(strvSub(hostname, 7, SIZE_MAX)); - } - else if(strvICompare(strvSub(hostname, 0, 8), strvInit("https://")) == 0) { - err("HTTPS protocol not yet supported"); - return; - } - else { - // undefined protocol, use HTTP - ctx->host_name = strvCopy(hostname); - } -} - -http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *req) { - if (strBack(ctx->host_name) == '/') { - ctx->host_name.buf[--ctx->host_name.len] = '\0'; - } - if(!reqHasField(req, "Host")) { - reqSetField(req, "Host", ctx->host_name.buf); - } - 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"); + for (int i = 0; i < request->header_count; ++i) { + http_header_t *header = request->headers + i; + header->next = h; + h = header; } - http_response_t res = {0}; - str_t req_str = strInit(); - str_ostream_t received = ostrInitLen(1024); - - if(!skInit()) { - err("couldn't initialize sockets %s", skGetErrorString()); - goto skopen_error; + req.headers = h; + + http_url_t url = httpSplitUrl(req.url); + + if (strvEndsWith(url.host, '/')) { + url.host = strvRemoveSuffix(url.host, 1); } - ctx->socket = skOpen(SOCK_TCP); - if(ctx->socket == INVALID_SOCKET) { - err("couldn't open socket %s", skGetErrorString()); + if (!httpHasHeader(req.headers, strv("Host"))) { + httpSetHeader(req.headers, strv("Host"), url.host); + } + if (!httpHasHeader(req.headers, strv("Content-Length"))) { + char tmp[16] = {0}; + fmtBuffer(tmp, arrlen(tmp), "%zu", req.body.len); + httpSetHeader(req.headers, strv("Content-Length"), strv(tmp)); + } + if (req.method == HTTP_POST && !httpHasHeader(req.headers, strv("Content-Type"))) { + httpSetHeader(req.headers, strv("Content-Type"), strv("application/x-www-form-urlencoded")); + } + if (!httpHasHeader(req.headers, strv("Connection"))) { + httpSetHeader(req.headers, strv("Connection"), strv("close")); + } + + if (!skInit()) { + err("couldn't initialise sockets: %s", skGetErrorString()); goto error; } - if(skConnect(ctx->socket, ctx->host_name.buf, ctx->port)) { - req_str = reqString(req); - if(req_str.len == 0) { - err("couldn't get string from request"); + socket_t sock = skOpen(SOCK_TCP); + if (!skIsValid(sock)) { + err("couldn't open socket: %s", skGetErrorString()); + goto error; + } + + char hostname[64] = {0}; + assert(url.host.len < arrlen(hostname)); + memcpy(hostname, url.host.buf, url.host.len); + + const int DEFAULT_HTTP_PORT = 80; + if (!skConnect(sock, hostname, DEFAULT_HTTP_PORT)) { + err("Couldn't connect to host %s: %s", hostname, skGetErrorString()); + goto error; + } + + str_t reqstr = httpReqToStr(request->arena, &req); + if (strIsEmpty(reqstr)) { + err("couldn't get string from request"); + goto error; + } + + if (skSend(sock, reqstr.buf, (int)reqstr.len) == -1) { + err("couldn't send request to socket: %s", skGetErrorString()); + goto error; + } + + outstream_t response = ostrInit(request->arena); + char buffer[4096]; + int read = 0; + do { + read = skReceive(sock, buffer, arrlen(buffer)); + if (read == -1) { + err("couldn't get the data from the server: %s", skGetErrorString()); goto error; } + ostrPuts(&response, strv(buffer, read)); + } while (read != 0); - 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.len) != '\0') { - ostrPutc(&received, '\0'); - received.len--; - } - - res = resParse(received.buf); - } - else { - err("Couldn't connect to host %s -> %s", ctx->host_name, skGetErrorString()); + if (!skClose(sock)) { + err("couldn't close socket: %s", skGetErrorString()); } - if(!skClose(ctx->socket)) { - err("Couldn't close socket"); + if (!skCleanup()) { + err("couldn't clean up sockets: %s", skGetErrorString()); } - + + return httpParseRes(request->arena, ostrAsView(&response)); + error: - if(!skCleanup()) { - err("couldn't clean up sockets %s", skGetErrorString()); - } -skopen_error: - strFree(req_str); - ostrFree(received); - return res; + arenaRewind(request->arena, arena_begin); + skCleanup(); + return (http_res_t){0}; } -http_response_t httpGet(strview_t hostname, strview_t uri) { - http_request_t request = reqInit(); - request.method = REQ_GET; - reqSetUri(&request, uri); +#if COLLA_WIN - http_client_t client = hcliInit(); - hcliSetHost(&client, hostname); - - http_response_t res = hcliSendRequest(&client, &request); - - reqFree(request); - hcliFree(client); - - return res; -} - -url_split_t urlSplit(strview_t uri) { - url_split_t out = {0}; - - if (strvStartsWithView(uri, strvInit("https://"))) { - uri = strvRemovePrefix(uri, 8); - } - else if (strvStartsWithView(uri, strvInit("http://"))) { - uri = strvRemovePrefix(uri, 7); +buffer_t httpsRequest(http_request_desc_t *req) { + HINTERNET internet = InternetOpen( + TEXT("COLLA"), + INTERNET_OPEN_TYPE_PRECONFIG, + NULL, + NULL, + 0 + ); + if (!internet) { + fatal("call to InternetOpen failed: %u", GetLastError()); } - out.host = strvSub(uri, 0, strvFind(uri, '/', 0)); - out.uri = strvSub(uri, out.host.len, SIZE_MAX); - return out; + http_url_t split = httpSplitUrl(req->url); + strview_t server = split.host; + strview_t page = split.uri; + + if (strvStartsWithView(server, strv("http://"))) { + server = strvRemovePrefix(server, 7); + } + + if (strvStartsWithView(server, strv("https://"))) { + server = strvRemovePrefix(server, 8); + } + + arena_t scratch = *req->arena; + const TCHAR *tserver = strvToTChar(&scratch, server); + const TCHAR *tpage = strvToTChar(&scratch, page); + + HINTERNET connection = InternetConnect( + internet, + tserver, + INTERNET_DEFAULT_HTTPS_PORT, + NULL, + NULL, + INTERNET_SERVICE_HTTP, + 0, + (DWORD_PTR)NULL // userdata + ); + if (!connection) { + fatal("call to InternetConnect failed: %u", GetLastError()); + } + + const TCHAR *accepted_types[] = { TEXT("*/*"), NULL }; + + HINTERNET request = HttpOpenRequest( + connection, + https__get_method_str(req->request_type), + tpage, + TEXT("HTTP/1.1"), + NULL, + accepted_types, + INTERNET_FLAG_SECURE, + (DWORD_PTR)NULL // userdata + ); + if (!request) { + fatal("call to HttpOpenRequest failed: %u", GetLastError()); + } + + outstream_t header = ostrInit(&scratch); + + for (int i = 0; i < req->header_count; ++i) { + http_header_t *h = &req->headers[i]; + ostrClear(&header); + ostrPrintf( + &header, + "%.*s: %.*s\r\n", + h->key.len, h->key.buf, + h->value.len, h->value.buf + ); + str_t header_str = ostrAsStr(&header); + HttpAddRequestHeadersA( + request, + header_str.buf, + header_str.len, + 0 + ); + } + + BOOL request_sent = HttpSendRequest( + request, + NULL, + 0, + (void *)req->body.buf, + req->body.len + ); + if (!request_sent) { + fatal("call to HttpSendRequest failed: %u", GetLastError()); + } + + outstream_t out = ostrInit(req->arena); + + while (true) { + DWORD bytes_read = 0; + char buffer[4096]; + BOOL read = InternetReadFile( + request, + buffer, + sizeof(buffer), + &bytes_read + ); + if (!read || bytes_read == 0) { + break; + } + ostrPuts(&out, strv(buffer, bytes_read)); + } + + InternetCloseHandle(request); + InternetCloseHandle(connection); + InternetCloseHandle(internet); + + str_t outstr = ostrAsStr(&out); + + return (buffer_t) { + .data = (uint8 *)outstr.buf, + .len = outstr.len + }; } + +static const TCHAR *https__get_method_str(http_method_e method) { + switch (method) { + case HTTP_GET: return TEXT("GET"); + case HTTP_POST: return TEXT("POST"); + case HTTP_HEAD: return TEXT("HEAD"); + case HTTP_PUT: return TEXT("PUT"); + case HTTP_DELETE: return TEXT("DELETE"); + } + // default GET + return NULL; +} +#endif + +#include "warnings/colla_warn_end.h" diff --git a/colla/http.h b/colla/http.h index b0fb23d..dc2fbba 100644 --- a/colla/http.h +++ b/colla/http.h @@ -1,57 +1,20 @@ #pragma once -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - #include "collatypes.h" #include "str.h" -#include "strstream.h" -#include "socket.h" + +typedef struct arena_t arena_t; +typedef uintptr_t socket_t; typedef enum { - REQ_GET, - REQ_POST, - REQ_HEAD, - REQ_PUT, - REQ_DELETE -} reqtype_t; + HTTP_GET, + HTTP_POST, + HTTP_HEAD, + HTTP_PUT, + HTTP_DELETE +} http_method_e; -typedef 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, -} resstatus_t; - -const char *httpGetStatusString(resstatus_t status); +const char *httpGetStatusString(int status); typedef struct { uint8 major; @@ -61,83 +24,59 @@ typedef struct { // 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; - -#include "vec.h" - -// == HTTP REQUEST ============================================================ +typedef struct http_header_t { + strview_t key; + strview_t value; + struct http_header_t *next; +} http_header_t; typedef struct { - reqtype_t method; + http_method_e method; http_version_t version; - vec(http_field_t) fields; - char *uri; - char *body; -} http_request_t; - -http_request_t reqInit(void); -http_request_t reqParse(const char *request); -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, strview_t uri); - -str_ostream_t reqPrepare(http_request_t *ctx); -str_t reqString(http_request_t *ctx); - -// == HTTP RESPONSE =========================================================== + http_header_t *headers; + strview_t url; + strview_t body; +} http_req_t; typedef struct { - resstatus_t status_code; - vec(http_field_t) fields; + int status_code; http_version_t version; - vec(uint8) body; -} http_response_t; + http_header_t *headers; + strview_t body; +} http_res_t; -http_response_t resParse(const char *data); -void resFree(http_response_t ctx); +// strview_t request needs to be valid for http_req_t to be valid! +http_req_t httpParseReq(arena_t *arena, strview_t request); +http_res_t httpParseRes(arena_t *arena, strview_t response); -bool resHasField(http_response_t *ctx, const char *key); -void resSetField(http_response_t *ctx, const char *key, const char *value); -const char *resGetField(http_response_t *ctx, const char *field); +str_t httpReqToStr(arena_t *arena, http_req_t *req); +str_t httpResToStr(arena_t *arena, http_res_t *res); -// void resParse(http_response_t *ctx, const char *data); -void resParseFields(http_response_t *ctx, str_istream_t *in); -str_ostream_t resPrepare(http_response_t *ctx); -str_t resString(http_response_t *ctx); +bool httpHasHeader(http_header_t *headers, strview_t key); +void httpSetHeader(http_header_t *headers, strview_t key, strview_t value); +strview_t httpGetHeader(http_header_t *headers, strview_t key); -// == HTTP CLIENT ============================================================= - -typedef struct { - str_t host_name; - uint16 port; - socket_t socket; -} http_client_t; - -http_client_t hcliInit(void); -void hcliFree(http_client_t ctx); - -void hcliSetHost(http_client_t *ctx, strview_t hostname); -http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *request); - -// == HTTP ==================================================================== - -http_response_t httpGet(strview_t hostname, strview_t uri); - -// == URL ===================================================================== +str_t httpMakeUrlSafe(arena_t *arena, strview_t string); typedef struct { strview_t host; strview_t uri; -} url_split_t; +} http_url_t; -url_split_t urlSplit(strview_t uri); +http_url_t httpSplitUrl(strview_t url); -#ifdef __cplusplus -} // extern "C" -#endif +typedef struct { + arena_t *arena; + strview_t url; + http_method_e request_type; + http_header_t *headers; + int header_count; + strview_t body; +} http_request_desc_t; + +// arena_t *arena, strview_t url, [ http_header_t *headers, int header_count, strview_t body ] +#define httpGet(arena, url, ...) httpRequest(&(http_request_desc_t){ arena, url, .request_type = HTTP_GET, __VA_ARGS__ }) +#define httpsGet(arena, url, ...) httpsRequest(&(http_request_desc_t){ arena, url, .request_type = HTTP_GET, __VA_ARGS__ }) + +http_res_t httpRequest(http_request_desc_t *request); +buffer_t httpsRequest(http_request_desc_t *request); diff --git a/colla/ini.c b/colla/ini.c index 00df248..f96e08e 100644 --- a/colla/ini.c +++ b/colla/ini.c @@ -1,347 +1,267 @@ #include "ini.h" +#include "warnings/colla_warn_beg.h" + +#include + #include "strstream.h" -#include "file.h" -#include "tracelog.h" -// == INI READER ======================================================================== +static void ini__parse(arena_t *arena, ini_t *ini, const iniopts_t *options); -static const iniopts_t default_opts = { - .key_value_divider = '=' -}; +ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options) { + file_t fp = fileOpen(*arena, filename, FILE_READ); + ini_t out = iniParseFile(arena, fp, options); + fileClose(fp); + return out; +} -static iniopts_t setDefaultOptions(const iniopts_t *options); -static initable_t *findTable(ini_t *ctx, strview_t name); -static inivalue_t *findValue(vec(inivalue_t) values, strview_t key); -static void addTable(ini_t *ctx, str_istream_t *in, const iniopts_t *options); -static void addValue(initable_t *table, str_istream_t *in, const iniopts_t *options); +ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options) { + str_t data = fileReadWholeStrFP(arena, file); + return iniParseStr(arena, strv(data), options); +} -void _iniParseInternal(ini_t *ini, const iniopts_t *options) { - // add root table - vecAppend(ini->tables, (initable_t){0}); - str_istream_t in = istrInitLen(ini->text.buf, ini->text.len); - istrSkipWhitespace(&in); - while (!istrIsFinished(in)) { - switch(*in.cur) { - case '[': - addTable(ini, &in, options); - break; - case '#': case ';': - istrIgnore(&in, '\n'); - break; - default: - addValue(&ini->tables[0], &in, options); - break; +ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options) { + ini_t out = { + .text = str, + .tables = NULL, + }; + ini__parse(arena, &out, options); + return out; +} + +initable_t *iniGetTable(ini_t *ctx, strview_t name) { + initable_t *t = ctx ? ctx->tables : NULL; + while (t) { + if (strvEquals(t->name, name)) { + return t; } - istrSkipWhitespace(&in); + t = t->next; } + return NULL; } -ini_t iniParse(const char *filename, const iniopts_t *options) { - ini_t ini = { .text = fileReadWholeText(filename) }; - if (strIsEmpty(ini.text)) return ini; - iniopts_t opts = setDefaultOptions(options); - _iniParseInternal(&ini, &opts); - return ini; -} - -ini_t iniParseString(const char *inistr, const iniopts_t *options) { - ini_t ini = { .text = strFromStr(inistr) }; - if (!options) options = &default_opts; - _iniParseInternal(&ini, options); - return ini; -} - -void iniFree(ini_t ctx) { - strFree(ctx.text); - for (uint32 i = 0; i < vecLen(ctx.tables); ++i) { - vecFree(ctx.tables[i].values); +inivalue_t *iniGet(initable_t *ctx, strview_t key) { + inivalue_t *v = ctx ? ctx->values : NULL; + while (v) { + if (strvEquals(v->key, key)) { + return v; + } + v = v->next; } - vecFree(ctx.tables); + return NULL; } -initable_t *iniGetTable(ini_t *ctx, const char *name) { - if (!name) { - return &ctx->tables[0]; - } - else { - return findTable(ctx, strvInit(name)); - } -} - -inivalue_t *iniGet(initable_t *ctx, const char *key) { - return ctx ? findValue(ctx->values, strvInit(key)) : NULL; -} - -vec(strview_t) iniAsArray(const inivalue_t *value, char delim) { - if (!value) return NULL; +iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim) { + strview_t v = value ? value->value : (strview_t){0}; if (!delim) delim = ' '; - vec(strview_t) out = NULL; - strview_t v = value->value; + strview_t *beg = (strview_t *)arena->current; + usize count = 0; usize start = 0; for (usize i = 0; i < v.len; ++i) { if (v.buf[i] == delim) { - strview_t arr_val = strvTrim(strvSub(v, start, i)); - if (!strvIsEmpty(arr_val)) vecAppend(out, arr_val); + strview_t arrval = strvTrim(strvSub(v, start, i)); + if (!strvIsEmpty(arrval)) { + strview_t *newval = alloc(arena, strview_t); + *newval = arrval; + ++count; + } start = i + 1; } } + strview_t last = strvTrim(strvSub(v, start, SIZE_MAX)); - if (!strvIsEmpty(last)) vecAppend(out, last); + if (!strvIsEmpty(last)) { + strview_t *newval = alloc(arena, strview_t); + *newval = last; + ++count; + } + + return (iniarray_t){ + .values = beg, + .count = count, + }; +} + +uint64 iniAsUInt(inivalue_t *value) { + strview_t v = value ? value->value : (strview_t){0}; + instream_t in = istrInitLen(v.buf, v.len); + uint64 out = 0; + if (!istrGetU64(&in, &out)) { + out = 0; + } return out; } -vec(strview_t) iniAsArrayU8(const inivalue_t *value, const char *delim) { - if (!value || !delim) return NULL; - - rune cpdelim = utf8Decode(&delim); - vec(strview_t) out = NULL; - strview_t v = value->value; - - const char *start = v.buf; - const char *buf = v.buf; - const char *prevbuf = buf; - - for(rune cp = utf8Decode(&buf); - buf != (v.buf + v.len); - cp = utf8Decode(&buf) - ) { - if (cp == cpdelim) { - usize start_pos = start - v.buf; - usize end_pos = prevbuf - v.buf; - strview_t arr_val = strvTrim(strvSub(v, start_pos, end_pos)); - if (!strvIsEmpty(arr_val)) vecAppend(out, arr_val); - // buf has already gone to the next codepoint, skipping the delimiter - start = buf; - } - prevbuf = buf; +int64 iniAsInt(inivalue_t *value) { + strview_t v = value ? value->value : (strview_t){0}; + instream_t in = istrInitLen(v.buf, v.len); + int64 out = 0; + if (!istrGetI64(&in, &out)) { + out = 0; } - - strview_t last = strvTrim(strvSub(v, start - v.buf, SIZE_MAX)); - if (!strvIsEmpty(last)) vecAppend(out, last); return out; } -uint64 iniAsUInt(const inivalue_t *value) { - if (!value) return 0; - str_istream_t in = istrInitLen(value->value.buf, value->value.len); - uint64 val = 0; - if (!istrGetu64(&in, &val)) val = 0; - return val; -} - -int64 iniAsInt(const inivalue_t *value) { - if (!value) return 0; - str_istream_t in = istrInitLen(value->value.buf, value->value.len); - int64 val = 0; - if (!istrGeti64(&in, &val)) val = 0; - return val; -} - -double iniAsNum(const inivalue_t *value) { - if (!value) return 0.f; - str_istream_t in = istrInitLen(value->value.buf, value->value.len); - double val = 0; - if (!istrGetdouble(&in, &val)) val = 0; - return val; -} - -bool iniAsBool(const inivalue_t *value) { - if (!value) return false; - return strvCompare(value->value, strvInit("true")) == 0; -} - -// == INI WRITER ======================================================================== - -#include "strstream.h" -#include "file.h" - -static const winiopts_t default_wopts = {0}; - -iniwriter_t winiInit() { - iniwriter_t out = {0}; - vecAppend(out.tables, (winitable_t){0}); +double iniAsNum(inivalue_t *value) { + strview_t v = value ? value->value : (strview_t){0}; + instream_t in = istrInitLen(v.buf, v.len); + double out = 0; + if (!istrGetDouble(&in, &out)) { + out = 0.0; + } return out; } -void winiFree(iniwriter_t ctx) { - for (winitable_t *tab = ctx.tables; tab != vecEnd(ctx.tables); ++tab) { - strFree(tab->key); - for (winivalue_t *val = tab->values; val != vecEnd(tab->values); ++val) { - strFree(val->key); - strFree(val->value); - } - vecFree(tab->values); +bool iniAsBool(inivalue_t *value) { + strview_t v = value ? value->value : (strview_t){0}; + instream_t in = istrInitLen(v.buf, v.len); + bool out = 0; + if (!istrGetBool(&in, &out)) { + out = false; } - vecFree(ctx.tables); + return out; } -str_t winiToString(iniwriter_t ctx, const winiopts_t *options) { - if (!options) options = &default_wopts; +// == PRIVATE FUNCTIONS ============================================================================== - str_ostream_t out = ostrInitLen(1024 * 20); - if (!options->no_discalimer) ostrPuts(&out, "# auto-generated by colla's ini.h, do not modify!\n"); - // add root values - winitable_t *root = &ctx.tables[0]; - for (winivalue_t *val = root->values; val != vecEnd(root->values); ++val) { - ostrPrintf(&out, "%s = %s\n", val->key.buf, val->value.buf); +#define INIPUSH(head, tail, val) \ + do { \ + if (!head) { \ + head = val; \ + tail = val; \ + } \ + else { \ + tail->next = val; \ + val = tail; \ + } \ + } while (0) + +static iniopts_t ini__get_options(const iniopts_t *options) { + iniopts_t out = { + .key_value_divider = '=', + }; + +#define SETOPT(v) out.v = options->v ? options->v : out.v + + if (options) { + SETOPT(key_value_divider); + SETOPT(merge_duplicate_keys); + SETOPT(merge_duplicate_tables); } - if (root->values) ostrPuts(&out, "\n"); - // add each table - for (usize i = 1; i < vecLen(ctx.tables); ++i) { - winitable_t *tab = &ctx.tables[i]; - ostrPrintf(&out, "[%s]\n", tab->key.buf); - for (winivalue_t *val = tab->values; val != vecEnd(tab->values); ++val) { - ostrPrintf(&out, "%s = %s\n", val->key.buf, val->value.buf); - } - if ((i + 1) < vecLen(ctx.tables)) ostrPuts(&out, "\n"); - } - return ostrAsStr(out); + +#undef SETOPT + + return out; } -void winiToFile(iniwriter_t ctx, const char *filename, const winiopts_t *options) { - if (!options) options = &default_wopts; +static void ini__add_value(arena_t *arena, initable_t *table, instream_t *in, iniopts_t *opts) { + assert(table); - file_t fp = fileOpen(filename, FILE_WRITE); - if (!fileIsValid(fp)) { - err("couldn't write ini to file %s", filename); - return; - } - str_t string = winiToString(ctx, options); - fileWriteWholeTextFP(fp, strvInitStr(string)); - strFree(string); - fileClose(fp); -} - -winivalue_t *winiAddValEmpty(winitable_t *table) { - if (!table) return NULL; - vecAppend(table->values, (winivalue_t){0}); - return &vecBack(table->values); -} - -winivalue_t *winiAddVal(winitable_t *table, const char *key, const char *value) { - if (!table) return NULL; - winivalue_t val = { .key = strFromStr(key), .value = strFromStr(value) }; - vecAppend(table->values, val); - return &vecBack(table->values); -} - -winivalue_t *winiAddValStr(winitable_t *table, str_t key, str_t value) { - if (!table) return NULL; - winivalue_t val = { .key = key, .value = value }; - vecAppend(table->values, val); - return &vecBack(table->values); -} - -winivalue_t *winiAddValView(winitable_t *table, strview_t key, strview_t value) { - if (!table) return NULL; - winivalue_t val = { .key = strFromView(key), .value = strFromView(value) }; - vecAppend(table->values, val); - return &vecBack(table->values); -} - -winitable_t *winiAddTablEmpty(iniwriter_t *ctx) { - vecAppend(ctx->tables, (winitable_t){0}); - return &vecBack(ctx->tables); -} - -winitable_t *winiAddTab(iniwriter_t *ctx, const char *name) { - winitable_t tab = { .key = strFromStr(name) }; - vecAppend(ctx->tables, tab); - return &vecBack(ctx->tables); -} - -winitable_t *winiAddTabStr(iniwriter_t *ctx, str_t name) { - winitable_t tab = { .key = name }; - vecAppend(ctx->tables, tab); - return &vecBack(ctx->tables); -} - -winitable_t *winiAddTabView(iniwriter_t *ctx, strview_t name) { - winitable_t tab = { .key = strFromView(name) }; - vecAppend(ctx->tables, tab); - return &vecBack(ctx->tables); -} - -// == PRIVATE FUNCTIONS ======================================================== - -static iniopts_t setDefaultOptions(const iniopts_t *options) { - if (!options) return default_opts; - - iniopts_t opts = default_opts; - - if (options->merge_duplicate_keys) - opts.merge_duplicate_keys = options->merge_duplicate_keys; - - if (options->merge_duplicate_tables) - opts.merge_duplicate_tables = options->merge_duplicate_tables; - - if (options->key_value_divider) - opts.key_value_divider = options->key_value_divider; - - return opts; -} - -static initable_t *findTable(ini_t *ctx, strview_t name) { - if (strvIsEmpty(name)) return NULL; - for (uint32 i = 1; i < vecLen(ctx->tables); ++i) { - if (strvCompare(ctx->tables[i].name, name) == 0) { - return &ctx->tables[i]; - } - } - return NULL; -} - -static inivalue_t *findValue(vec(inivalue_t) values, strview_t key) { - if (strvIsEmpty(key)) return NULL; - for (uint32 i = 0; i < vecLen(values); ++i) { - if (strvCompare(values[i].key, key) == 0) { - return &values[i]; - } - } - return NULL; -} - -static void addTable(ini_t *ctx, str_istream_t *in, const iniopts_t *options) { - istrSkip(in, 1); // skip [ - strview_t name = istrGetview(in, ']'); - istrSkip(in, 1); // skip ] - initable_t *table = options->merge_duplicate_tables ? findTable(ctx, name) : NULL; - if (!table) { - vecAppend(ctx->tables, (initable_t){ name }); - table = &vecBack(ctx->tables); - } - istrIgnore(in, '\n'); istrSkip(in, 1); - while (!istrIsFinished(*in)) { - switch (*in->cur) { - case '\n': case '\r': - return; - case '#': case ';': - istrIgnore(in, '\n'); - break; - default: - addValue(table, in, options); - break; - } - } -} - -static void addValue(initable_t *table, str_istream_t *in, const iniopts_t *options) { - if (!table) fatal("table is null"); - - strview_t key = strvTrim(istrGetview(in, options->key_value_divider)); + strview_t key = strvTrim(istrGetView(in, opts->key_value_divider)); istrSkip(in, 1); - strview_t value = strvTrim(istrGetview(in, '\n')); - // value might be until EOF, in that case no use in skipping - if (!istrIsFinished(*in)) istrSkip(in, 1); // skip newline - inivalue_t *new_value = options->merge_duplicate_keys ? findValue(table->values, key) : NULL; - if (!new_value) { - inivalue_t ini_val = (inivalue_t){ key, value }; - vecAppend(table->values, ini_val); + strview_t value = strvTrim(istrGetView(in, '\n')); + istrSkip(in, 1); + inivalue_t *newval = NULL; + + if (opts->merge_duplicate_keys) { + newval = table->values; + while (newval) { + if (strvEquals(newval->key, key)) { + break; + } + newval = newval->next; + } + } + + if (newval) { + newval->value = value; } else { - new_value->value = value; + newval = alloc(arena, inivalue_t); + newval->key = key; + newval->value = value; + + if (!table->values) { + table->values = newval; + } + else { + table->tail->next = newval; + } + + table->tail = newval; } } + +static void ini__add_table(arena_t *arena, ini_t *ctx, instream_t *in, iniopts_t *options) { + istrSkip(in, 1); // skip [ + strview_t name = istrGetView(in, ']'); + istrSkip(in, 1); // skip ] + initable_t *table = NULL; + + if (options->merge_duplicate_tables) { + table = ctx->tables; + while (table) { + if (strvEquals(table->name, name)) { + break; + } + table = table->next; + } + } + + if (!table) { + table = alloc(arena, initable_t); + + if (!ctx->tables) { + ctx->tables = table; + } + else { + ctx->tail->next = table; + } + + ctx->tail = table; + } + + istrIgnoreAndSkip(in, '\n'); + while (!istrIsFinished(*in)) { + switch (istrPeek(in)) { + case '\n': // fallthrough + case '\r': + return; + case '#': // fallthrough + case ';': + istrIgnoreAndSkip(in, '\n'); + break; + default: + ini__add_value(arena, table, in, options); + break; + } + } +} + +static void ini__parse(arena_t *arena, ini_t *ini, const iniopts_t *options) { + iniopts_t opts = ini__get_options(options); + + instream_t in = istrInitLen(ini->text.buf, ini->text.len); + + while (!istrIsFinished(in)) { + istrSkipWhitespace(&in); + switch (istrPeek(&in)) { + case '[': + ini__add_table(arena, ini, &in, &opts); + break; + case '#': // fallthrough + case ';': + istrIgnoreAndSkip(&in, '\n'); + break; + default: + ini__add_value(arena, ini->tables, &in, &opts); + break; + } + } +} + +#undef INIPUSH + +#include "warnings/colla_warn_end.h" diff --git a/colla/ini.h b/colla/ini.h index 24d8010..7349cf0 100644 --- a/colla/ini.h +++ b/colla/ini.h @@ -1,29 +1,28 @@ #pragma once -#include +#include "collatypes.h" #include "str.h" -#include "vec.h" -#include "utf8.h" +#include "file.h" -#ifdef __cplusplus -extern "C" { -#endif +typedef struct arena_t arena_t; -// == INI READER ======================================================================== - -typedef struct { +typedef struct inivalue_t { strview_t key; strview_t value; + struct inivalue_t *next; } inivalue_t; -typedef struct { +typedef struct initable_t { strview_t name; - vec(inivalue_t) values; + inivalue_t *values; + inivalue_t *tail; + struct initable_t *next; } initable_t; typedef struct { - str_t text; - vec(initable_t) tables; + strview_t text; + initable_t *tables; + initable_t *tail; } ini_t; typedef struct { @@ -32,58 +31,22 @@ typedef struct { char key_value_divider; // default = } iniopts_t; -ini_t iniParse(const char *filename, const iniopts_t *options); -ini_t iniParseString(const char *inistr, const iniopts_t *options); -void iniFree(ini_t ctx); +ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options); +ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options); +ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options); -initable_t *iniGetTable(ini_t *ctx, const char *name); -inivalue_t *iniGet(initable_t *ctx, const char *key); +#define INI_ROOT strv("__ROOT__") -vec(strview_t) iniAsArray(const inivalue_t *value, char delim); -// delim is expected to be a single utf8 character -vec(strview_t) iniAsArrayU8(const inivalue_t *value, const char *delim); -uint64 iniAsUInt(const inivalue_t *value); -int64 iniAsInt(const inivalue_t *value); -double iniAsNum(const inivalue_t *value); -bool iniAsBool(const inivalue_t *value); - -// == INI WRITER ======================================================================== +initable_t *iniGetTable(ini_t *ctx, strview_t name); +inivalue_t *iniGet(initable_t *ctx, strview_t key); typedef struct { - str_t key; - str_t value; -} winivalue_t; + strview_t *values; + usize count; +} iniarray_t; -typedef struct { - str_t key; - vec(winivalue_t) values; -} winitable_t; - -typedef struct { - vec(winitable_t) tables; -} iniwriter_t; - -typedef struct { - bool no_discalimer; // default false - char key_value_divider; // default = -} winiopts_t; - -iniwriter_t winiInit(); -void winiFree(iniwriter_t ctx); - -str_t winiToString(iniwriter_t ctx, const winiopts_t *options); -void winiToFile(iniwriter_t ctx, const char *filename, const winiopts_t *options); - -winivalue_t *winiAddValEmpty(winitable_t *table); -winivalue_t *winiAddVal(winitable_t *table, const char *key, const char *value); -winivalue_t *winiAddValStr(winitable_t *table, str_t key, str_t value); -winivalue_t *winiAddValView(winitable_t *table, strview_t key, strview_t value); - -winitable_t *winiAddTablEmpty(iniwriter_t *ctx); -winitable_t *winiAddTab(iniwriter_t *ctx, const char *name); -winitable_t *winiAddTabStr(iniwriter_t *ctx, str_t name); -winitable_t *winiAddTabView(iniwriter_t *ctx, strview_t name); - -#ifdef __cplusplus -} // extern "C" -#endif +iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim); +uint64 iniAsUInt(inivalue_t *value); +int64 iniAsInt(inivalue_t *value); +double iniAsNum(inivalue_t *value); +bool iniAsBool(inivalue_t *value); \ No newline at end of file diff --git a/colla/json.c b/colla/json.c new file mode 100644 index 0000000..d08cb46 --- /dev/null +++ b/colla/json.c @@ -0,0 +1,288 @@ +#include "json.h" + +#include "warnings/colla_warn_beg.h" + +#include + +#include "strstream.h" +#include "tracelog.h" + +#define json__ensure(c) \ + if (istrGet(in) != (c)) { \ + istrRewindN(in, 1); \ + fatal("wrong character at %zu, should be " #c " but is %c", istrTell(*in), istrPeek(in));\ + } + +str_t json__read_whole(arena_t *arena, const char *filename) { + str_t out = {0}; + FILE *fp = fopen(filename, "rb"); + if (!fp) { + err("could not open %s", filename); + return (str_t){0}; + } + + fseek(fp, 0, SEEK_END); + long len = ftell(fp); + fseek(fp, 0, SEEK_SET); + + out.buf = alloc(arena, char, len + 1); + out.len = len; + + fread(out.buf, 1, len, fp); + + fclose(fp); + + return out; +} + +jsonval_t *json__parse_pair(arena_t *arena, instream_t *in, jsonflags_e flags); +jsonval_t *json__parse_value(arena_t *arena, instream_t *in, jsonflags_e flags); + +bool json__is_value_finished(instream_t *in) { + usize old_pos = istrTell(*in); + + istrSkipWhitespace(in); + switch(istrPeek(in)) { + case '}': // fallthrough + case ']': // fallthrough + case ',': + return true; + } + + in->cur = in->start + old_pos; + return false; +} + +void json__parse_null(instream_t *in) { + strview_t null_view = istrGetViewLen(in, 4); + + if (!strvEquals(null_view, strv("null"))) { + fatal("should be null but is: (%.*s) at %zu", null_view.len, null_view.buf, istrTell(*in)); + } + + if (!json__is_value_finished(in)) { + fatal("null, should be finished, but isn't at %zu", istrTell(*in)); + } +} + +jsonval_t *json__parse_array(arena_t *arena, instream_t *in, jsonflags_e flags) { + json__ensure('['); + + istrSkipWhitespace(in); + + // if it is an empty array + if (istrPeek(in) == ']') { + istrSkip(in, 1); + return NULL; + } + + jsonval_t *head = json__parse_value(arena, in, flags); + jsonval_t *cur = head; + + while (true) { + istrSkipWhitespace(in); + switch (istrGet(in)) { + case ']': + return head; + case ',': + { + istrSkipWhitespace(in); + // trailing comma + if (!(flags & JSON_NO_TRAILING_COMMAS) && istrPeek(in) == ']') { + return head; + } + + jsonval_t *next = json__parse_value(arena, in, flags); + cur->next = next; + next->prev = cur; + cur = next; + break; + } + default: + istrRewindN(in, 1); + fatal("unknown char after array at %zu: (%c)(%d)", istrTell(*in), *in->cur, *in->cur); + } + } + + return NULL; +} + +str_t json__parse_string(arena_t *arena, instream_t *in, jsonflags_e flags) { + istrSkipWhitespace(in); + + json__ensure('"'); + + const char *from = in->cur; + + for (; !istrIsFinished(*in) && *in->cur != '"'; ++in->cur) { + if (istrPeek(in) == '\\') { + ++in->cur; + } + } + + usize len = in->cur - from; + + str_t out = str(arena, from, len); + + json__ensure('"'); + + return out; +} + +double json__parse_number(instream_t *in, jsonflags_e flags) { + double value = 0.0; + istrGetDouble(in, &value); + return value; +} + +bool json__parse_bool(instream_t *in, jsonflags_e flags) { + size_t remaining = istrRemaining(*in); + if (remaining >= 4 && memcmp(in->cur, "true", 4) == 0) { + istrSkip(in, 4); + return true; + } + if (remaining >= 5 && memcmp(in->cur, "false", 5) == 0) { + istrSkip(in, 5); + return false; + } + fatal("unknown boolean at %zu: %.10s", istrTell(*in), in->cur); + return false; +} + +jsonval_t *json__parse_obj(arena_t *arena, instream_t *in, jsonflags_e flags) { + json__ensure('{'); + + istrSkipWhitespace(in); + + // if it is an empty object + if (istrPeek(in) == '}') { + istrSkip(in, 1); + return NULL; + } + + jsonval_t *head = json__parse_pair(arena, in, flags); + jsonval_t *cur = head; + + while (true) { + istrSkipWhitespace(in); + switch (istrGet(in)) { + case '}': + return head; + case ',': + { + istrSkipWhitespace(in); + // trailing commas + if (!(flags & JSON_NO_TRAILING_COMMAS) && istrPeek(in) == '}') { + return head; + } + + jsonval_t *next = json__parse_pair(arena, in, flags); + cur->next = next; + next->prev = cur; + cur = next; + break; + } + default: + istrRewindN(in, 1); + fatal("unknown char after object at %zu: (%c)(%d)", istrTell(*in), *in->cur, *in->cur); + } + } + + return head; +} + +jsonval_t *json__parse_pair(arena_t *arena, instream_t *in, jsonflags_e flags) { + str_t key = json__parse_string(arena, in, flags); + + // skip preamble + istrSkipWhitespace(in); + json__ensure(':'); + + jsonval_t *out = json__parse_value(arena, in, flags); + out->key = key; + return out; +} + +jsonval_t *json__parse_value(arena_t *arena, instream_t *in, jsonflags_e flags) { + jsonval_t *out = alloc(arena, jsonval_t); + + istrSkipWhitespace(in); + + switch (istrPeek(in)) { + // object + case '{': + out->object = json__parse_obj(arena, in, flags); + out->type = JSON_OBJECT; + break; + // array + case '[': + out->array = json__parse_array(arena, in, flags); + out->type = JSON_ARRAY; + break; + // string + case '"': + out->string = json__parse_string(arena, in, flags); + out->type = JSON_STRING; + break; + // boolean + case 't': // fallthrough + case 'f': + out->boolean = json__parse_bool(in, flags); + out->type = JSON_BOOL; + break; + // null + case 'n': + json__parse_null(in); + out->type = JSON_NULL; + break; + // comment + case '/': + fatal("TODO comments"); + break; + // number + default: + out->number = json__parse_number(in, flags); + out->type = JSON_NUMBER; + break; + } + + return out; +} + +json_t jsonParse(arena_t *arena, arena_t scratch, const char *filename, jsonflags_e flags) { + str_t data = json__read_whole(&scratch, filename); + json_t json = jsonParseStr(arena, strv(data), flags); + return json; +} + +json_t jsonParseStr(arena_t *arena, strview_t jsonstr, jsonflags_e flags) { + jsonval_t *root = alloc(arena, jsonval_t); + root->type = JSON_OBJECT; + + instream_t in = istrInitLen(jsonstr.buf, jsonstr.len); + root->object = json__parse_obj(arena, &in, flags); + + return root; +} + +jsonval_t *jsonGet(jsonval_t *node, strview_t key) { + if (!node) return NULL; + + if (node->type != JSON_OBJECT) { + err("passed type is not an object"); + return NULL; + } + + node = node->object; + + while (node) { + if (strvEquals(strv(node->key), key)) { + return node; + } + node = node->next; + } + + return NULL; +} + +#include "warnings/colla_warn_end.h" \ No newline at end of file diff --git a/colla/json.h b/colla/json.h new file mode 100644 index 0000000..2b55231 --- /dev/null +++ b/colla/json.h @@ -0,0 +1,47 @@ +#pragma once + +#include "str.h" +#include "arena.h" + +typedef enum { + JSON_NULL, + JSON_ARRAY, + JSON_STRING, + JSON_NUMBER, + JSON_BOOL, + JSON_OBJECT, +} jsontype_e; + +typedef enum { + JSON_DEFAULT = 0, + JSON_NO_TRAILING_COMMAS = 1 << 0, + JSON_NO_COMMENTS = 1 << 1, +} jsonflags_e; + +typedef struct jsonval_t jsonval_t; + +typedef struct jsonval_t { + jsonval_t *next; + jsonval_t *prev; + + str_t key; + + union { + jsonval_t *array; + str_t string; + double number; + bool boolean; + jsonval_t *object; + }; + jsontype_e type; +} jsonval_t; + +typedef jsonval_t *json_t; + +json_t jsonParse(arena_t *arena, arena_t scratch, const char *filename, jsonflags_e flags); +json_t jsonParseStr(arena_t *arena, strview_t jsonstr, jsonflags_e flags); + +jsonval_t *jsonGet(jsonval_t *node, strview_t key); + +#define json_for(name, arr) for (jsonval_t *name = arr->array; name; name = name->next) +#define json_check(val, js_type) ((val) && (val)->type == js_type) diff --git a/colla/server.c b/colla/server.c new file mode 100644 index 0000000..e9a9838 --- /dev/null +++ b/colla/server.c @@ -0,0 +1,382 @@ +#include "server.h" + +#include + +#include "socket.h" +#include "tracelog.h" +#include "strstream.h" +#include "arena.h" + +#define SERVER_BUFSZ 4096 + +typedef enum { + PARSE_REQ_BEGIN, + PARSE_REQ_PAGE, + PARSE_REQ_VERSION, + PARSE_REQ_FIELDS, + PARSE_REQ_FINISHED, +} server__req_status_e; + +typedef struct { + server_req_t req; + server__req_status_e status; + char fullbuf[SERVER_BUFSZ * 2]; + usize prevbuf_len; +} server__req_ctx_t; + + +typedef struct server__route_t { + str_t page; + server_route_f fn; + void *userdata; + struct server__route_t *next; +} server__route_t; + +typedef struct server_t { + socket_t socket; + server__route_t *routes; + server__route_t *routes_default; + bool should_stop; +} server_t; + +bool server__parse_chunk(arena_t *arena, server__req_ctx_t *ctx, char buffer[SERVER_BUFSZ], usize buflen) { + memcpy(ctx->fullbuf + ctx->prevbuf_len, buffer, buflen); + usize fulllen = ctx->prevbuf_len + buflen; + + instream_t in = istrInitLen(ctx->fullbuf, fulllen); + +#define RESET_STREAM() in.cur = in.start + begin +#define BEGIN_STREAM() begin = istrTell(in) + + usize begin = istrTell(in); + + switch (ctx->status) { + case PARSE_REQ_BEGIN: + { + BEGIN_STREAM(); + + strview_t method = istrGetView(&in, ' '); + if (istrGet(&in) != ' ') { + RESET_STREAM(); + break; + } + + if (strvEquals(method, strv("GET"))) { + ctx->req.method = HTTP_GET; + } + else if(strvEquals(method, strv("POST"))) { + ctx->req.method = HTTP_POST; + } + else { + fatal("unknown method: (%.*s)", method.len, method.buf); + } + + info("parsed method: %.*s", method.len, method.buf); + + ctx->status = PARSE_REQ_PAGE; + } + // fallthrough + case PARSE_REQ_PAGE: + { + BEGIN_STREAM(); + + strview_t page = istrGetView(&in, ' '); + if (istrGet(&in) != ' ') { + RESET_STREAM(); + break; + } + + ctx->req.page = str(arena, page); + + info("parsed page: %.*s", page.len, page.buf); + + ctx->status = PARSE_REQ_VERSION; + } + // fallthrough + case PARSE_REQ_VERSION: + { + BEGIN_STREAM(); + + strview_t version = istrGetView(&in, '\n'); + if (istrGet(&in) != '\n') { + RESET_STREAM(); + break; + } + + if (version.len < 8) { + fatal("version too short: (%.*s)", version.len, version.buf); + } + + if (strvEquals(strvSub(version, 0, 4), strv("HTTP"))) { + fatal("version does not start with HTTP: (%.4s)", version.buf); + } + + // skip HTTP + version = strvRemovePrefix(version, 4); + + uint8 major, minor; + int scanned = sscanf(version.buf, "/%hhu.%hhu", &major, &minor); + + if (scanned != 2) { + fatal("could not scan both major and minor from version: (%.*s)", version.len, version.buf); + } + + ctx->req.version_major = major; + ctx->req.version_minor = minor; + + ctx->status = PARSE_REQ_FIELDS; + } + // fallthrough + case PARSE_REQ_FIELDS: + { + bool finished_parsing = false; + + while (true) { + BEGIN_STREAM(); + + strview_t field = istrGetView(&in, '\n'); + if (istrGet(&in) != '\n') { + RESET_STREAM(); + break; + } + + instream_t field_in = istrInitLen(field.buf, field.len); + + strview_t key = istrGetView(&field_in, ':'); + if (istrGet(&field_in) != ':') { + fatal("field does not have ':' (%.*s)", field.len, field.buf); + } + + istrSkipWhitespace(&field_in); + + strview_t value = istrGetView(&field_in, '\r'); + if (istrGet(&field_in) != '\r') { + warn("field does not finish with \\r: (%.*s)", field.len, field.buf); + RESET_STREAM(); + break; + } + + server_field_t *new_field = alloc(arena, server_field_t); + new_field->key = str(arena, key); + new_field->value = str(arena, value); + + if (!ctx->req.fields) { + ctx->req.fields = new_field; + } + + if (ctx->req.fields_tail) { + ctx->req.fields_tail->next = new_field; + } + + ctx->req.fields_tail = new_field; + + // check if we finished parsing the fields + if (istrGet(&in) == '\r') { + if (istrGet(&in) == '\n') { + finished_parsing = true; + break; + } + else { + istrRewindN(&in, 2); + warn("should have finished parsing field, but apparently not?: (%.*s)", in.cur, istrRemaining(in)); + } + } + else { + istrRewindN(&in, 1); + } + } + + if (!finished_parsing) { + break; + } + + ctx->status = PARSE_REQ_FINISHED; + break; + } + default: break; + } + +#undef RESET_STREAM + + ctx->prevbuf_len = istrRemaining(in); + + memmove(ctx->fullbuf, ctx->fullbuf + istrTell(in), ctx->prevbuf_len); + + return ctx->status == PARSE_REQ_FINISHED; +} + +void server__parse_req_url(arena_t *arena, server_req_t *req) { + instream_t in = istrInitLen(req->page.buf, req->page.len); + istrIgnore(&in, '?'); + // no fields in url + if (istrGet(&in) != '?') { + return; + } + + req->page.len = istrTell(in) - 1; + req->page.buf[req->page.len] = '\0'; + + while (!istrIsFinished(in)) { + strview_t field = istrGetView(&in, '&'); + istrSkip(&in, 1); // skip & + usize pos = strvFind(field, '=', 0); + if (pos == SIZE_MAX) { + fatal("url parameter does not include =: %.*s", field.buf, field.len); + } + strview_t key = strvSub(field, 0, pos); + strview_t val = strvSub(field, pos + 1, SIZE_MAX); + + server_field_t *f = alloc(arena, server_field_t); + f->key = str(arena, key); + f->value = str(arena, val); + f->next = req->page_fields; + req->page_fields = f; + } +} + +server_t *serverSetup(arena_t *arena, uint16 port) { + if (!skInit()) { + fatal("couldn't initialise sockets: %s", skGetErrorString()); + } + + socket_t sk = skOpen(SOCK_TCP); + if (!skIsValid(sk)) { + fatal("couldn't open socket: %s", skGetErrorString()); + } + + skaddrin_t addr = { + .sin_family = AF_INET, + .sin_addr.s_addr = INADDR_ANY, + .sin_port = htons(port), + }; + + if (!skBindPro(sk, (skaddr_t *)&addr, sizeof(addr))) { + fatal("could not bind socket: %s", skGetErrorString()); + } + + if (!skListenPro(sk, 10)) { + fatal("could not listen on socket: %s", skGetErrorString()); + } + + server_t *server = alloc(arena, server_t); + + server->socket = sk; + + return server; +} + +void serverRoute(arena_t *arena, server_t *server, strview_t page, server_route_f cb, void *userdata) { + // check if a route for that page already exists + server__route_t *r = server->routes; + + while (r) { + if (strvEquals(strv(r->page), page)) { + r->fn = cb; + r->userdata = userdata; + break; + } + r = r->next; + } + + // no route found, make a new one + if (!r) { + r = alloc(arena, server__route_t); + r->next = server->routes; + server->routes = r; + + r->page = str(arena, page); + } + + r->fn = cb; + r->userdata = userdata; +} + +void serverRouteDefault(arena_t *arena, server_t *server, server_route_f cb, void *userdata) { + server__route_t *r = server->routes_default; + + if (!r) { + r = alloc(arena, server__route_t); + server->routes_default = r; + } + + r->fn = cb; + r->userdata = userdata; +} + +void serverStart(arena_t scratch, server_t *server) { + usize start = arenaTell(&scratch); + + while (!server->should_stop) { + socket_t client = skAccept(server->socket); + if (!skIsValid(client)) { + err("accept failed"); + continue; + } + + info("received connection: %zu", client); + + arenaRewind(&scratch, start); + + server__req_ctx_t req_ctx = {0}; + + char buffer[SERVER_BUFSZ]; + int read = 0; + do { + read = skReceive(client, buffer, sizeof(buffer)); + if(read == -1) { + fatal("couldn't get the data from the server: %s", skGetErrorString()); + } + if (server__parse_chunk(&scratch, &req_ctx, buffer, read)) { + break; + } + } while(read != 0); + + info("parsed request"); + + server_req_t req = req_ctx.req; + + server__parse_req_url(&scratch, &req); + + server__route_t *route = server->routes; + while (route) { + if (strEquals(route->page, req.page)) { + break; + } + route = route->next; + } + + if (!route) { + route = server->routes_default; + } + + str_t response = route->fn(scratch, server, &req, route->userdata); + + skSend(client, response.buf, response.len); + skClose(client); + } + + if (!skClose(server->socket)) { + fatal("couldn't close socket: %s", skGetErrorString()); + } +} + +void serverStop(server_t *server) { + server->should_stop = true; +} + +str_t serverMakeResponse(arena_t *arena, int status_code, strview_t content_type, strview_t body) { + return strFmt( + arena, + + "HTTP/1.1 %d %s\r\n" + "Content-Type: %.*s\r\n" + "\r\n" + "%.*s", + + status_code, httpGetStatusString(status_code), + content_type.len, content_type.buf, + body.len, body.buf + ); +} + +#undef SERVER_BUFSZ \ No newline at end of file diff --git a/colla/server.h b/colla/server.h new file mode 100644 index 0000000..c5df090 --- /dev/null +++ b/colla/server.h @@ -0,0 +1,36 @@ +#pragma once + +#include "collatypes.h" +#include "str.h" +#include "http.h" + +typedef struct arena_t arena_t; +typedef struct server_t server_t; + +typedef struct server_field_t { + str_t key; + str_t value; + struct server_field_t *next; +} server_field_t; + +typedef struct { + http_method_e method; + str_t page; + server_field_t *page_fields; + uint8 version_minor; + uint8 version_major; + server_field_t *fields; + server_field_t *fields_tail; + // buffer_t body; +} server_req_t; + +typedef str_t (*server_route_f)(arena_t scratch, server_t *server, server_req_t *req, void *userdata); + + +server_t *serverSetup(arena_t *arena, uint16 port); +void serverRoute(arena_t *arena, server_t *server, strview_t page, server_route_f cb, void *userdata); +void serverRouteDefault(arena_t *arena, server_t *server, server_route_f cb, void *userdata); +void serverStart(arena_t scratch, server_t *server); +void serverStop(server_t *server); + +str_t serverMakeResponse(arena_t *arena, int status_code, strview_t content_type, strview_t body); \ No newline at end of file diff --git a/colla/socket.c b/colla/socket.c index 72d0e66..8eeb630 100644 --- a/colla/socket.c +++ b/colla/socket.c @@ -1,119 +1,114 @@ #include "socket.h" -#include -#include "tracelog.h" - -#ifndef NDEBUG -// VERY MUCH NOT THREAD SAFE -static int initialize_count = 0; +#if COLLA_WIN && COLLA_CMT_LIB +#pragma comment(lib, "Ws2_32") #endif -#if SOCK_WINDOWS -static bool _win_skInit(); -static bool _win_skCleanup(); -static int _win_skGetError(); -static const char *_win_skGetErrorString(); +#if COLLA_WIN +#include -#define SOCK_CALL(fun) _win_##fun +bool skInit(void) { + WSADATA w; + int error = WSAStartup(0x0202, &w); + return error == 0; +} -#elif SOCK_POSIX +bool skCleanup(void) { + return WSACleanup() == 0; +} + +bool skClose(socket_t sock) { + return closesocket(sock) != -1; +} + +int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) { + return WSAPoll(to_poll, num_to_poll, timeout); +} + +int skGetError(void) { + return WSAGetLastError(); +} + +#else + +#include +#include #include #include #include #include // strerror #include -#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 skInit(void) { + return true; } -bool skCleanup() { -#ifndef NDEBUG - --initialize_count; -#endif - return SOCK_CALL(skCleanup()); +bool skCleanup(void) { + return true; } -socket_t skOpen(sktype_t type) { +bool skClose(socket_t sock) { + return close(sock) != -1; +} + +int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) { + return poll(to_poll, num_to_poll, timeout); +} + +int skGetError(void) { + return errno; +} + +const char *skGetErrorString(void) { + return strerror(errno); +} + +#endif + +socket_t skOpen(sktype_e 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; + 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 (socket_t){0}; } 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 skIsValid(socket_t sock) { + return sock != (socket_t)-1; } -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; +skaddrin_t skAddrinInit(const char *ip, uint16_t port) { + return (skaddrin_t){ + .sin_family = AF_INET, + .sin_port = htons(port), + // TODO use inet_pton instead + .sin_addr.s_addr = inet_addr(ip), + }; } 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)); + skaddrin_t addr = skAddrinInit(ip, port); + return skBindPro(sock, (skaddr_t *)&addr, sizeof(addr)); } -bool skBindPro(socket_t sock, const sk_addr_t *name, sk_len_t namelen) { - return bind(sock, name, namelen) != SOCKET_ERROR; +bool skBindPro(socket_t sock, const skaddr_t *name, int namelen) { + return bind(sock, name, namelen) != -1; } bool skListen(socket_t sock) { @@ -121,16 +116,16 @@ bool skListen(socket_t sock) { } bool skListenPro(socket_t sock, int backlog) { - return listen(sock, backlog) != SOCKET_ERROR; + return listen(sock, backlog) != -1; } socket_t skAccept(socket_t sock) { - sk_addrin_t addr; - sk_len_t addr_size = (sk_len_t)sizeof(addr); - return skAcceptPro(sock, (sk_addr_t *) &addr, &addr_size); + skaddrin_t addr = {0}; + int addr_size = sizeof(addr); + return skAcceptPro(sock, (skaddr_t *)&addr, &addr_size); } -socket_t skAcceptPro(socket_t sock, sk_addr_t *addr, sk_len_t *addrlen) { +socket_t skAcceptPro(socket_t sock, skaddr_t *addr, int *addrlen) { return accept(sock, addr, addrlen); } @@ -143,16 +138,13 @@ bool skConnect(socket_t sock, const char *server, unsigned short server_port) { 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); + skaddrin_t addr = skAddrinInit(address, server_port); - return skConnectPro(sock, (sk_addr_t *) &addr, sizeof(addr)); + return skConnectPro(sock, (skaddr_t *)&addr, sizeof(addr)); } -bool skConnectPro(socket_t sock, const sk_addr_t *name, sk_len_t namelen) { - return connect(sock, name, namelen) != SOCKET_ERROR; +bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen) { + return connect(sock, name, namelen) != -1; } int skSend(socket_t sock, const void *buf, int len) { @@ -163,11 +155,11 @@ int skSendPro(socket_t sock, const void *buf, int len, int flags) { return send(sock, buf, len, flags); } -int skSendTo(socket_t sock, const void *buf, int len, const sk_addrin_t *to) { - return skSendToPro(sock, buf, len, 0, (sk_addr_t*) to, sizeof(sk_addrin_t)); +int skSendTo(socket_t sock, const void *buf, int len, const skaddrin_t *to) { + return skSendToPro(sock, buf, len, 0, (skaddr_t*)to, sizeof(skaddrin_t)); } -int skSendToPro(socket_t sock, const void *buf, int len, int flags, const sk_addr_t *to, int tolen) { +int skSendToPro(socket_t sock, const void *buf, int len, int flags, const skaddr_t *to, int tolen) { return sendto(sock, buf, len, flags, to, tolen); } @@ -179,52 +171,19 @@ int skReceivePro(socket_t sock, void *buf, int len, int flags) { return recv(sock, buf, len, flags); } -int skReceiveFrom(socket_t sock, void *buf, int len, sk_addrin_t *from) { - sk_len_t fromlen = sizeof(sk_addr_t); - return skReceiveFromPro(sock, buf, len, 0, (sk_addr_t*)from, &fromlen); +int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from) { + int fromlen = sizeof(skaddr_t); + return skReceiveFromPro(sock, buf, len, 0, (skaddr_t*)from, &fromlen); } -int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, sk_addr_t *from, sk_len_t *fromlen) { +int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, skaddr_t *from, int *fromlen) { return recvfrom(sock, buf, len, flags, from, fromlen); } -int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) { -#if SOCK_WINDOWS - return WSAPoll(to_poll, num_to_poll, timeout); -#elif SOCK_POSIX - return poll(to_poll, num_to_poll, timeout); -#endif -} - -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()) { +// put this at the end of file to not make everything else unreadable +#if COLLA_WIN +const char *skGetErrorString(void) { + switch(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."; @@ -324,22 +283,4 @@ static const char *_win_skGetErrorString() { 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 +#endif \ No newline at end of file diff --git a/colla/socket.h b/colla/socket.h index b0ce112..c0cbfdc 100644 --- a/colla/socket.h +++ b/colla/socket.h @@ -1,45 +1,22 @@ #pragma once -#ifdef __cplusplus -extern "C" { -#endif +#include "collatypes.h" -#include -#include - -#ifdef _WIN32 - #define SOCK_WINDOWS 1 +#if COLLA_WIN +#include #else - #define SOCK_POSIX 1 +#include #endif -#if SOCK_WINDOWS - #pragma warning(disable:4996) // _WINSOCK_DEPRECATED_NO_WARNINGS - #include "win32_slim.h" - #include - #include - typedef SOCKET socket_t; - typedef int sk_len_t; -#elif SOCK_POSIX - #include - #include - #include - typedef int socket_t; - typedef uint32_t sk_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 uintptr_t socket_t; +typedef struct sockaddr skaddr_t; +typedef struct sockaddr_in skaddrin_t; typedef struct pollfd skpoll_t; typedef enum { SOCK_TCP, SOCK_UDP, -} sktype_t; - -// == RAW SOCKETS ========================================== +} sktype_e; // Initialize sockets, returns true on success bool skInit(void); @@ -47,7 +24,7 @@ bool skInit(void); bool skCleanup(void); // Opens a socket, check socket_t with skValid -socket_t skOpen(sktype_t type); +socket_t skOpen(sktype_e type); // Opens a socket using 'protocol', options are // ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp // check socket_t with skValid @@ -62,12 +39,12 @@ bool skIsValid(socket_t sock); bool skClose(socket_t sock); // Fill out a sk_addrin_t structure with "ip" and "port" -sk_addrin_t skAddrinInit(const char *ip, uint16_t port); +skaddrin_t skAddrinInit(const char *ip, uint16_t port); // 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, sk_len_t namelen); +bool skBindPro(socket_t sock, const skaddr_t *name, int namelen); // Place a socket in a state in which it is listening for an incoming connection bool skListen(socket_t sock); @@ -77,29 +54,29 @@ 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, sk_len_t *addrlen); +socket_t skAcceptPro(socket_t sock, skaddr_t *addr, int *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, sk_len_t namelen); +bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen); // Sends data on a socket, returns true on success int skSend(socket_t sock, const void *buf, int len); // Sends data on a socket, returns true on success int skSendPro(socket_t sock, const void *buf, int len, int flags); // Sends data to a specific destination -int skSendTo(socket_t sock, const void *buf, int len, const sk_addrin_t *to); +int skSendTo(socket_t sock, const void *buf, int len, const skaddrin_t *to); // Sends data to a specific destination -int skSendToPro(socket_t sock, const void *buf, int len, int flags, const sk_addr_t *to, int tolen); +int skSendToPro(socket_t sock, const void *buf, int len, int flags, const skaddr_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, void *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, void *buf, int len, int flags); // Receives a datagram and stores the source address. -int skReceiveFrom(socket_t sock, void *buf, int len, sk_addrin_t *from); +int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from); // Receives a datagram and stores the source address. -int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, sk_addr_t *from, sk_len_t *fromlen); +int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, skaddr_t *from, int *fromlen); // Wait for an event on some sockets int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout); @@ -108,14 +85,3 @@ int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout); 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 diff --git a/colla/stb/stb_sprintf.h b/colla/stb/stb_sprintf.h new file mode 100644 index 0000000..9711697 --- /dev/null +++ b/colla/stb/stb_sprintf.h @@ -0,0 +1,1930 @@ +// stb_sprintf - v1.10 - public domain snprintf() implementation +// originally by Jeff Roberts / RAD Game Tools, 2015/10/20 +// http://github.com/nothings/stb +// +// allowed types: sc uidBboXx p AaGgEef n +// lengths : hh h ll j z t I64 I32 I +// +// Contributors: +// Fabian "ryg" Giesen (reformatting) +// github:aganm (attribute format) +// +// Contributors (bugfixes): +// github:d26435 +// github:trex78 +// github:account-login +// Jari Komppa (SI suffixes) +// Rohit Nirmal +// Marcin Wojdyr +// Leonard Ritter +// Stefano Zanotti +// Adam Allison +// Arvid Gerstmann +// Markus Kolb +// +// LICENSE: +// +// See end of file for license information. + +#define STB_SPRINTF_IMPLEMENTATION + +#ifndef STB_SPRINTF_H_INCLUDE +#define STB_SPRINTF_H_INCLUDE + +/* +Single file sprintf replacement. + +Originally written by Jeff Roberts at RAD Game Tools - 2015/10/20. +Hereby placed in public domain. + +This is a full sprintf replacement that supports everything that +the C runtime sprintfs support, including float/double, 64-bit integers, +hex floats, field parameters (%*.*d stuff), length reads backs, etc. + +Why would you need this if sprintf already exists? Well, first off, +it's *much* faster (see below). It's also much smaller than the CRT +versions code-space-wise. We've also added some simple improvements +that are super handy (commas in thousands, callbacks at buffer full, +for example). Finally, the format strings for MSVC and GCC differ +for 64-bit integers (among other small things), so this lets you use +the same format strings in cross platform code. + +It uses the standard single file trick of being both the header file +and the source itself. If you just include it normally, you just get +the header file function definitions. To get the code, you include +it from a C or C++ file and define STB_SPRINTF_IMPLEMENTATION first. + +It only uses va_args macros from the C runtime to do it's work. It +does cast doubles to S64s and shifts and divides U64s, which does +drag in CRT code on most platforms. + +It compiles to roughly 8K with float support, and 4K without. +As a comparison, when using MSVC static libs, calling sprintf drags +in 16K. + +API: +==== +int stbsp_sprintf( char * buf, char const * fmt, ... ) +int stbsp_snprintf( char * buf, int count, char const * fmt, ... ) + Convert an arg list into a buffer. stbsp_snprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintf( char * buf, char const * fmt, va_list va ) +int stbsp_vsnprintf( char * buf, int count, char const * fmt, va_list va ) + Convert a va_list arg list into a buffer. stbsp_vsnprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintfcb( STBSP_SPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va ) + typedef char * STBSP_SPRINTFCB( char const * buf, void * user, int len ); + Convert into a buffer, calling back every STB_SPRINTF_MIN chars. + Your callback can then copy the chars out, print them or whatever. + This function is actually the workhorse for everything else. + The buffer you pass in must hold at least STB_SPRINTF_MIN characters. + // you return the next buffer to use or 0 to stop converting + +void stbsp_set_separators( char comma, char period ) + Set the comma and period characters to use. + +FLOATS/DOUBLES: +=============== +This code uses a internal float->ascii conversion method that uses +doubles with error correction (double-doubles, for ~105 bits of +precision). This conversion is round-trip perfect - that is, an atof +of the values output here will give you the bit-exact double back. + +One difference is that our insignificant digits will be different than +with MSVC or GCC (but they don't match each other either). We also +don't attempt to find the minimum length matching float (pre-MSVC15 +doesn't either). + +If you don't need float or doubles at all, define STB_SPRINTF_NOFLOAT +and you'll save 4K of code space. + +64-BIT INTS: +============ +This library also supports 64-bit integers and you can use MSVC style or +GCC style indicators (%I64d or %lld). It supports the C99 specifiers +for size_t and ptr_diff_t (%jd %zd) as well. + +EXTRAS: +======= +Like some GCCs, for integers and floats, you can use a ' (single quote) +specifier and commas will be inserted on the thousands: "%'d" on 12345 +would print 12,345. + +For integers and floats, you can use a "$" specifier and the number +will be converted to float and then divided to get kilo, mega, giga or +tera and then printed, so "%$d" 1000 is "1.0 k", "%$.2d" 2536000 is +"2.53 M", etc. For byte values, use two $:s, like "%$$d" to turn +2536000 to "2.42 Mi". If you prefer JEDEC suffixes to SI ones, use three +$:s: "%$$$d" -> "2.42 M". To remove the space between the number and the +suffix, add "_" specifier: "%_$d" -> "2.53M". + +In addition to octal and hexadecimal conversions, you can print +integers in binary: "%b" for 256 would print 100. + +PERFORMANCE vs MSVC 2008 32-/64-bit (GCC is even slower than MSVC): +=================================================================== +"%d" across all 32-bit ints (4.8x/4.0x faster than 32-/64-bit MSVC) +"%24d" across all 32-bit ints (4.5x/4.2x faster) +"%x" across all 32-bit ints (4.5x/3.8x faster) +"%08x" across all 32-bit ints (4.3x/3.8x faster) +"%f" across e-10 to e+10 floats (7.3x/6.0x faster) +"%e" across e-10 to e+10 floats (8.1x/6.0x faster) +"%g" across e-10 to e+10 floats (10.0x/7.1x faster) +"%f" for values near e-300 (7.9x/6.5x faster) +"%f" for values near e+300 (10.0x/9.1x faster) +"%e" for values near e-300 (10.1x/7.0x faster) +"%e" for values near e+300 (9.2x/6.0x faster) +"%.320f" for values near e-300 (12.6x/11.2x faster) +"%a" for random values (8.6x/4.3x faster) +"%I64d" for 64-bits with 32-bit values (4.8x/3.4x faster) +"%I64d" for 64-bits > 32-bit values (4.9x/5.5x faster) +"%s%s%s" for 64 char strings (7.1x/7.3x faster) +"...512 char string..." ( 35.0x/32.5x faster!) +*/ + +#if defined(__clang__) + #if defined(__has_feature) && defined(__has_attribute) + #if __has_feature(address_sanitizer) + #if __has_attribute(__no_sanitize__) + #define STBSP__ASAN __attribute__((__no_sanitize__("address"))) + #elif __has_attribute(__no_sanitize_address__) + #define STBSP__ASAN __attribute__((__no_sanitize_address__)) + #elif __has_attribute(__no_address_safety_analysis__) + #define STBSP__ASAN __attribute__((__no_address_safety_analysis__)) + #endif + #endif + #endif +#elif defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) + #if defined(__SANITIZE_ADDRESS__) && __SANITIZE_ADDRESS__ + #define STBSP__ASAN __attribute__((__no_sanitize_address__)) + #endif +#endif + +#ifndef STBSP__ASAN +#define STBSP__ASAN +#endif + +#ifdef STB_SPRINTF_STATIC +#define STBSP__PUBLICDEC static +#define STBSP__PUBLICDEF static STBSP__ASAN +#else +#ifdef __cplusplus +#define STBSP__PUBLICDEC extern "C" +#define STBSP__PUBLICDEF extern "C" STBSP__ASAN +#else +#define STBSP__PUBLICDEC extern +#define STBSP__PUBLICDEF STBSP__ASAN +#endif +#endif + +#if defined(__has_attribute) + #if __has_attribute(format) + #define STBSP__ATTRIBUTE_FORMAT(fmt,va) __attribute__((format(printf,fmt,va))) + #endif +#endif + +#ifndef STBSP__ATTRIBUTE_FORMAT +#define STBSP__ATTRIBUTE_FORMAT(fmt,va) +#endif + +#ifdef _MSC_VER +#define STBSP__NOTUSED(v) (void)(v) +#else +#define STBSP__NOTUSED(v) (void)sizeof(v) +#endif + +#include // for va_arg(), va_list() +#include // size_t, ptrdiff_t + +#ifndef STB_SPRINTF_MIN +#define STB_SPRINTF_MIN 512 // how many characters per callback +#endif +typedef char *STBSP_SPRINTFCB(const char *buf, void *user, int len); + +#ifndef STB_SPRINTF_DECORATE +#define STB_SPRINTF_DECORATE(name) stbsp_##name // define this before including if you want to change the names +#endif + +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, int count, char const *fmt, va_list va); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(2,3); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(3,4); + +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEC void STB_SPRINTF_DECORATE(set_separators)(char comma, char period); + +#endif // STB_SPRINTF_H_INCLUDE + +#ifdef STB_SPRINTF_IMPLEMENTATION + +#define stbsp__uint32 unsigned int +#define stbsp__int32 signed int + +#ifdef _MSC_VER +#define stbsp__uint64 unsigned __int64 +#define stbsp__int64 signed __int64 +#else +#define stbsp__uint64 unsigned long long +#define stbsp__int64 signed long long +#endif +#define stbsp__uint16 unsigned short + +#ifndef stbsp__uintptr +#if defined(__ppc64__) || defined(__powerpc64__) || defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) || defined(__s390x__) +#define stbsp__uintptr stbsp__uint64 +#else +#define stbsp__uintptr stbsp__uint32 +#endif +#endif + +#ifndef STB_SPRINTF_MSVC_MODE // used for MSVC2013 and earlier (MSVC2015 matches GCC) +#if defined(_MSC_VER) && (_MSC_VER < 1900) +#define STB_SPRINTF_MSVC_MODE +#endif +#endif + +#ifdef STB_SPRINTF_NOUNALIGNED // define this before inclusion to force stbsp_sprintf to always use aligned accesses +#define STBSP__UNALIGNED(code) +#else +#define STBSP__UNALIGNED(code) code +#endif + +#ifndef STB_SPRINTF_NOFLOAT +// internal float utility functions +static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits); +static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value); +#define STBSP__SPECIAL 0x7000 +#endif + +static char stbsp__period = '.'; +static char stbsp__comma = ','; +static struct +{ + short temp; // force next field to be 2-byte aligned + char pair[201]; +} stbsp__digitpair = +{ + 0, + "00010203040506070809101112131415161718192021222324" + "25262728293031323334353637383940414243444546474849" + "50515253545556575859606162636465666768697071727374" + "75767778798081828384858687888990919293949596979899" +}; + +STBSP__PUBLICDEF void STB_SPRINTF_DECORATE(set_separators)(char pcomma, char pperiod) +{ + stbsp__period = pperiod; + stbsp__comma = pcomma; +} + +#define STBSP__LEFTJUST 1 +#define STBSP__LEADINGPLUS 2 +#define STBSP__LEADINGSPACE 4 +#define STBSP__LEADING_0X 8 +#define STBSP__LEADINGZERO 16 +#define STBSP__INTMAX 32 +#define STBSP__TRIPLET_COMMA 64 +#define STBSP__NEGATIVE 128 +#define STBSP__METRIC_SUFFIX 256 +#define STBSP__HALFWIDTH 512 +#define STBSP__METRIC_NOSPACE 1024 +#define STBSP__METRIC_1024 2048 +#define STBSP__METRIC_JEDEC 4096 + +static void stbsp__lead_sign(stbsp__uint32 fl, char *sign) +{ + sign[0] = 0; + if (fl & STBSP__NEGATIVE) { + sign[0] = 1; + sign[1] = '-'; + } else if (fl & STBSP__LEADINGSPACE) { + sign[0] = 1; + sign[1] = ' '; + } else if (fl & STBSP__LEADINGPLUS) { + sign[0] = 1; + sign[1] = '+'; + } +} + +static STBSP__ASAN stbsp__uint32 stbsp__strlen_limited(char const *s, stbsp__uint32 limit) +{ + char const * sn = s; + + // get up to 4-byte alignment + for (;;) { + if (((stbsp__uintptr)sn & 3) == 0) + break; + + if (!limit || *sn == 0) + return (stbsp__uint32)(sn - s); + + ++sn; + --limit; + } + + // scan over 4 bytes at a time to find terminating 0 + // this will intentionally scan up to 3 bytes past the end of buffers, + // but becase it works 4B aligned, it will never cross page boundaries + // (hence the STBSP__ASAN markup; the over-read here is intentional + // and harmless) + while (limit >= 4) { + stbsp__uint32 v = *(stbsp__uint32 *)sn; + // bit hack to find if there's a 0 byte in there + if ((v - 0x01010101) & (~v) & 0x80808080UL) + break; + + sn += 4; + limit -= 4; + } + + // handle the last few characters to find actual size + while (limit && *sn) { + ++sn; + --limit; + } + + return (stbsp__uint32)(sn - s); +} + +typedef struct { + char *buf; + size_t len; +} stb__strv_t; + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va) +{ + static char hex[] = "0123456789abcdefxp"; + static char hexu[] = "0123456789ABCDEFXP"; + char *bf; + char const *f; + int tlen = 0; + + bf = buf; + f = fmt; + for (;;) { + stbsp__int32 fw, pr, tz; + stbsp__uint32 fl; + + // macros for the callback buffer stuff + #define stbsp__chk_cb_bufL(bytes) \ + { \ + int len = (int)(bf - buf); \ + if ((len + (bytes)) >= STB_SPRINTF_MIN) { \ + tlen += len; \ + if (0 == (bf = buf = callback(buf, user, len))) \ + goto done; \ + } \ + } + #define stbsp__chk_cb_buf(bytes) \ + { \ + if (callback) { \ + stbsp__chk_cb_bufL(bytes); \ + } \ + } + #define stbsp__flush_cb() \ + { \ + stbsp__chk_cb_bufL(STB_SPRINTF_MIN - 1); \ + } // flush if there is even one byte in the buffer + #define stbsp__cb_buf_clamp(cl, v) \ + cl = v; \ + if (callback) { \ + int lg = STB_SPRINTF_MIN - (int)(bf - buf); \ + if (cl > lg) \ + cl = lg; \ + } + + // fast copy everything up to the next % (or end of string) + for (;;) { + while (((stbsp__uintptr)f) & 3) { + schk1: + if (f[0] == '%') + goto scandd; + schk2: + if (f[0] == 0) + goto endfmt; + stbsp__chk_cb_buf(1); + *bf++ = f[0]; + ++f; + } + for (;;) { + // Check if the next 4 bytes contain %(0x25) or end of string. + // Using the 'hasless' trick: + // https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord + stbsp__uint32 v, c; + v = *(stbsp__uint32 *)f; + c = (~v) & 0x80808080; + if (((v ^ 0x25252525) - 0x01010101) & c) + goto schk1; + if ((v - 0x01010101) & c) + goto schk2; + if (callback) + if ((STB_SPRINTF_MIN - (int)(bf - buf)) < 4) + goto schk1; + #ifdef STB_SPRINTF_NOUNALIGNED + if(((stbsp__uintptr)bf) & 3) { + bf[0] = f[0]; + bf[1] = f[1]; + bf[2] = f[2]; + bf[3] = f[3]; + } else + #endif + { + *(stbsp__uint32 *)bf = v; + } + bf += 4; + f += 4; + } + } + scandd: + + ++f; + + // ok, we have a percent, read the modifiers first + fw = 0; + pr = -1; + fl = 0; + tz = 0; + + // flags + for (;;) { + switch (f[0]) { + // if we have left justify + case '-': + fl |= STBSP__LEFTJUST; + ++f; + continue; + // if we have leading plus + case '+': + fl |= STBSP__LEADINGPLUS; + ++f; + continue; + // if we have leading space + case ' ': + fl |= STBSP__LEADINGSPACE; + ++f; + continue; + // if we have leading 0x + case '#': + fl |= STBSP__LEADING_0X; + ++f; + continue; + // if we have thousand commas + case '\'': + fl |= STBSP__TRIPLET_COMMA; + ++f; + continue; + // if we have kilo marker (none->kilo->kibi->jedec) + case '$': + if (fl & STBSP__METRIC_SUFFIX) { + if (fl & STBSP__METRIC_1024) { + fl |= STBSP__METRIC_JEDEC; + } else { + fl |= STBSP__METRIC_1024; + } + } else { + fl |= STBSP__METRIC_SUFFIX; + } + ++f; + continue; + // if we don't want space between metric suffix and number + case '_': + fl |= STBSP__METRIC_NOSPACE; + ++f; + continue; + // if we have leading zero + case '0': + fl |= STBSP__LEADINGZERO; + ++f; + goto flags_done; + default: goto flags_done; + } + } + flags_done: + + // get the field width + if (f[0] == '*') { + fw = va_arg(va, stbsp__uint32); + ++f; + } else { + while ((f[0] >= '0') && (f[0] <= '9')) { + fw = fw * 10 + f[0] - '0'; + f++; + } + } + // get the precision + if (f[0] == '.') { + ++f; + if (f[0] == '*') { + pr = va_arg(va, stbsp__uint32); + ++f; + } else { + pr = 0; + while ((f[0] >= '0') && (f[0] <= '9')) { + pr = pr * 10 + f[0] - '0'; + f++; + } + } + } + + // handle integer size overrides + switch (f[0]) { + // are we halfwidth? + case 'h': + fl |= STBSP__HALFWIDTH; + ++f; + if (f[0] == 'h') + ++f; // QUARTERWIDTH + break; + // are we 64-bit (unix style) + case 'l': + fl |= ((sizeof(long) == 8) ? STBSP__INTMAX : 0); + ++f; + if (f[0] == 'l') { + fl |= STBSP__INTMAX; + ++f; + } + break; + // are we 64-bit on intmax? (c99) + case 'j': + fl |= (sizeof(size_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + // are we 64-bit on size_t or ptrdiff_t? (c99) + case 'z': + fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + case 't': + fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + // are we 64-bit (msft style) + case 'I': + if ((f[1] == '6') && (f[2] == '4')) { + fl |= STBSP__INTMAX; + f += 3; + } else if ((f[1] == '3') && (f[2] == '2')) { + f += 3; + } else { + fl |= ((sizeof(void *) == 8) ? STBSP__INTMAX : 0); + ++f; + } + break; + default: break; + } + + // handle each replacement + switch (f[0]) { + #define STBSP__NUMSZ 512 // big enough for e308 (with commas) or e-307 + char num[STBSP__NUMSZ]; + char lead[8]; + char tail[8]; + char *s; + char const *h; + stbsp__uint32 l, n, cs; + stbsp__uint64 n64; +#ifndef STB_SPRINTF_NOFLOAT + double fv; +#endif + stbsp__int32 dp; + char const *sn; + + stb__strv_t strv; + + case 's': + // get the string + s = va_arg(va, char *); + if (s == 0) + s = (char *)"null"; + // get the length, limited to desired precision + // always limit to ~0u chars since our counts are 32b + l = stbsp__strlen_limited(s, (pr >= 0) ? pr : ~0u); + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + // copy the string in + goto scopy; + + case 'v': + // get the view + strv = va_arg(va, stb__strv_t); + s = strv.buf; + if (s == 0) + s = (char *)"null"; + l = (unsigned int)strv.len; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + // copy the string in + goto scopy; + + case 'c': // char + // get the character + s = num + STBSP__NUMSZ - 1; + *s = (char)va_arg(va, int); + l = 1; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + + case 'n': // weird write-bytes specifier + { + int *d = va_arg(va, int *); + *d = tlen + (int)(bf - buf); + } break; + +#ifdef STB_SPRINTF_NOFLOAT + case 'A': // float + case 'a': // hex float + case 'G': // float + case 'g': // float + case 'E': // float + case 'e': // float + case 'f': // float + va_arg(va, double); // eat it + s = (char *)"No float"; + l = 8; + lead[0] = 0; + tail[0] = 0; + pr = 0; + cs = 0; + STBSP__NOTUSED(dp); + goto scopy; +#else + case 'A': // hex float + case 'a': // hex float + h = (f[0] == 'A') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_parts((stbsp__int64 *)&n64, &dp, fv)) + fl |= STBSP__NEGATIVE; + + s = num + 64; + + stbsp__lead_sign(fl, lead); + + if (dp == -1023) + dp = (n64) ? -1022 : 0; + else + n64 |= (((stbsp__uint64)1) << 52); + n64 <<= (64 - 56); + if (pr < 15) + n64 += ((((stbsp__uint64)8) << 56) >> (pr * 4)); +// add leading chars + +#ifdef STB_SPRINTF_MSVC_MODE + *s++ = '0'; + *s++ = 'x'; +#else + lead[1 + lead[0]] = '0'; + lead[2 + lead[0]] = 'x'; + lead[0] += 2; +#endif + *s++ = h[(n64 >> 60) & 15]; + n64 <<= 4; + if (pr) + *s++ = stbsp__period; + sn = s; + + // print the bits + n = pr; + if (n > 13) + n = 13; + if (pr > (stbsp__int32)n) + tz = pr - n; + pr = 0; + while (n--) { + *s++ = h[(n64 >> 60) & 15]; + n64 <<= 4; + } + + // print the expo + tail[1] = h[17]; + if (dp < 0) { + tail[2] = '-'; + dp = -dp; + } else + tail[2] = '+'; + n = (dp >= 1000) ? 6 : ((dp >= 100) ? 5 : ((dp >= 10) ? 4 : 3)); + tail[0] = (char)n; + for (;;) { + tail[n] = '0' + dp % 10; + if (n <= 3) + break; + --n; + dp /= 10; + } + + dp = (int)(s - sn); + l = (int)(s - (num + 64)); + s = num + 64; + cs = 1 + (3 << 24); + goto scopy; + + case 'G': // float + case 'g': // float + h = (f[0] == 'G') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; + else if (pr == 0) + pr = 1; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, (pr - 1) | 0x80000000)) + fl |= STBSP__NEGATIVE; + + // clamp the precision and delete extra zeros after clamp + n = pr; + if (l > (stbsp__uint32)pr) + l = pr; + while ((l > 1) && (pr) && (sn[l - 1] == '0')) { + --pr; + --l; + } + + // should we use %e + if ((dp <= -4) || (dp > (stbsp__int32)n)) { + if (pr > (stbsp__int32)l) + pr = l - 1; + else if (pr) + --pr; // when using %e, there is one digit before the decimal + goto doexpfromg; + } + // this is the insane action to get the pr to match %g semantics for %f + if (dp > 0) { + pr = (dp < (stbsp__int32)l) ? l - dp : 0; + } else { + pr = -dp + ((pr > (stbsp__int32)l) ? (stbsp__int32) l : pr); + } + goto dofloatfromg; + + case 'E': // float + case 'e': // float + h = (f[0] == 'E') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr | 0x80000000)) + fl |= STBSP__NEGATIVE; + doexpfromg: + tail[0] = 0; + stbsp__lead_sign(fl, lead); + if (dp == STBSP__SPECIAL) { + s = (char *)sn; + cs = 0; + pr = 0; + goto scopy; + } + s = num + 64; + // handle leading chars + *s++ = sn[0]; + + if (pr) + *s++ = stbsp__period; + + // handle after decimal + if ((l - 1) > (stbsp__uint32)pr) + l = pr + 1; + for (n = 1; n < l; n++) + *s++ = sn[n]; + // trailing zeros + tz = pr - (l - 1); + pr = 0; + // dump expo + tail[1] = h[0xe]; + dp -= 1; + if (dp < 0) { + tail[2] = '-'; + dp = -dp; + } else + tail[2] = '+'; +#ifdef STB_SPRINTF_MSVC_MODE + n = 5; +#else + n = (dp >= 100) ? 5 : 4; +#endif + tail[0] = (char)n; + for (;;) { + tail[n] = '0' + dp % 10; + if (n <= 3) + break; + --n; + dp /= 10; + } + cs = 1 + (3 << 24); // how many tens + goto flt_lead; + + case 'f': // float + fv = va_arg(va, double); + doafloat: + // do kilos + if (fl & STBSP__METRIC_SUFFIX) { + double divisor; + divisor = 1000.0f; + if (fl & STBSP__METRIC_1024) + divisor = 1024.0; + while (fl < 0x4000000) { + if ((fv < divisor) && (fv > -divisor)) + break; + fv /= divisor; + fl += 0x1000000; + } + } + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr)) + fl |= STBSP__NEGATIVE; + dofloatfromg: + tail[0] = 0; + stbsp__lead_sign(fl, lead); + if (dp == STBSP__SPECIAL) { + s = (char *)sn; + cs = 0; + pr = 0; + goto scopy; + } + s = num + 64; + + // handle the three decimal varieties + if (dp <= 0) { + stbsp__int32 i; + // handle 0.000*000xxxx + *s++ = '0'; + if (pr) + *s++ = stbsp__period; + n = -dp; + if ((stbsp__int32)n > pr) + n = pr; + i = n; + while (i) { + if ((((stbsp__uintptr)s) & 3) == 0) + break; + *s++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)s = 0x30303030; + s += 4; + i -= 4; + } + while (i) { + *s++ = '0'; + --i; + } + if ((stbsp__int32)(l + n) > pr) + l = pr - n; + i = l; + while (i) { + *s++ = *sn++; + --i; + } + tz = pr - (n + l); + cs = 1 + (3 << 24); // how many tens did we write (for commas below) + } else { + cs = (fl & STBSP__TRIPLET_COMMA) ? ((600 - (stbsp__uint32)dp) % 3) : 0; + if ((stbsp__uint32)dp >= l) { + // handle xxxx000*000.0 + n = 0; + for (;;) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = sn[n]; + ++n; + if (n >= l) + break; + } + } + if (n < (stbsp__uint32)dp) { + n = dp - n; + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + while (n) { + if ((((stbsp__uintptr)s) & 3) == 0) + break; + *s++ = '0'; + --n; + } + while (n >= 4) { + *(stbsp__uint32 *)s = 0x30303030; + s += 4; + n -= 4; + } + } + while (n) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = '0'; + --n; + } + } + } + cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens + if (pr) { + *s++ = stbsp__period; + tz = pr; + } + } else { + // handle xxxxx.xxxx000*000 + n = 0; + for (;;) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = sn[n]; + ++n; + if (n >= (stbsp__uint32)dp) + break; + } + } + cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens + if (pr) + *s++ = stbsp__period; + if ((l - dp) > (stbsp__uint32)pr) + l = pr + dp; + while (n < l) { + *s++ = sn[n]; + ++n; + } + tz = pr - (l - dp); + } + } + pr = 0; + + // handle k,m,g,t + if (fl & STBSP__METRIC_SUFFIX) { + char idx; + idx = 1; + if (fl & STBSP__METRIC_NOSPACE) + idx = 0; + tail[0] = idx; + tail[1] = ' '; + { + if (fl >> 24) { // SI kilo is 'k', JEDEC and SI kibits are 'K'. + if (fl & STBSP__METRIC_1024) + tail[idx + 1] = "_KMGT"[fl >> 24]; + else + tail[idx + 1] = "_kMGT"[fl >> 24]; + idx++; + // If printing kibits and not in jedec, add the 'i'. + if (fl & STBSP__METRIC_1024 && !(fl & STBSP__METRIC_JEDEC)) { + tail[idx + 1] = 'i'; + idx++; + } + tail[0] = idx; + } + } + }; + + flt_lead: + // get the length that we copied + l = (stbsp__uint32)(s - (num + 64)); + s = num + 64; + goto scopy; +#endif + + case 'B': // upper binary + case 'b': // lower binary + h = (f[0] == 'B') ? hexu : hex; + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 2; + lead[1] = '0'; + lead[2] = h[0xb]; + } + l = (8 << 4) | (1 << 8); + goto radixnum; + + case 'o': // octal + h = hexu; + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 1; + lead[1] = '0'; + } + l = (3 << 4) | (3 << 8); + goto radixnum; + + case 'p': // pointer + fl |= (sizeof(void *) == 8) ? STBSP__INTMAX : 0; + pr = sizeof(void *) * 2; + fl &= ~STBSP__LEADINGZERO; // 'p' only prints the pointer with zeros + // fall through - to X + + case 'X': // upper hex + case 'x': // lower hex + h = (f[0] == 'X') ? hexu : hex; + l = (4 << 4) | (4 << 8); + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 2; + lead[1] = '0'; + lead[2] = h[16]; + } + radixnum: + // get the number + if (fl & STBSP__INTMAX) + n64 = va_arg(va, stbsp__uint64); + else + n64 = va_arg(va, stbsp__uint32); + + s = num + STBSP__NUMSZ; + dp = 0; + // clear tail, and clear leading if value is zero + tail[0] = 0; + if (n64 == 0) { + lead[0] = 0; + if (pr == 0) { + l = 0; + cs = 0; + goto scopy; + } + } + // convert to string + for (;;) { + *--s = h[n64 & ((1 << (l >> 8)) - 1)]; + n64 >>= (l >> 8); + if (!((n64) || ((stbsp__int32)((num + STBSP__NUMSZ) - s) < pr))) + break; + if (fl & STBSP__TRIPLET_COMMA) { + ++l; + if ((l & 15) == ((l >> 4) & 15)) { + l &= ~15; + *--s = stbsp__comma; + } + } + }; + // get the tens and the comma pos + cs = (stbsp__uint32)((num + STBSP__NUMSZ) - s) + ((((l >> 4) & 15)) << 24); + // get the length that we copied + l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); + // copy it + goto scopy; + + case 'u': // unsigned + case 'i': + case 'd': // integer + // get the integer and abs it + if (fl & STBSP__INTMAX) { + stbsp__int64 i64 = va_arg(va, stbsp__int64); + n64 = (stbsp__uint64)i64; + if ((f[0] != 'u') && (i64 < 0)) { + n64 = (stbsp__uint64)-i64; + fl |= STBSP__NEGATIVE; + } + } else { + stbsp__int32 i = va_arg(va, stbsp__int32); + n64 = (stbsp__uint32)i; + if ((f[0] != 'u') && (i < 0)) { + n64 = (stbsp__uint32)-i; + fl |= STBSP__NEGATIVE; + } + } + +#ifndef STB_SPRINTF_NOFLOAT + if (fl & STBSP__METRIC_SUFFIX) { + if (n64 < 1024) + pr = 0; + else if (pr == -1) + pr = 1; + fv = (double)(stbsp__int64)n64; + goto doafloat; + } +#endif + + // convert to string + s = num + STBSP__NUMSZ; + l = 0; + + for (;;) { + // do in 32-bit chunks (avoid lots of 64-bit divides even with constant denominators) + char *o = s - 8; + if (n64 >= 100000000) { + n = (stbsp__uint32)(n64 % 100000000); + n64 /= 100000000; + } else { + n = (stbsp__uint32)n64; + n64 = 0; + } + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + do { + s -= 2; + *(stbsp__uint16 *)s = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; + n /= 100; + } while (n); + } + while (n) { + if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { + l = 0; + *--s = stbsp__comma; + --o; + } else { + *--s = (char)(n % 10) + '0'; + n /= 10; + } + } + if (n64 == 0) { + if ((s[0] == '0') && (s != (num + STBSP__NUMSZ))) + ++s; + break; + } + while (s != o) + if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { + l = 0; + *--s = stbsp__comma; + --o; + } else { + *--s = '0'; + } + } + + tail[0] = 0; + stbsp__lead_sign(fl, lead); + + // get the length that we copied + l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); + if (l == 0) { + *--s = '0'; + l = 1; + } + cs = l + (3 << 24); + if (pr < 0) + pr = 0; + + scopy: + // get fw=leading/trailing space, pr=leading zeros + if (pr < (stbsp__int32)l) + pr = l; + n = pr + lead[0] + tail[0] + tz; + if (fw < (stbsp__int32)n) + fw = n; + fw -= n; + pr -= l; + + // handle right justify and leading zeros + if ((fl & STBSP__LEFTJUST) == 0) { + if (fl & STBSP__LEADINGZERO) // if leading zeros, everything is in pr + { + pr = (fw > pr) ? fw : pr; + fw = 0; + } else { + fl &= ~STBSP__TRIPLET_COMMA; // if no leading zeros, then no commas + } + } + + // copy the spaces and/or zeros + if (fw + pr) { + stbsp__int32 i; + stbsp__uint32 c; + + // copy leading spaces (or when doing %8.4d stuff) + if ((fl & STBSP__LEFTJUST) == 0) + while (fw > 0) { + stbsp__cb_buf_clamp(i, fw); + fw -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = ' '; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x20202020; + bf += 4; + i -= 4; + } + while (i) { + *bf++ = ' '; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy leader + sn = lead + 1; + while (lead[0]) { + stbsp__cb_buf_clamp(i, lead[0]); + lead[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy leading zeros + c = cs >> 24; + cs &= 0xffffff; + cs = (fl & STBSP__TRIPLET_COMMA) ? ((stbsp__uint32)(c - ((pr + cs) % (c + 1)))) : 0; + while (pr > 0) { + stbsp__cb_buf_clamp(i, pr); + pr -= i; + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x30303030; + bf += 4; + i -= 4; + } + } + while (i) { + if ((fl & STBSP__TRIPLET_COMMA) && (cs++ == c)) { + cs = 0; + *bf++ = stbsp__comma; + } else + *bf++ = '0'; + --i; + } + stbsp__chk_cb_buf(1); + } + } + + // copy leader if there is still one + sn = lead + 1; + while (lead[0]) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, lead[0]); + lead[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy the string + n = l; + while (n) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, n); + n -= i; + STBSP__UNALIGNED(while (i >= 4) { + *(stbsp__uint32 volatile *)bf = *(stbsp__uint32 volatile *)s; + bf += 4; + s += 4; + i -= 4; + }) + while (i) { + *bf++ = *s++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy trailing zeros + while (tz) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, tz); + tz -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x30303030; + bf += 4; + i -= 4; + } + while (i) { + *bf++ = '0'; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy tail if there is one + sn = tail + 1; + while (tail[0]) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, tail[0]); + tail[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // handle the left justify + if (fl & STBSP__LEFTJUST) + if (fw > 0) { + while (fw) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, fw); + fw -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = ' '; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x20202020; + bf += 4; + i -= 4; + } + while (i--) + *bf++ = ' '; + stbsp__chk_cb_buf(1); + } + } + break; + + default: // unknown, just copy code + s = num + STBSP__NUMSZ - 1; + *s = f[0]; + l = 1; + fw = fl = 0; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + } + ++f; + } +endfmt: + + if (!callback) + *bf = 0; + else + stbsp__flush_cb(); + +done: + return tlen + (int)(bf - buf); +} + +// cleanup +#undef STBSP__LEFTJUST +#undef STBSP__LEADINGPLUS +#undef STBSP__LEADINGSPACE +#undef STBSP__LEADING_0X +#undef STBSP__LEADINGZERO +#undef STBSP__INTMAX +#undef STBSP__TRIPLET_COMMA +#undef STBSP__NEGATIVE +#undef STBSP__METRIC_SUFFIX +#undef STBSP__NUMSZ +#undef stbsp__chk_cb_bufL +#undef stbsp__chk_cb_buf +#undef stbsp__flush_cb +#undef stbsp__cb_buf_clamp + +// ============================================================================ +// wrapper functions + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) +{ + int result; + va_list va; + va_start(va, fmt); + result = STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); + va_end(va); + return result; +} + +typedef struct stbsp__context { + char *buf; + int count; + int length; + char tmp[STB_SPRINTF_MIN]; +} stbsp__context; + +static char *stbsp__clamp_callback(const char *buf, void *user, int len) +{ + stbsp__context *c = (stbsp__context *)user; + c->length += len; + + if (len > c->count) + len = c->count; + + if (len) { + if (buf != c->buf) { + const char *s, *se; + char *d; + d = c->buf; + s = buf; + se = buf + len; + do { + *d++ = *s++; + } while (s < se); + } + c->buf += len; + c->count -= len; + } + + if (c->count <= 0) + return c->tmp; + return (c->count >= STB_SPRINTF_MIN) ? c->buf : c->tmp; // go direct into buffer if you can +} + +static char * stbsp__count_clamp_callback( const char * buf, void * user, int len ) +{ + stbsp__context * c = (stbsp__context*)user; + (void) sizeof(buf); + + c->length += len; + return c->tmp; // go direct into buffer if you can +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE( vsnprintf )( char * buf, int count, char const * fmt, va_list va ) +{ + stbsp__context c; + + if ( (count == 0) && !buf ) + { + c.length = 0; + + STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__count_clamp_callback, &c, c.tmp, fmt, va ); + } + else + { + int l; + + c.buf = buf; + c.count = count; + c.length = 0; + + STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__clamp_callback, &c, stbsp__clamp_callback(0,&c,0), fmt, va ); + + // zero-terminate + l = (int)( c.buf - buf ); + if ( l >= count ) // should never be greater, only equal (or less) than count + l = count - 1; + buf[l] = 0; + } + + return c.length; +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) +{ + int result; + va_list va; + va_start(va, fmt); + + result = STB_SPRINTF_DECORATE(vsnprintf)(buf, count, fmt, va); + va_end(va); + + return result; +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va) +{ + return STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); +} + +// ======================================================================= +// low level float utility functions + +#ifndef STB_SPRINTF_NOFLOAT + +// copies d to bits w/ strict aliasing (this compiles to nothing on /Ox) +#define STBSP__COPYFP(dest, src) \ + { \ + int cn; \ + for (cn = 0; cn < 8; cn++) \ + ((char *)&dest)[cn] = ((char *)&src)[cn]; \ + } + +// get float info +static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value) +{ + double d; + stbsp__int64 b = 0; + + // load value and round at the frac_digits + d = value; + + STBSP__COPYFP(b, d); + + *bits = b & ((((stbsp__uint64)1) << 52) - 1); + *expo = (stbsp__int32)(((b >> 52) & 2047) - 1023); + + return (stbsp__int32)((stbsp__uint64) b >> 63); +} + +static double const stbsp__bot[23] = { + 1e+000, 1e+001, 1e+002, 1e+003, 1e+004, 1e+005, 1e+006, 1e+007, 1e+008, 1e+009, 1e+010, 1e+011, + 1e+012, 1e+013, 1e+014, 1e+015, 1e+016, 1e+017, 1e+018, 1e+019, 1e+020, 1e+021, 1e+022 +}; +static double const stbsp__negbot[22] = { + 1e-001, 1e-002, 1e-003, 1e-004, 1e-005, 1e-006, 1e-007, 1e-008, 1e-009, 1e-010, 1e-011, + 1e-012, 1e-013, 1e-014, 1e-015, 1e-016, 1e-017, 1e-018, 1e-019, 1e-020, 1e-021, 1e-022 +}; +static double const stbsp__negboterr[22] = { + -5.551115123125783e-018, -2.0816681711721684e-019, -2.0816681711721686e-020, -4.7921736023859299e-021, -8.1803053914031305e-022, 4.5251888174113741e-023, + 4.5251888174113739e-024, -2.0922560830128471e-025, -6.2281591457779853e-026, -3.6432197315497743e-027, 6.0503030718060191e-028, 2.0113352370744385e-029, + -3.0373745563400371e-030, 1.1806906454401013e-032, -7.7705399876661076e-032, 2.0902213275965398e-033, -7.1542424054621921e-034, -7.1542424054621926e-035, + 2.4754073164739869e-036, 5.4846728545790429e-037, 9.2462547772103625e-038, -4.8596774326570872e-039 +}; +static double const stbsp__top[13] = { + 1e+023, 1e+046, 1e+069, 1e+092, 1e+115, 1e+138, 1e+161, 1e+184, 1e+207, 1e+230, 1e+253, 1e+276, 1e+299 +}; +static double const stbsp__negtop[13] = { + 1e-023, 1e-046, 1e-069, 1e-092, 1e-115, 1e-138, 1e-161, 1e-184, 1e-207, 1e-230, 1e-253, 1e-276, 1e-299 +}; +static double const stbsp__toperr[13] = { + 8388608, + 6.8601809640529717e+028, + -7.253143638152921e+052, + -4.3377296974619174e+075, + -1.5559416129466825e+098, + -3.2841562489204913e+121, + -3.7745893248228135e+144, + -1.7356668416969134e+167, + -3.8893577551088374e+190, + -9.9566444326005119e+213, + 6.3641293062232429e+236, + -5.2069140800249813e+259, + -5.2504760255204387e+282 +}; +static double const stbsp__negtoperr[13] = { + 3.9565301985100693e-040, -2.299904345391321e-063, 3.6506201437945798e-086, 1.1875228833981544e-109, + -5.0644902316928607e-132, -6.7156837247865426e-155, -2.812077463003139e-178, -5.7778912386589953e-201, + 7.4997100559334532e-224, -4.6439668915134491e-247, -6.3691100762962136e-270, -9.436808465446358e-293, + 8.0970921678014997e-317 +}; + +#if defined(_MSC_VER) && (_MSC_VER <= 1200) +static stbsp__uint64 const stbsp__powten[20] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + 10000000000000000, + 100000000000000000, + 1000000000000000000, + 10000000000000000000U +}; +#define stbsp__tento19th ((stbsp__uint64)1000000000000000000) +#else +static stbsp__uint64 const stbsp__powten[20] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000ULL, + 100000000000ULL, + 1000000000000ULL, + 10000000000000ULL, + 100000000000000ULL, + 1000000000000000ULL, + 10000000000000000ULL, + 100000000000000000ULL, + 1000000000000000000ULL, + 10000000000000000000ULL +}; +#define stbsp__tento19th (1000000000000000000ULL) +#endif + +#define stbsp__ddmulthi(oh, ol, xh, yh) \ + { \ + double ahi = 0, alo, bhi = 0, blo; \ + stbsp__int64 bt; \ + oh = xh * yh; \ + STBSP__COPYFP(bt, xh); \ + bt &= ((~(stbsp__uint64)0) << 27); \ + STBSP__COPYFP(ahi, bt); \ + alo = xh - ahi; \ + STBSP__COPYFP(bt, yh); \ + bt &= ((~(stbsp__uint64)0) << 27); \ + STBSP__COPYFP(bhi, bt); \ + blo = yh - bhi; \ + ol = ((ahi * bhi - oh) + ahi * blo + alo * bhi) + alo * blo; \ + } + +#define stbsp__ddtoS64(ob, xh, xl) \ + { \ + double ahi = 0, alo, vh, t; \ + ob = (stbsp__int64)xh; \ + vh = (double)ob; \ + ahi = (xh - vh); \ + t = (ahi - xh); \ + alo = (xh - (ahi - t)) - (vh + t); \ + ob += (stbsp__int64)(ahi + alo + xl); \ + } + +#define stbsp__ddrenorm(oh, ol) \ + { \ + double s; \ + s = oh + ol; \ + ol = ol - (s - oh); \ + oh = s; \ + } + +#define stbsp__ddmultlo(oh, ol, xh, xl, yh, yl) ol = ol + (xh * yl + xl * yh); + +#define stbsp__ddmultlos(oh, ol, xh, yl) ol = ol + (xh * yl); + +static void stbsp__raise_to_power10(double *ohi, double *olo, double d, stbsp__int32 power) // power can be -323 to +350 +{ + double ph, pl; + if ((power >= 0) && (power <= 22)) { + stbsp__ddmulthi(ph, pl, d, stbsp__bot[power]); + } else { + stbsp__int32 e, et, eb; + double p2h, p2l; + + e = power; + if (power < 0) + e = -e; + et = (e * 0x2c9) >> 14; /* %23 */ + if (et > 13) + et = 13; + eb = e - (et * 23); + + ph = d; + pl = 0.0; + if (power < 0) { + if (eb) { + --eb; + stbsp__ddmulthi(ph, pl, d, stbsp__negbot[eb]); + stbsp__ddmultlos(ph, pl, d, stbsp__negboterr[eb]); + } + if (et) { + stbsp__ddrenorm(ph, pl); + --et; + stbsp__ddmulthi(p2h, p2l, ph, stbsp__negtop[et]); + stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__negtop[et], stbsp__negtoperr[et]); + ph = p2h; + pl = p2l; + } + } else { + if (eb) { + e = eb; + if (eb > 22) + eb = 22; + e -= eb; + stbsp__ddmulthi(ph, pl, d, stbsp__bot[eb]); + if (e) { + stbsp__ddrenorm(ph, pl); + stbsp__ddmulthi(p2h, p2l, ph, stbsp__bot[e]); + stbsp__ddmultlos(p2h, p2l, stbsp__bot[e], pl); + ph = p2h; + pl = p2l; + } + } + if (et) { + stbsp__ddrenorm(ph, pl); + --et; + stbsp__ddmulthi(p2h, p2l, ph, stbsp__top[et]); + stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__top[et], stbsp__toperr[et]); + ph = p2h; + pl = p2l; + } + } + } + stbsp__ddrenorm(ph, pl); + *ohi = ph; + *olo = pl; +} + +// given a float value, returns the significant bits in bits, and the position of the +// decimal point in decimal_pos. +/-INF and NAN are specified by special values +// returned in the decimal_pos parameter. +// frac_digits is absolute normally, but if you want from first significant digits (got %g and %e), or in 0x80000000 +static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits) +{ + double d; + stbsp__int64 bits = 0; + stbsp__int32 expo, e, ng, tens; + + d = value; + STBSP__COPYFP(bits, d); + expo = (stbsp__int32)((bits >> 52) & 2047); + ng = (stbsp__int32)((stbsp__uint64) bits >> 63); + if (ng) + d = -d; + + if (expo == 2047) // is nan or inf? + { + *start = (bits & ((((stbsp__uint64)1) << 52) - 1)) ? "NaN" : "Inf"; + *decimal_pos = STBSP__SPECIAL; + *len = 3; + return ng; + } + + if (expo == 0) // is zero or denormal + { + if (((stbsp__uint64) bits << 1) == 0) // do zero + { + *decimal_pos = 1; + *start = out; + out[0] = '0'; + *len = 1; + return ng; + } + // find the right expo for denormals + { + stbsp__int64 v = ((stbsp__uint64)1) << 51; + while ((bits & v) == 0) { + --expo; + v >>= 1; + } + } + } + + // find the decimal exponent as well as the decimal bits of the value + { + double ph, pl; + + // log10 estimate - very specifically tweaked to hit or undershoot by no more than 1 of log10 of all expos 1..2046 + tens = expo - 1023; + tens = (tens < 0) ? ((tens * 617) / 2048) : (((tens * 1233) / 4096) + 1); + + // move the significant bits into position and stick them into an int + stbsp__raise_to_power10(&ph, &pl, d, 18 - tens); + + // get full as much precision from double-double as possible + stbsp__ddtoS64(bits, ph, pl); + + // check if we undershot + if (((stbsp__uint64)bits) >= stbsp__tento19th) + ++tens; + } + + // now do the rounding in integer land + frac_digits = (frac_digits & 0x80000000) ? ((frac_digits & 0x7ffffff) + 1) : (tens + frac_digits); + if ((frac_digits < 24)) { + stbsp__uint32 dg = 1; + if ((stbsp__uint64)bits >= stbsp__powten[9]) + dg = 10; + while ((stbsp__uint64)bits >= stbsp__powten[dg]) { + ++dg; + if (dg == 20) + goto noround; + } + if (frac_digits < dg) { + stbsp__uint64 r; + // add 0.5 at the right position and round + e = dg - frac_digits; + if ((stbsp__uint32)e >= 24) + goto noround; + r = stbsp__powten[e]; + bits = bits + (r / 2); + if ((stbsp__uint64)bits >= stbsp__powten[dg]) + ++tens; + bits /= r; + } + noround:; + } + + // kill long trailing runs of zeros + if (bits) { + stbsp__uint32 n; + for (;;) { + if (bits <= 0xffffffff) + break; + if (bits % 1000) + goto donez; + bits /= 1000; + } + n = (stbsp__uint32)bits; + while ((n % 1000) == 0) + n /= 1000; + bits = n; + donez:; + } + + // convert to string + out += 64; + e = 0; + for (;;) { + stbsp__uint32 n; + char *o = out - 8; + // do the conversion in chunks of U32s (avoid most 64-bit divides, worth it, constant denomiators be damned) + if (bits >= 100000000) { + n = (stbsp__uint32)(bits % 100000000); + bits /= 100000000; + } else { + n = (stbsp__uint32)bits; + bits = 0; + } + while (n) { + out -= 2; + *(stbsp__uint16 *)out = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; + n /= 100; + e += 2; + } + if (bits == 0) { + if ((e) && (out[0] == '0')) { + ++out; + --e; + } + break; + } + while (out != o) { + *--out = '0'; + ++e; + } + } + + *decimal_pos = tens; + *start = out; + *len = e; + return ng; +} + +#undef stbsp__ddmulthi +#undef stbsp__ddrenorm +#undef stbsp__ddmultlo +#undef stbsp__ddmultlos +#undef STBSP__SPECIAL +#undef STBSP__COPYFP + +#endif // STB_SPRINTF_NOFLOAT + +// clean up +#undef stbsp__uint16 +#undef stbsp__uint32 +#undef stbsp__int32 +#undef stbsp__uint64 +#undef stbsp__int64 +#undef STBSP__UNALIGNED + +#endif // STB_SPRINTF_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/colla/str.c b/colla/str.c index 570a1d9..c781aa9 100644 --- a/colla/str.c +++ b/colla/str.c @@ -1,291 +1,259 @@ #include "str.h" -#include -#include -#include -#include -#include -#include +#include "warnings/colla_warn_beg.h" +#include "arena.h" +#include "format.h" #include "tracelog.h" -#include "strstream.h" -#ifdef _WIN32 -#include "win32_slim.h" -#else -#include -#endif - -#ifndef min -#define min(a, b) ((a) < (b) ? (a) : (b)) +#if COLLA_WIN +#include #endif // == STR_T ======================================================== -str_t strInit(void) { - return (str_t) { - .buf = NULL, - .len = 0 +str_t strInit(arena_t *arena, const char *buf) { + return buf ? strInitLen(arena, buf, strlen(buf)) : (str_t){0}; +} + +str_t strInitLen(arena_t *arena, const char *buf, usize len) { + if (!buf || !len) return (str_t){0}; + + str_t out = { + .buf = alloc(arena, char, len + 1), + .len = len }; -} -str_t strFromStr(const char *cstr) { - return cstr ? strFromBuf(cstr, strlen(cstr)) : strInit(); -} + memcpy(out.buf, buf, len); -str_t strFromView(strview_t view) { - return strFromBuf(view.buf, view.len); -} - -str_t strFromBuf(const char *buf, usize len) { - if (!buf) return strInit(); - str_t str; - str.len = len; - str.buf = (char *)malloc(len + 1); - memcpy(str.buf, buf, len); - str.buf[len] = '\0'; - return str; -} - -str_t strFromFmt(const char *fmt, ...) { - str_ostream_t out = ostrInit(); - va_list va; - va_start(va, fmt); - ostrPrintfV(&out, fmt, va); - va_end(va); - return ostrAsStr(out); -} - -void strFree(str_t ctx) { - free(ctx.buf); -} - -str_t strFromWCHAR(const wchar_t *src, usize 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 = (char *)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 - usize actual_len = len * sizeof(wchar_t); - - usize dest_len = len * 6; - char *dest = (char *)malloc(dest_len); - - iconv_t cd = iconv_open("UTF-8", "WCHAR_T"); - assert(cd); - - usize dest_left = dest_len; - char *dest_temp = dest; - char *src_temp = (char*)src; - usize lost = iconv(cd, &src_temp, &actual_len, &dest_temp, &dest_left); - assert(lost != ((usize)-1)); - (void)lost; - - dest_len -= dest_left; - dest = (char *)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 = (wchar_t *)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 - usize dest_len = ctx.len * sizeof(wchar_t); - char *dest = (char *)malloc(dest_len); - - iconv_t cd = iconv_open("WCHAR_T", "UTF-8"); - assert(cd); - - usize dest_left = dest_len; - char *dest_temp = dest; - char *src_temp = ctx.buf; - usize lost = iconv(cd, &src_temp, &ctx.len, &dest_temp, &dest_left); - assert(lost != ((usize)-1)); - (void)lost; - - dest_len -= dest_left; - dest = (char *)realloc(dest, dest_len + 1); - dest[dest_len] = '\0'; - - iconv_close(cd); - - return (wchar_t *)dest; -#endif -} - -str_t strDup(str_t ctx) { - return strFromBuf(ctx.buf, ctx.len); -} - -str_t strMove(str_t *ctx) { - str_t out = *ctx; - ctx->buf = NULL; - ctx->len = 0; return out; } -strview_t strGetView(str_t ctx) { - return (strview_t) { - .buf = ctx.buf, - .len = ctx.len - }; +str_t strInitView(arena_t *arena, strview_t view) { + return strInitLen(arena, view.buf, view.len); } -char *strBegin(str_t ctx) { - return ctx.buf; +str_t strFmt(arena_t *arena, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + str_t out = strFmtv(arena, fmt, args); + va_end(args); + return out; } -char *strEnd(str_t ctx) { - return ctx.buf ? ctx.buf + ctx.len : NULL; +str_t strFmtv(arena_t *arena, const char *fmt, va_list args) { + va_list vcopy; + va_copy(vcopy, args); + int len = fmtBufferv(NULL, 0, fmt, vcopy); + va_end(vcopy); + + char *buffer = alloc(arena, char, len + 1); + fmtBufferv(buffer, len + 1, fmt, args); + + return (str_t){ .buf = buffer, .len = (usize)len }; } -char strBack(str_t ctx) { - return ctx.buf ? ctx.buf[ctx.len - 1] : '\0'; +str_t strFromWChar(arena_t *arena, const wchar_t *src, usize srclen) { + if (!src) return (str_t){0}; + if (!srclen) srclen = wcslen(src); + + str_t out = {0}; + +#if COLLA_WIN + int outlen = WideCharToMultiByte( + CP_UTF8, 0, + src, (int)srclen, + NULL, 0, + NULL, NULL + ); + + if (outlen == 0) { + unsigned long error = GetLastError(); + if (error == ERROR_NO_UNICODE_TRANSLATION) { + err("couldn't translate wide string (%S) to utf8, no unicode translation", src); + } + else { + err("couldn't translate wide string (%S) to utf8, %u", error); + } + + return (str_t){0}; + } + + out.buf = alloc(arena, char, outlen + 1); + WideCharToMultiByte( + CP_UTF8, 0, + src, (int)srclen, + out.buf, outlen, + NULL, NULL + ); + +#elif COLLA_LIN + fatal("strFromWChar not implemented yet!"); +#endif + + return out; +} + +bool strEquals(str_t a, str_t b) { + return strCompare(a, b) == 0; +} + +int strCompare(str_t a, str_t b) { + return a.len == b.len ? + memcmp(a.buf, b.buf, a.len) : + a.len - (int)b.len; +} + +str_t strDup(arena_t *arena, str_t src) { + return strInitLen(arena, src.buf, src.len); } bool strIsEmpty(str_t ctx) { - return ctx.len == 0; -} - -void strSwap(str_t *ctx, str_t *other) { - char *buf = other->buf; - usize len = other->len; - other->buf = ctx->buf; - other->len = ctx->len; - ctx->buf = buf; - ctx->len = len; + return ctx.len == 0 || ctx.buf == NULL; } void strReplace(str_t *ctx, char from, char to) { - for(usize i = 0; i < ctx->len; ++i) { - if(ctx->buf[i] == from) { - ctx->buf[i] = to; - } + if (!ctx) return; + char *buf = ctx->buf; + for (usize i = 0; i < ctx->len; ++i) { + buf[i] = buf[i] == from ? to : buf[i]; } } -str_t strSubstr(str_t ctx, usize from, usize to) { - if(strIsEmpty(ctx)) return strInit(); +strview_t strSub(str_t ctx, usize from, usize to) { if (to > ctx.len) to = ctx.len; - if (from > to) from = to; - return strFromBuf(ctx.buf + from, to - from); -} - -strview_t strSubview(str_t ctx, usize from, usize to) { - if(strIsEmpty(ctx)) return strvInit(NULL); - if (to > ctx.len) to = ctx.len; - if (from > to) from = to; - return strvInitLen(ctx.buf + from, to - from); + if (from > to) from = to; + return (strview_t){ ctx.buf + from, to - from }; } void strLower(str_t *ctx) { - for(usize i = 0; i < ctx->len; ++i) { - ctx->buf[i] = (char)tolower(ctx->buf[i]); + char *buf = ctx->buf; + for (usize i = 0; i < ctx->len; ++i) { + buf[i] = (buf[i] >= 'A' && buf[i] <= 'Z') ? + buf[i] += 'a' - 'A' : + buf[i]; } } -str_t strToLower(str_t ctx) { - str_t str = strDup(ctx); - strLower(&str); - return str; -} - void strUpper(str_t *ctx) { - for(usize i = 0; i < ctx->len; ++i) { - ctx->buf[i] = (char)toupper(ctx->buf[i]); + char *buf = ctx->buf; + for (usize i = 0; i < ctx->len; ++i) { + buf[i] = (buf[i] >= 'a' && buf[i] <= 'z') ? + buf[i] -= 'a' - 'A' : + buf[i]; } } -str_t strToUpper(str_t ctx) { - str_t str = strDup(ctx); - strUpper(&str); - return str; +str_t strToLower(arena_t *arena, str_t ctx) { + strLower(&ctx); + return strDup(arena, ctx); } +str_t strToUpper(arena_t *arena, str_t ctx) { + strUpper(&ctx); + return strDup(arena, ctx); +} + + // == 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, usize size) { - return (strview_t) { - .buf = buf, - .len = size + return (strview_t){ + .buf = cstr, + .len = cstr ? strlen(cstr) : 0, }; } -char strvFront(strview_t ctx) { - return ctx.buf[0]; +strview_t strvInitLen(const char *buf, usize size) { + return (strview_t){ + .buf = buf, + .len = size, + }; } -char strvBack(strview_t ctx) { - return ctx.buf[ctx.len - 1]; +strview_t strvInitStr(str_t str) { + return (strview_t){ + .buf = str.buf, + .len = str.len + }; } -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; + return ctx.len == 0 || !ctx.buf; +} + +bool strvEquals(strview_t a, strview_t b) { + return strvCompare(a, b) == 0; +} + +int strvCompare(strview_t a, strview_t b) { + return a.len == b.len ? + memcmp(a.buf, b.buf, a.len) : + a.len - (int)b.len; +} + +wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen) { + wchar_t *out = NULL; + int len = 0; + + if (strvIsEmpty(ctx)) { + goto error; + } + +#if COLLA_WIN + len = MultiByteToWideChar( + CP_UTF8, 0, + ctx.buf, (int)ctx.len, + NULL, 0 + ); + + if (len == 0) { + unsigned long error = GetLastError(); + if (error == ERROR_NO_UNICODE_TRANSLATION) { + err("couldn't translate string (%v) to a wide string, no unicode translation", ctx); + } + else { + err("couldn't translate string (%v) to a wide string, %u", ctx, error); + } + + goto error; + } + + out = alloc(arena, wchar_t, len + 1); + + MultiByteToWideChar( + CP_UTF8, 0, + ctx.buf, (int)ctx.len, + out, len + ); + +#elif COLLA_LIN + fatal("strFromWChar not implemented yet!"); +#endif + +error: + if (outlen) { + *outlen = (usize)len; + } + return out; +} + +TCHAR *strvToTChar(arena_t *arena, strview_t str) { +#if UNICODE + return strvToWChar(arena, str, NULL); +#else + char *cstr = alloc(arena, char, str.len + 1); + memcpy(cstr, str.buf, str.len); + return cstr; +#endif } strview_t strvRemovePrefix(strview_t ctx, usize n) { if (n > ctx.len) n = ctx.len; return (strview_t){ .buf = ctx.buf + n, - .len = ctx.len - n + .len = ctx.len - n, }; } @@ -293,9 +261,9 @@ strview_t strvRemoveSuffix(strview_t ctx, usize n) { if (n > ctx.len) n = ctx.len; return (strview_t){ .buf = ctx.buf, - .len = ctx.len - n + .len = ctx.len - n, }; -} +} strview_t strvTrim(strview_t ctx) { return strvTrimLeft(strvTrimRight(ctx)); @@ -303,334 +271,115 @@ strview_t strvTrim(strview_t ctx) { strview_t strvTrimLeft(strview_t ctx) { strview_t out = ctx; - for (usize i = 0; i < ctx.len && isspace(ctx.buf[i]); ++i) { - ++out.buf; - --out.len; + for (usize i = 0; i < ctx.len; ++i) { + char c = ctx.buf[i]; + if (c != ' ' || c < '\t' || c > '\r') { + break; + } + out.buf++; + out.len--; } return out; } strview_t strvTrimRight(strview_t ctx) { strview_t out = ctx; - for (isize i = ctx.len - 1; i >= 0 && isspace(ctx.buf[i]); --i) { - --out.len; + for (isize i = ctx.len - 1; i >= 0; --i) { + char c = ctx.buf[i]; + if (c != ' ' || c < '\t' || c > '\r') { + break; + } + out.len--; } return out; } -str_t strvCopy(strview_t ctx) { - return strFromView(ctx); -} - -str_t strvCopyN(strview_t ctx, usize count, usize from) { - usize sz = ctx.len + 1 - from; - count = min(count, sz); - return strFromBuf(ctx.buf + from, count); -} - -usize strvCopyBuf(strview_t ctx, char *buf, usize len, usize from) { - usize sz = ctx.len + 1 - from; - len = min(len, sz); - memcpy(buf, ctx.buf + from, len); - buf[len - 1] = '\0'; - return len - 1; -} - strview_t strvSub(strview_t ctx, usize from, usize to) { if (to > ctx.len) to = ctx.len; if (from > to) from = to; - return strvInitLen(ctx.buf + from, to - from); -} - -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(usize 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; + return (strview_t){ ctx.buf + from, to - from }; } bool strvStartsWith(strview_t ctx, char c) { - return strvFront(ctx) == c; + return ctx.len > 0 && ctx.buf[0] == 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; + return ctx.len >= view.len && memcmp(ctx.buf, view.buf, view.len) == 0; } bool strvEndsWith(strview_t ctx, char c) { - return strvBack(ctx) == c; + return ctx.len > 0 && ctx.buf[ctx.len - 1] == 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; + return ctx.len >= view.len && memcmp(ctx.buf + ctx.len, view.buf, view.len) == 0; } bool strvContains(strview_t ctx, char c) { for(usize i = 0; i < ctx.len; ++i) { - if(ctx.buf[i] == c) return true; + if(ctx.buf[i] == c) { + return true; + } } return false; } bool strvContainsView(strview_t ctx, strview_t view) { - if(ctx.len < view.len) return false; + if (ctx.len < view.len) return false; usize end = ctx.len - view.len; - for(usize i = 0; i < end; ++i) { - if(memcmp(ctx.buf + i, view.buf, view.len) == 0) return true; + for (usize i = 0; i < end; ++i) { + if (memcmp(ctx.buf + i, view.buf, view.len) == 0) { + return true; + } } return false; } usize strvFind(strview_t ctx, char c, usize from) { - for(usize i = from; i < ctx.len; ++i) { - if(ctx.buf[i] == c) return i; - } - return SIZE_MAX; -} - -usize strvFindView(strview_t ctx, strview_t view, usize from) { - if(ctx.len < view.len) return SIZE_MAX; - usize end = ctx.len - view.len; - for(usize i = from; i < end; ++i) { - if(memcmp(ctx.buf + i, view.buf, view.len) == 0) return i; - } - return SIZE_MAX; -} - -usize strvRFind(strview_t ctx, char c, usize from) { - if(from >= ctx.len) { - from = ctx.len; - } - - from = ctx.len - from; - - const char *buf = ctx.buf + from; - for(; buf >= ctx.buf; --buf) { - if(*buf == c) return (buf - ctx.buf); - } - - return SIZE_MAX; -} - -usize strvRFindView(strview_t ctx, strview_t view, usize from) { - if(view.len > ctx.len) { - return SIZE_MAX; - } - - if(from > ctx.len) { - from = ctx.len; - } - - from = ctx.len - from; - 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; -} - -usize strvFindFirstOf(strview_t ctx, strview_t view, usize from) { - if(ctx.len < view.len) return SIZE_MAX; - for(usize i = from; i < ctx.len; ++i) { - for(usize j = 0; j < view.len; ++j) { - if(ctx.buf[i] == view.buf[j]) return i; - } - } - return SIZE_MAX; -} - -usize strvFindLastOf(strview_t ctx, strview_t view, usize from) { - if(from >= ctx.len) { - from = ctx.len - 1; - } - - const char *buf = ctx.buf + from; - for(; buf >= ctx.buf; --buf) { - for(usize j = 0; j < view.len; ++j) { - if(*buf == view.buf[j]) return (buf - ctx.buf); - } - } - - return SIZE_MAX; -} - -usize strvFindFirstNot(strview_t ctx, char c, usize from) { - usize end = ctx.len - 1; - for(usize i = from; i < end; ++i) { - if(ctx.buf[i] != c) return i; - } - return SIZE_MAX; -} - -usize strvFindFirstNotOf(strview_t ctx, strview_t view, usize from) { - for(usize i = from; i < ctx.len; ++i) { - if(!strvContains(view, ctx.buf[i])) { + for (usize i = from; i < ctx.len; ++i) { + if (ctx.buf[i] == c) { return i; } } - return SIZE_MAX; + return STR_NONE; } -usize strvFindLastNot(strview_t ctx, char c, usize 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; +usize strvFindView(strview_t ctx, strview_t view, usize from) { + if (ctx.len < view.len) return STR_NONE; + usize end = ctx.len - view.len; + for (usize i = 0; i < end; ++i) { + if (memcmp(ctx.buf + i, view.buf, view.len) == 0) { + return i; } } - - return SIZE_MAX; + return STR_NONE; } -usize strvFindLastNotOf(strview_t ctx, strview_t view, usize 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; +usize strvRFind(strview_t ctx, char c, usize from_right) { + if (from_right > ctx.len) from_right = ctx.len; + isize end = (isize)(ctx.len - from_right); + for (isize i = end; i >= 0; --i) { + if (ctx.buf[i] == c) { + return (usize)i; } } - - return SIZE_MAX; + return STR_NONE; } -#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 buf[] = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' }; - s = strFromBuf((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); +usize strvRFindView(strview_t ctx, strview_t view, usize from_right) { + if (from_right > ctx.len) from_right = ctx.len; + isize end = (isize)(ctx.len - from_right); + if (end < view.len) return STR_NONE; + for (isize i = end - view.len; i >= 0; --i) { + if (memcmp(ctx.buf + i, view.buf, view.len) == 0) { + return (usize)i; } - 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 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); + return STR_NONE; } -#endif + +#include "warnings/colla_warn_beg.h" + +#undef CP_UTF8 +#undef ERROR_NO_UNICODE_TRANSLATION \ No newline at end of file diff --git a/colla/str.h b/colla/str.h index c2e1a0a..5a65720 100644 --- a/colla/str.h +++ b/colla/str.h @@ -1,19 +1,15 @@ #pragma once -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include -#include +#include // va_list +#include // strlen #include "collatypes.h" -#define STRV_NOT_FOUND SIZE_MAX +typedef struct arena_t arena_t; -typedef struct str_t { +#define STR_NONE SIZE_MAX + +typedef struct { char *buf; usize len; } str_t; @@ -25,73 +21,77 @@ typedef struct { // == STR_T ======================================================== -str_t strInit(void); -str_t strFromStr(const char *cstr); -str_t strFromView(strview_t view); -str_t strFromBuf(const char *buf, usize len); -str_t strFromFmt(const char *fmt, ...); +#define str__1(arena, x) \ + _Generic((x), \ + const char *: strInit, \ + char *: strInit, \ + strview_t: strInitView \ + )(arena, x) -str_t strFromWCHAR(const wchar_t *src, usize len); -wchar_t *strToWCHAR(str_t ctx); +#define str__2(arena, cstr, clen) strInitLen(arena, cstr, clen) +#define str__impl(_1, _2, n, ...) str__##n -void strFree(str_t ctx); -str_t strDup(str_t ctx); -str_t strMove(str_t *ctx); +// either: +// arena_t arena, [const] char *cstr, [usize len] +// arena_t arena, strview_t view +#define str(arena, ...) str__impl(__VA_ARGS__, 2, 1, 0)(arena, __VA_ARGS__) -strview_t strGetView(str_t ctx); +str_t strInit(arena_t *arena, const char *buf); +str_t strInitLen(arena_t *arena, const char *buf, usize len); +str_t strInitView(arena_t *arena, strview_t view); +str_t strFmt(arena_t *arena, const char *fmt, ...); +str_t strFmtv(arena_t *arena, const char *fmt, va_list args); -char *strBegin(str_t ctx); -char *strEnd(str_t ctx); -char strBack(str_t ctx); +str_t strFromWChar(arena_t *arena, const wchar_t *src, usize srclen); +bool strEquals(str_t a, str_t b); +int strCompare(str_t a, str_t b); + +str_t strDup(arena_t *arena, str_t src); bool strIsEmpty(str_t ctx); -void strReplace(str_t *ctx, char from, char to); +void strReplace(str_t *ctx, char from, char to); // if len == SIZE_MAX, copies until end -str_t strSubstr(str_t ctx, usize from, usize to); -// if len == SIZE_MAX, returns until end -strview_t strSubview(str_t ctx, usize from, usize to); +strview_t strSub(str_t ctx, usize from, usize to); void strLower(str_t *ctx); -str_t strToLower(str_t ctx); - void strUpper(str_t *ctx); -str_t strToUpper(str_t ctx); -#ifdef STR_TESTING -void strTest(void); -#endif +str_t strToLower(arena_t *arena, str_t ctx); +str_t strToUpper(arena_t *arena, str_t ctx); // == STRVIEW_T ==================================================== -strview_t strvInit(const char *cstr); -strview_t strvInitStr(str_t str); -strview_t strvInitLen(const char *buf, usize size); +#define strv__1(x) \ + _Generic((x), \ + char *: strvInit, \ + const char *: strvInit, \ + str_t: strvInitStr \ + )(x) -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 -strview_t strvRemovePrefix(strview_t ctx, usize n); -// move view backwards by n characters -strview_t strvRemoveSuffix(strview_t ctx, usize n); -// removes whitespace from the beginning and the end -strview_t strvTrim(strview_t ctx); -// removes whitespace from the beginning -strview_t strvTrimLeft(strview_t ctx); -// removes whitespace from the end -strview_t strvTrimRight(strview_t ctx); +#define strv__2(cstr, clen) strvInitLen(cstr, clen) +#define strv__impl(_1, _2, n, ...) strv__##n + +#define strv(...) strv__impl(__VA_ARGS__, 2, 1, 0)(__VA_ARGS__) + +strview_t strvInit(const char *cstr); +strview_t strvInitLen(const char *buf, usize size); +strview_t strvInitStr(str_t str); bool strvIsEmpty(strview_t ctx); +bool strvEquals(strview_t a, strview_t b); +int strvCompare(strview_t a, strview_t b); -str_t strvCopy(strview_t ctx); -str_t strvCopyN(strview_t ctx, usize count, usize from); -usize strvCopyBuf(strview_t ctx, char *buf, usize len, usize from); +wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen); +TCHAR *strvToTChar(arena_t *arena, strview_t str); + +strview_t strvRemovePrefix(strview_t ctx, usize n); +strview_t strvRemoveSuffix(strview_t ctx, usize n); +strview_t strvTrim(strview_t ctx); +strview_t strvTrimLeft(strview_t ctx); +strview_t strvTrimRight(strview_t ctx); strview_t strvSub(strview_t ctx, usize from, usize to); -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); @@ -105,18 +105,5 @@ bool strvContainsView(strview_t ctx, strview_t view); usize strvFind(strview_t ctx, char c, usize from); usize strvFindView(strview_t ctx, strview_t view, usize from); -usize strvRFind(strview_t ctx, char c, usize from); -usize strvRFindView(strview_t ctx, strview_t view, usize from); - -// Finds the first occurrence of any of the characters of 'view' in this view -usize strvFindFirstOf(strview_t ctx, strview_t view, usize from); -usize strvFindLastOf(strview_t ctx, strview_t view, usize from); - -usize strvFindFirstNot(strview_t ctx, char c, usize from); -usize strvFindFirstNotOf(strview_t ctx, strview_t view, usize from); -usize strvFindLastNot(strview_t ctx, char c, usize from); -usize strvFindLastNotOf(strview_t ctx, strview_t view, usize from); - -#ifdef __cplusplus -} // extern "C" -#endif +usize strvRFind(strview_t ctx, char c, usize from_right); +usize strvRFindView(strview_t ctx, strview_t view, usize from_right); diff --git a/colla/strstream.c b/colla/strstream.c index dec6f0f..b2fcd19 100644 --- a/colla/strstream.c +++ b/colla/strstream.c @@ -1,5 +1,7 @@ #include "strstream.h" +#include "warnings/colla_warn_beg.h" + #include #include #include @@ -7,9 +9,11 @@ #include #include #include // HUGE_VALF -#include "tracelog.h" -#if defined(_WIN32) && defined(__TINYC__) +#include "tracelog.h" +#include "arena.h" + +#if COLLA_WIN && COLLA_TCC #define strtoull _strtoui64 #define strtoll _strtoi64 #define strtof strtod @@ -17,39 +21,48 @@ /* == INPUT STREAM ============================================ */ -str_istream_t istrInit(const char *str) { +instream_t istrInit(const char *str) { return istrInitLen(str, strlen(str)); } -str_istream_t istrInitLen(const char *str, usize len) { - str_istream_t res; +instream_t istrInitLen(const char *str, usize len) { + instream_t res; res.start = res.cur = str; res.size = len; return res; } -char istrGet(str_istream_t *ctx) { +char istrGet(instream_t *ctx) { return *ctx->cur++; } -void istrIgnore(str_istream_t *ctx, char delim) { - usize position = ctx->cur - ctx->start; - usize i; - for(i = position; - i < ctx->size && *ctx->cur != delim; - ++i, ++ctx->cur); +void istrIgnore(instream_t *ctx, char delim) { + for (; !istrIsFinished(*ctx) && *ctx->cur != delim; ++ctx->cur) { + + } + + //usize position = ctx->cur - ctx->start; + //usize i; + //for(i = position; + // i < ctx->size && *ctx->cur != delim; + // ++i, ++ctx->cur); } -void istrIgnoreAndSkip(str_istream_t *ctx, char delim) { +void istrIgnoreAndSkip(instream_t *ctx, char delim) { istrIgnore(ctx, delim); istrSkip(ctx, 1); } -char istrPeek(str_istream_t *ctx) { +char istrPeek(instream_t *ctx) { return *ctx->cur; } -void istrSkip(str_istream_t *ctx, usize n) { +char istrPeekNext(instream_t *ctx) { + usize offset = (ctx->cur - ctx->start) + 1; + return offset > ctx->size ? '\0' : *(ctx->cur + 1); +} + +void istrSkip(instream_t *ctx, usize n) { usize remaining = ctx->size - (ctx->cur - ctx->start); if(n > remaining) { warn("skipping more then remaining: %zu -> %zu", n, remaining); @@ -58,13 +71,13 @@ void istrSkip(str_istream_t *ctx, usize n) { ctx->cur += n; } -void istrSkipWhitespace(str_istream_t *ctx) { +void istrSkipWhitespace(instream_t *ctx) { while (*ctx->cur && isspace(*ctx->cur)) { ++ctx->cur; } } -void istrRead(str_istream_t *ctx, char *buf, usize len) { +void istrRead(instream_t *ctx, char *buf, usize len) { usize remaining = ctx->size - (ctx->cur - ctx->start); if(len > remaining) { warn("istrRead: trying to read len %zu from remaining %zu", len, remaining); @@ -74,7 +87,7 @@ void istrRead(str_istream_t *ctx, char *buf, usize len) { ctx->cur += len; } -usize istrReadMax(str_istream_t *ctx, char *buf, usize len) { +usize istrReadMax(instream_t *ctx, char *buf, usize len) { usize remaining = ctx->size - (ctx->cur - ctx->start); len = remaining < len ? remaining : len; memcpy(buf, ctx->cur, len); @@ -82,29 +95,29 @@ usize istrReadMax(str_istream_t *ctx, char *buf, usize len) { return len; } -void istrRewind(str_istream_t *ctx) { +void istrRewind(instream_t *ctx) { ctx->cur = ctx->start; } -void istrRewindN(str_istream_t *ctx, usize amount) { +void istrRewindN(instream_t *ctx, usize amount) { usize remaining = ctx->size - (ctx->cur - ctx->start); if (amount > remaining) amount = remaining; ctx->cur -= amount; } -usize istrTell(str_istream_t ctx) { +usize istrTell(instream_t ctx) { return ctx.cur - ctx.start; } -usize istrRemaining(str_istream_t ctx) { +usize istrRemaining(instream_t ctx) { return ctx.size - (ctx.cur - ctx.start); } -bool istrIsFinished(str_istream_t ctx) { +bool istrIsFinished(instream_t ctx) { return (usize)(ctx.cur - ctx.start) >= ctx.size; } -bool istrGetbool(str_istream_t *ctx, bool *val) { +bool istrGetBool(instream_t *ctx, bool *val) { usize remaining = ctx->size - (ctx->cur - ctx->start); if(strncmp(ctx->cur, "true", remaining) == 0) { *val = true; @@ -117,16 +130,16 @@ bool istrGetbool(str_istream_t *ctx, bool *val) { return false; } -bool istrGetu8(str_istream_t *ctx, uint8 *val) { +bool istrGetU8(instream_t *ctx, uint8 *val) { char *end = NULL; *val = (uint8) strtoul(ctx->cur, &end, 0); if(ctx->cur == end) { - warn("istrGetu8: no valid conversion could be performed"); + 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"); + warn("istrGetU8: value read is out of the range of representable values"); return false; } @@ -134,16 +147,16 @@ bool istrGetu8(str_istream_t *ctx, uint8 *val) { return true; } -bool istrGetu16(str_istream_t *ctx, uint16 *val) { +bool istrGetU16(instream_t *ctx, uint16 *val) { char *end = NULL; *val = (uint16) strtoul(ctx->cur, &end, 0); if(ctx->cur == end) { - warn("istrGetu16: no valid conversion could be performed"); + 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"); + warn("istrGetU16: value read is out of the range of representable values"); return false; } @@ -151,16 +164,16 @@ bool istrGetu16(str_istream_t *ctx, uint16 *val) { return true; } -bool istrGetu32(str_istream_t *ctx, uint32 *val) { +bool istrGetU32(instream_t *ctx, uint32 *val) { char *end = NULL; *val = (uint32) strtoul(ctx->cur, &end, 0); if(ctx->cur == end) { - warn("istrGetu32: no valid conversion could be performed"); + 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"); + warn("istrGetU32: value read is out of the range of representable values"); return false; } @@ -168,16 +181,16 @@ bool istrGetu32(str_istream_t *ctx, uint32 *val) { return true; } -bool istrGetu64(str_istream_t *ctx, uint64 *val) { +bool istrGetU64(instream_t *ctx, uint64 *val) { char *end = NULL; *val = strtoull(ctx->cur, &end, 0); if(ctx->cur == end) { - warn("istrGetu64: no valid conversion could be performed"); + 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"); + warn("istrGetU64: value read is out of the range of representable values"); return false; } @@ -185,16 +198,16 @@ bool istrGetu64(str_istream_t *ctx, uint64 *val) { return true; } -bool istrGeti8(str_istream_t *ctx, int8 *val) { +bool istrGetI8(instream_t *ctx, int8 *val) { char *end = NULL; *val = (int8) strtol(ctx->cur, &end, 0); if(ctx->cur == end) { - warn("istrGeti8: no valid conversion could be performed"); + 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"); + warn("istrGetI8: value read is out of the range of representable values"); return false; } @@ -202,16 +215,16 @@ bool istrGeti8(str_istream_t *ctx, int8 *val) { return true; } -bool istrGeti16(str_istream_t *ctx, int16 *val) { +bool istrGetI16(instream_t *ctx, int16 *val) { char *end = NULL; *val = (int16) strtol(ctx->cur, &end, 0); if(ctx->cur == end) { - warn("istrGeti16: no valid conversion could be performed"); + 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"); + warn("istrGetI16: value read is out of the range of representable values"); return false; } @@ -219,16 +232,16 @@ bool istrGeti16(str_istream_t *ctx, int16 *val) { return true; } -bool istrGeti32(str_istream_t *ctx, int32 *val) { +bool istrGetI32(instream_t *ctx, int32 *val) { char *end = NULL; *val = (int32) strtol(ctx->cur, &end, 0); if(ctx->cur == end) { - warn("istrGeti32: no valid conversion could be performed"); + 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"); + warn("istrGetI32: value read is out of the range of representable values"); return false; } @@ -236,16 +249,16 @@ bool istrGeti32(str_istream_t *ctx, int32 *val) { return true; } -bool istrGeti64(str_istream_t *ctx, int64 *val) { +bool istrGetI64(instream_t *ctx, int64 *val) { char *end = NULL; *val = strtoll(ctx->cur, &end, 0); if(ctx->cur == end) { - warn("istrGeti64: no valid conversion could be performed"); + 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"); + warn("istrGetI64: value read is out of the range of representable values"); return false; } @@ -253,16 +266,16 @@ bool istrGeti64(str_istream_t *ctx, int64 *val) { return true; } -bool istrGetfloat(str_istream_t *ctx, float *val) { +bool istrGetFloat(instream_t *ctx, float *val) { char *end = NULL; *val = strtof(ctx->cur, &end); if(ctx->cur == end) { - warn("istrGetfloat: no valid conversion could be performed"); + 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"); + warn("istrGetFloat: value read is out of the range of representable values"); return false; } @@ -270,16 +283,16 @@ bool istrGetfloat(str_istream_t *ctx, float *val) { return true; } -bool istrGetdouble(str_istream_t *ctx, double *val) { +bool istrGetDouble(instream_t *ctx, double *val) { char *end = NULL; *val = strtod(ctx->cur, &end); if(ctx->cur == end) { - warn("istrGetdouble: no valid conversion could be performed"); + warn("istrGetDouble: no valid conversion could be performed (%.5s)", ctx->cur); return false; } else if(*val == HUGE_VAL || *val == -HUGE_VAL) { - warn("istrGetdouble: value read is out of the range of representable values"); + warn("istrGetDouble: value read is out of the range of representable values"); return false; } @@ -287,286 +300,127 @@ bool istrGetdouble(str_istream_t *ctx, double *val) { return true; } -usize istrGetstring(str_istream_t *ctx, char **val, char delim) { +str_t istrGetStr(arena_t *arena, instream_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) { - *val = NULL; - return 0; + return (str_t){0}; } usize len = ctx->cur - from; - *val = (char *)malloc(len + 1); - memcpy(*val, from, len); - (*val)[len] = '\0'; - return len; + str_t out = { + .buf = alloc(arena, char, len + 1), + .len = len + }; + memcpy(out.buf, from, len); + return out; } -usize istrGetstringBuf(str_istream_t *ctx, char *val, usize len) { +usize istrGetBuf(instream_t *ctx, char *buf, usize buflen) { usize 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; + buflen -= 1; + buflen = remaining < buflen ? remaining : buflen; + memcpy(buf, ctx->cur, buflen); + buf[buflen] = '\0'; + ctx->cur += buflen; + return buflen; } -strview_t istrGetview(str_istream_t *ctx, char delim) { +strview_t istrGetView(instream_t *ctx, char delim) { const char *from = ctx->cur; istrIgnore(ctx, delim); usize len = ctx->cur - from; return strvInitLen(from, len); } -strview_t istrGetviewLen(str_istream_t *ctx, usize from, usize to) { - usize len = ctx->size - (ctx->cur - ctx->start) - from; - if (to > len) to = len; - if (from > to) from = to; - return strvInitLen(ctx->cur + from, to - from); +strview_t istrGetViewLen(instream_t *ctx, usize len) { + const char *from = ctx->cur; + istrSkip(ctx, len); + usize buflen = ctx->cur - from; + return (strview_t){ from, buflen }; } /* == OUTPUT STREAM =========================================== */ -static void _ostrRealloc(str_ostream_t *ctx, usize needed) { - ctx->cap = (ctx->cap * 2) + needed; - ctx->buf = (char *)realloc(ctx->buf, ctx->cap); +void ostr__remove_null(outstream_t *o) { + if (ostrTell(o)) { + arenaPop(o->arena, 1); + } } -str_ostream_t ostrInit() { - return ostrInitLen(1); -} - -str_ostream_t ostrInitLen(usize initial_alloc) { - str_ostream_t stream; - stream.buf = (char *)calloc(initial_alloc, 1); - stream.len = 0; - stream.cap = initial_alloc; - return stream; -} - -str_ostream_t ostrInitStr(const char *cstr, usize len) { - str_ostream_t stream; - stream.buf = (char *)malloc(len + 1); - memcpy(stream.buf, cstr, len); - stream.len = len; - stream.cap = len + 1; - return stream; -} - -void ostrFree(str_ostream_t ctx) { - free(ctx.buf); -} - -void ostrClear(str_ostream_t *ctx) { - ctx->len = 0; -} - -char ostrBack(str_ostream_t ctx) { - if(ctx.len == 0) return '\0'; - return ctx.buf[ctx.len - 1]; -} - -str_t ostrAsStr(str_ostream_t ctx) { - return (str_t) { - .buf = ctx.buf, - .len = ctx.len +outstream_t ostrInit(arena_t *arena) { + return (outstream_t){ + .beg = (char *)(arena ? arena->current : NULL), + .arena = arena, }; } -strview_t ostrAsView(str_ostream_t ctx) { - return (strview_t) { - .buf = ctx.buf, - .len = ctx.len +void ostrClear(outstream_t *ctx) { + arenaPop(ctx->arena, ostrTell(ctx)); +} + +usize ostrTell(outstream_t *ctx) { + return ctx->arena ? (char *)ctx->arena->current - ctx->beg : 0; +} + +char ostrBack(outstream_t *ctx) { + return arenaTell(ctx->arena) ? *ctx->arena->current : '\0'; +} + +str_t ostrAsStr(outstream_t *ctx) { + return (str_t){ + .buf = ctx->beg, + .len = ostrTell(ctx) }; } -void ostrReplace(str_ostream_t *ctx, char from, char to) { - for(usize i = 0; i < ctx->len; ++i) { - if(ctx->buf[i] == from) { - ctx->buf[i] = to; - } - } +strview_t ostrAsView(outstream_t *ctx) { + return (strview_t){ + .buf = ctx->beg, + .len = ostrTell(ctx) + }; } -void ostrPrintf(str_ostream_t *ctx, const char *fmt, ...) { - va_list va; - va_start(va, fmt); - ostrPrintfV(ctx, fmt, va); - va_end(va); +void ostrPrintf(outstream_t *ctx, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + ostrPrintfV(ctx, fmt, args); + va_end(args); } -void ostrPrintfV(str_ostream_t *ctx, const char *fmt, va_list args) { - va_list vtemp; - int len; - usize remaining; - - // vsnprintf returns the length of the formatted string, even if truncated - // we use this to get the actual length of the formatted string - va_copy(vtemp, args); - len = vsnprintf(NULL, 0, fmt, vtemp); - va_end(vtemp); - if(len < 0) { - err("couldn't format string \"%s\"", fmt); - goto error; - } - - remaining = ctx->cap - ctx->len; - if(remaining < (usize)len) { - _ostrRealloc(ctx, len + 1); - remaining = ctx->cap - ctx->len; - } - - // actual formatting here - va_copy(vtemp, args); - len = vsnprintf(ctx->buf + ctx->len, remaining, fmt, vtemp); - va_end(vtemp); - if(len < 0) { - err("couldn't format stringh \"%s\"", fmt); - goto error; - } - ctx->len += len; - -error: - return; +void ostrPrintfV(outstream_t *ctx, const char *fmt, va_list args) { + if (!ctx->arena) return; + ostr__remove_null(ctx); + strFmtv(ctx->arena, fmt, args); } - -#define APPEND_BUF_LEN 20 - -void ostrPutc(str_ostream_t *ctx, char c) { - ostrAppendchar(ctx, c); +void ostrPutc(outstream_t *ctx, char c) { + if (!ctx->arena) return; + ostr__remove_null(ctx); + char *newc = alloc(ctx->arena, char); + *newc = c; } -void ostrPuts(str_ostream_t *ctx, const char *str) { - ostrAppendview(ctx, strvInit(str)); +void ostrPuts(outstream_t *ctx, strview_t v) { + if (strvIsEmpty(v)) return; + ostr__remove_null(ctx); + str(ctx->arena, v); } -void ostrAppendbool(str_ostream_t *ctx, bool val) { - ostrAppendview(ctx, strvInit(val ? "true" : "false")); +void ostrAppendBool(outstream_t *ctx, bool val) { + ostrPuts(ctx, val ? strv("true") : strv("false")); } -void ostrAppendchar(str_ostream_t *ctx, char val) { - if(ctx->len >= ctx->cap) { - _ostrRealloc(ctx, 1); - } - ctx->buf[ctx->len++] = val; - ctx->buf[ctx->len] = '\0'; +void ostrAppendUInt(outstream_t *ctx, uint64 val) { + ostrPrintf(ctx, "%I64u", val); } -void ostrAppendu8(str_ostream_t *ctx, uint8 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 ostrAppendInt(outstream_t *ctx, int64 val) { + ostrPrintf(ctx, "%I64d", val); } -void ostrAppendu16(str_ostream_t *ctx, uint16 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 ostrAppendNum(outstream_t *ctx, double val) { + ostrPrintf(ctx, "%g", val); } -void ostrAppendu32(str_ostream_t *ctx, uint32 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 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 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 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 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 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->cap - ctx->len) <= view.len) { - _ostrRealloc(ctx, view.len + 1); - } - memcpy(ctx->buf + ctx->len, view.buf, view.len); - ctx->len += view.len; - ctx->buf[ctx->len] = '\0'; -} +#include "warnings/colla_warn_end.h" \ No newline at end of file diff --git a/colla/strstream.h b/colla/strstream.h index 55aa041..19f09bb 100644 --- a/colla/strstream.h +++ b/colla/strstream.h @@ -4,110 +4,97 @@ extern "C" { #endif -#include -#include #include #include "collatypes.h" #include "str.h" +typedef struct arena_t arena_t; + /* == INPUT STREAM ============================================ */ typedef struct { const char *start; const char *cur; usize size; -} str_istream_t; +} instream_t; // initialize with null-terminated string -str_istream_t istrInit(const char *str); -str_istream_t istrInitLen(const char *str, usize len); +instream_t istrInit(const char *str); +instream_t istrInitLen(const char *str, usize len); // get the current character and advance -char istrGet(str_istream_t *ctx); +char istrGet(instream_t *ctx); // get the current character but don't advance -char istrPeek(str_istream_t *ctx); +char istrPeek(instream_t *ctx); +// get the next character but don't advance +char istrPeekNext(instream_t *ctx); // ignore characters until the delimiter -void istrIgnore(str_istream_t *ctx, char delim); +void istrIgnore(instream_t *ctx, char delim); // ignore characters until the delimiter and skip it -void istrIgnoreAndSkip(str_istream_t *ctx, char delim); +void istrIgnoreAndSkip(instream_t *ctx, char delim); // skip n characters -void istrSkip(str_istream_t *ctx, usize n); +void istrSkip(instream_t *ctx, usize n); // skips whitespace (' ', '\n', '\t', '\r') -void istrSkipWhitespace(str_istream_t *ctx); +void istrSkipWhitespace(instream_t *ctx); // read len bytes into buffer, the buffer will not be null terminated -void istrRead(str_istream_t *ctx, char *buf, usize len); +void istrRead(instream_t *ctx, char *buf, usize len); // read a maximum of len bytes into buffer, the buffer will not be null terminated // returns the number of bytes read -usize istrReadMax(str_istream_t *ctx, char *buf, usize len); +usize istrReadMax(instream_t *ctx, char *buf, usize len); // returns to the beginning of the stream -void istrRewind(str_istream_t *ctx); +void istrRewind(instream_t *ctx); // returns back characters -void istrRewindN(str_istream_t *ctx, usize amount); +void istrRewindN(instream_t *ctx, usize amount); // returns the number of bytes read from beginning of stream -usize istrTell(str_istream_t ctx); +usize istrTell(instream_t ctx); // returns the number of bytes left to read in the stream -usize istrRemaining(str_istream_t ctx); +usize istrRemaining(instream_t ctx); // return true if the stream doesn't have any new bytes to read -bool istrIsFinished(str_istream_t ctx); +bool istrIsFinished(instream_t ctx); -bool istrGetbool(str_istream_t *ctx, bool *val); -bool istrGetu8(str_istream_t *ctx, uint8 *val); -bool istrGetu16(str_istream_t *ctx, uint16 *val); -bool istrGetu32(str_istream_t *ctx, uint32 *val); -bool istrGetu64(str_istream_t *ctx, uint64 *val); -bool istrGeti8(str_istream_t *ctx, int8 *val); -bool istrGeti16(str_istream_t *ctx, int16 *val); -bool istrGeti32(str_istream_t *ctx, int32 *val); -bool istrGeti64(str_istream_t *ctx, int64 *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 -usize istrGetstring(str_istream_t *ctx, char **val, char delim); +bool istrGetBool(instream_t *ctx, bool *val); +bool istrGetU8(instream_t *ctx, uint8 *val); +bool istrGetU16(instream_t *ctx, uint16 *val); +bool istrGetU32(instream_t *ctx, uint32 *val); +bool istrGetU64(instream_t *ctx, uint64 *val); +bool istrGetI8(instream_t *ctx, int8 *val); +bool istrGetI16(instream_t *ctx, int16 *val); +bool istrGetI32(instream_t *ctx, int32 *val); +bool istrGetI64(instream_t *ctx, int64 *val); +bool istrGetFloat(instream_t *ctx, float *val); +bool istrGetDouble(instream_t *ctx, double *val); +str_t istrGetStr(arena_t *arena, instream_t *ctx, char delim); // get a string of maximum size len, the string is not allocated by the function and will be null terminated -usize istrGetstringBuf(str_istream_t *ctx, char *val, usize len); -strview_t istrGetview(str_istream_t *ctx, char delim); -strview_t istrGetviewLen(str_istream_t *ctx, usize from, usize to); +usize istrGetBuf(instream_t *ctx, char *buf, usize buflen); +strview_t istrGetView(instream_t *ctx, char delim); +strview_t istrGetViewLen(instream_t *ctx, usize len); /* == OUTPUT STREAM =========================================== */ typedef struct { - char *buf; - usize len; - usize cap; -} str_ostream_t; + char *beg; + arena_t *arena; +} outstream_t; -str_ostream_t ostrInit(void); -str_ostream_t ostrInitLen(usize initial_alloc); -str_ostream_t ostrInitStr(const char *buf, usize len); +outstream_t ostrInit(arena_t *exclusive_arena); +void ostrClear(outstream_t *ctx); -void ostrFree(str_ostream_t ctx); -void ostrClear(str_ostream_t *ctx); +usize ostrTell(outstream_t *ctx); -char ostrBack(str_ostream_t ctx); -str_t ostrAsStr(str_ostream_t ctx); -strview_t ostrAsView(str_ostream_t ctx); +char ostrBack(outstream_t *ctx); +str_t ostrAsStr(outstream_t *ctx); +strview_t ostrAsView(outstream_t *ctx); -void ostrReplace(str_ostream_t *ctx, char from, char to); +void ostrPrintf(outstream_t *ctx, const char *fmt, ...); +void ostrPrintfV(outstream_t *ctx, const char *fmt, va_list args); +void ostrPutc(outstream_t *ctx, char c); +void ostrPuts(outstream_t *ctx, strview_t v); -void ostrPrintf(str_ostream_t *ctx, const char *fmt, ...); -void ostrPrintfV(str_ostream_t *ctx, const char *fmt, va_list args); -void ostrPutc(str_ostream_t *ctx, char c); -void ostrPuts(str_ostream_t *ctx, const char *str); - -void ostrAppendbool(str_ostream_t *ctx, bool val); -void ostrAppendchar(str_ostream_t *ctx, char val); -void ostrAppendu8(str_ostream_t *ctx, uint8 val); -void ostrAppendu16(str_ostream_t *ctx, uint16 val); -void ostrAppendu32(str_ostream_t *ctx, uint32 val); -void ostrAppendu64(str_ostream_t *ctx, uint64 val); -void ostrAppendi8(str_ostream_t *ctx, int8 val); -void ostrAppendi16(str_ostream_t *ctx, int16 val); -void ostrAppendi32(str_ostream_t *ctx, int32 val); -void ostrAppendi64(str_ostream_t *ctx, int64 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); +void ostrAppendBool(outstream_t *ctx, bool val); +void ostrAppendUInt(outstream_t *ctx, uint64 val); +void ostrAppendInt(outstream_t *ctx, int64 val); +void ostrAppendNum(outstream_t *ctx, double val); #ifdef __cplusplus } // extern "C" diff --git a/colla/tracelog.c b/colla/tracelog.c index bd126d0..71ab15b 100644 --- a/colla/tracelog.c +++ b/colla/tracelog.c @@ -4,9 +4,32 @@ #include #include -#ifdef _WIN32 - #pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS. - #include "win32_slim.h" +#include "format.h" + +#if COLLA_WIN + #if COLLA_MSVC + #pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS. + #endif + + #include + + // avoid including windows.h + + #ifndef STD_OUTPUT_HANDLE + #define STD_OUTPUT_HANDLE ((DWORD)-11) + #endif + #ifndef CP_UTF8 + #define CP_UTF8 65001 + #endif + + typedef unsigned short WORD; + typedef unsigned long DWORD; + typedef unsigned int UINT; + typedef int BOOL; + WINBASEAPI HANDLE WINAPI GetStdHandle(DWORD nStdHandle); + WINBASEAPI BOOL WINAPI SetConsoleTextAttribute(HANDLE hConsoleOutput, WORD wAttributes); + WINBASEAPI BOOL WINAPI SetConsoleOutputCP(UINT wCodePageID); + #ifndef TLOG_VS #define TLOG_WIN32_NO_VS #ifndef TLOG_NO_COLOURS @@ -16,33 +39,33 @@ #endif #ifdef TLOG_VS - #ifndef _WIN32 + #if COLLA_WIN #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 "" + #define COLOUR_BLACK "" + #define COLOUR_RED "" + #define COLOUR_GREEN "" + #define COLOUR_YELLOW "" + #define COLOUR_BLUE "" + #define COLOUR_MAGENTA "" + #define COLOUR_CYAN "" + #define COLOUR_WHITE "" + #define COLOUR_RESET "" + #define COLOUR_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" + #define COLOUR_BLACK "\033[30m" + #define COLOUR_RED "\033[31m" + #define COLOUR_GREEN "\033[32m" + #define COLOUR_YELLOW "\033[33m" + #define COLOUR_BLUE "\033[22;34m" + #define COLOUR_MAGENTA "\033[35m" + #define COLOUR_CYAN "\033[36m" + #define COLOUR_WHITE "\033[37m" + #define COLOUR_RESET "\033[0m" + #define COLOUR_BOLD "\033[1m" #endif #define MAX_TRACELOG_MSG_LENGTH 1024 @@ -67,8 +90,7 @@ static void setLevelColour(int level) { void traceLog(int level, const char *fmt, ...) { va_list args; - va_start(args, fmt); - traceLogVaList(level, fmt, args); + va_start(args, fmt); traceLogVaList(level, fmt, args); va_end(args); } @@ -88,12 +110,12 @@ void traceLogVaList(int level, const char *fmt, va_list args) { 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; + case LogTrace: beg = COLOUR_BOLD COLOUR_WHITE "[TRACE]: " COLOUR_RESET; break; + case LogDebug: beg = COLOUR_BOLD COLOUR_BLUE "[DEBUG]: " COLOUR_RESET; break; + case LogInfo: beg = COLOUR_BOLD COLOUR_GREEN "[INFO]: " COLOUR_RESET; break; + case LogWarning: beg = COLOUR_BOLD COLOUR_YELLOW "[WARNING]: " COLOUR_RESET; break; + case LogError: beg = COLOUR_BOLD COLOUR_RED "[ERROR]: " COLOUR_RESET; break; + case LogFatal: beg = COLOUR_BOLD COLOUR_RED "[FATAL]: " COLOUR_RESET; break; default: beg = ""; break; } @@ -104,7 +126,7 @@ void traceLogVaList(int level, const char *fmt, va_list args) { strncpy(buffer, beg, sizeof(buffer)); #endif - vsnprintf(buffer + offset, sizeof(buffer) - offset, fmt, args); + fmtBufferv(buffer + offset, sizeof(buffer) - offset, fmt, args); #if defined(TLOG_VS) OutputDebugStringA(buffer); @@ -123,7 +145,10 @@ void traceLogVaList(int level, const char *fmt, va_list args) { #endif #ifndef TLOG_DONT_EXIT_ON_FATAL - if (level == LogFatal) exit(1); + if (level == LogFatal) { + abort(); + exit(1); + } #endif #ifdef TLOG_MUTEX diff --git a/colla/tracelog.h b/colla/tracelog.h index 7e10cd2..516dc3e 100644 --- a/colla/tracelog.h +++ b/colla/tracelog.h @@ -12,7 +12,7 @@ extern "C" { * -> TLOG_MUTEX: use a mutex on every traceLog call */ -#include +#include "collatypes.h" #include enum { diff --git a/colla/vmem.c b/colla/vmem.c new file mode 100644 index 0000000..fb19a71 --- /dev/null +++ b/colla/vmem.c @@ -0,0 +1,133 @@ +#include "vmem.h" + +#include + +#include "tracelog.h" + +static usize vmem__page_size = 0; +static void vmem__update_page_size(void); + +// platform generic functions + +usize vmemGetPageSize(void) { + if (!vmem__page_size) { + vmem__update_page_size(); + } + return vmem__page_size; +} + +usize vmemPadToPage(usize byte_count) { + if (!vmem__page_size) { + vmem__update_page_size(); + } + + if (byte_count == 0) { + return vmem__page_size; + } + + // bit twiddiling, vmem__page_size MUST be a power of 2 + usize padding = vmem__page_size - (byte_count & (vmem__page_size - 1)); + if (padding == vmem__page_size) { + padding = 0; + } + return byte_count + padding; +} + +#if COLLA_WIN + +#define WIN32_LEAN_AND_MEAN +#include + +void *vmemInit(usize size, usize *out_padded_size) { + usize alloc_size = vmemPadToPage(size); + + void *ptr = VirtualAlloc(NULL, alloc_size, MEM_RESERVE, PAGE_NOACCESS); + + if (out_padded_size) { + *out_padded_size = alloc_size; + } + + return ptr; +} + +bool vmemRelease(void *base_ptr) { + return VirtualFree(base_ptr, 0, MEM_RELEASE); +} + +bool vmemCommit(void *ptr, usize num_of_pages) { + usize page_size = vmemGetPageSize(); + + void *new_ptr = VirtualAlloc(ptr, num_of_pages * page_size, MEM_COMMIT, PAGE_READWRITE); + + if (!new_ptr) { + debug("ERROR: failed to commit memory: %lu\n", GetLastError()); + } + + return new_ptr != NULL; +} + +static void vmem__update_page_size(void) { + SYSTEM_INFO info = {0}; + GetSystemInfo(&info); + vmem__page_size = info.dwPageSize; +} + +#elif COLLA_LIN + +#include +#include +#include +#include + +struct vmem__header { + usize len; +}; + +void *vmemInit(usize size, usize *out_padded_size) { + size += sizeof(vmem__header); + usize alloc_size = padToPage(size); + + vmem__header *header = mmap(NULL, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + if (header == MAP_FAILED) { + fatal("could not map %zu memory: %s", size, strerror(errno)); + } + + if (padded_size) { + *padded_size = alloc_size; + } + + header->len = alloc_size; + + return header + 1; +} + +bool vmemRelease(void *base_ptr) { + if (!base_ptr) return false; + vmem__header *header = (vmem__header *)base_ptr - 1; + + int res = munmap(header, header->len); + if (res == -1) { + err("munmap failed: %s", strerror(errno)); + } + return res != -1; +} + +bool vmemCommit(void *ptr, usize num_of_pages) { + // mmap doesn't need a commit step + (VOID)ptr; + (VOID)num_of_pages; + return true; +} + +static void vmem__update_page_size(void) { + long lin_page_size = sysconf(_SC_PAGE_SIZE); + + if (lin_page_size < 0) { + fatal("could not get page size: %s", strerror(errno)); + } + + page_size = (usize)lin_page_size; +} + +#endif \ No newline at end of file diff --git a/colla/vmem.h b/colla/vmem.h new file mode 100644 index 0000000..39c81ed --- /dev/null +++ b/colla/vmem.h @@ -0,0 +1,12 @@ +#ifndef VIRTUAL_MEMORY_HEADER +#define VIRTUAL_MEMORY_HEADER + +#include "collatypes.h" + +void *vmemInit(usize size, usize *out_padded_size); +bool vmemRelease(void *base_ptr); +bool vmemCommit(void *ptr, usize num_of_pages); +usize vmemGetPageSize(void); +usize vmemPadToPage(usize byte_count); + +#endif // VIRTUAL_MEMORY_HEADER \ No newline at end of file diff --git a/colla/warnings/colla_warn_beg.h b/colla/warnings/colla_warn_beg.h new file mode 100644 index 0000000..4747736 --- /dev/null +++ b/colla/warnings/colla_warn_beg.h @@ -0,0 +1,7 @@ +#if COLLA_CLANG + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winitializer-overrides" +#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" + +#endif \ No newline at end of file diff --git a/colla/warnings/colla_warn_end.h b/colla/warnings/colla_warn_end.h new file mode 100644 index 0000000..e45a222 --- /dev/null +++ b/colla/warnings/colla_warn_end.h @@ -0,0 +1,5 @@ +#if COLLA_CLANG + +#pragma clang diagnostic pop + +#endif \ No newline at end of file diff --git a/colla/win32_slim.h b/colla/win32_slim.h deleted file mode 100644 index 9991c7d..0000000 --- a/colla/win32_slim.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#ifdef _WIN32 - -#ifndef WIN32_LEAN_AND_MEAN - #define WIN32_LEAN_AND_MEAN -#endif - -#ifndef WIN32_EXTRA_LEAN - #define WIN32_EXTRA_LEAN -#endif - -#include - -#endif // _WIN32 diff --git a/compile.ini b/compile.ini deleted file mode 100644 index b0e26db..0000000 --- a/compile.ini +++ /dev/null @@ -1,8 +0,0 @@ -include = colla/ -files = colla/*.c - -[windows] -link = ws2_32.lib - -[linux] -link = pthread \ No newline at end of file diff --git a/deprecated/coroutine.h b/deprecated/coroutine.h index ab5905b..7a24984 100644 --- a/deprecated/coroutine.h +++ b/deprecated/coroutine.h @@ -4,8 +4,8 @@ extern "C" { #endif -#include // bool #include // memset +#include "collatypes.h" #include "tracelog.h" // fatal // heavily inspired by https://gist.github.com/Enichan/5f01140530ff0133fde19c9549a2a973 diff --git a/colla/dir.c b/deprecated/dir.c similarity index 93% rename from colla/dir.c rename to deprecated/dir.c index b6771d2..5a4c2a5 100644 --- a/colla/dir.c +++ b/deprecated/dir.c @@ -1,204 +1,204 @@ -#include "dir.h" -#include "tracelog.h" - -#ifdef _WIN32 -#include "win32_slim.h" -#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.len += 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; -} - -void dirClose(dir_t ctx) { - free(ctx); -} - -bool dirValid(dir_t ctx) { - _dir_internal_t *dir = (_dir_internal_t*)ctx; - return dir->handle != INVALID_HANDLE_VALUE; -} - -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 dirCreate(const char *path) { - CreateDirectoryA(path, NULL); -} - -#else - -#include -#include -#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 = (_dir_internal_t *)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); - } -} - -bool dirValid(dir_t ctx) { - _dir_internal_t *dir = (_dir_internal_t*)ctx; - return dir->dir != NULL; -} - -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 = strFromStr(dp->d_name); - return &in->next; -} - -void dirCreate(const char *path) { - mkdir(path, 0700); -} - -#endif - -#include - -bool dirRemove(const char *path) { - dir_t dir = dirOpen(path); - if (!dirValid(dir)) return false; - dir_entry_t *it = NULL; - while((it = dirNext(dir))) { - if (it->type == FS_TYPE_FILE) { - str_t file_path = strFromFmt("%s/%s", path, it->name.buf); - if (remove(file_path.buf)) { - err("couldn't remove %s > %s", file_path.buf, strerror(errno)); - } - strFree(file_path); - } - else if (it->type == FS_TYPE_DIR) { - if (strcmp(it->name.buf, ".") == 0 || strcmp(it->name.buf, "..") == 0) { - continue; - } - str_t new_path = strFromFmt("%s/%s", path, it->name.buf); - info("new path: %s--%s -> %s", path, it->name.buf, new_path.buf); - if (!dirRemove(new_path.buf)) { - err("couldn't delete folder %s", new_path.buf); - break; - } - strFree(new_path); - } - else { - err("%d -> %s", it->type, it->name.buf); - } - } - dirClose(dir); -#ifdef _WIN32 - return RemoveDirectoryA(path); -#else - return rmdir(path) == 0; -#endif -} +#include "dir.h" +#include "tracelog.h" + +#if COLLA_WIN +#include "win32_slim.h" +#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); + outstream_t out = ostrInitLen(n + 3); + n = GetFullPathName(path, n, out.buf, NULL); + assert(n > 0); + out.len += 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; +} + +void dirClose(dir_t ctx) { + free(ctx); +} + +bool dirValid(dir_t ctx) { + _dir_internal_t *dir = (_dir_internal_t*)ctx; + return dir->handle != INVALID_HANDLE_VALUE; +} + +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 dirCreate(const char *path) { + CreateDirectoryA(path, NULL); +} + +#else + +#include +#include +#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 = (_dir_internal_t *)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); + } +} + +bool dirValid(dir_t ctx) { + _dir_internal_t *dir = (_dir_internal_t*)ctx; + return dir->dir != NULL; +} + +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 = strFromStr(dp->d_name); + return &in->next; +} + +void dirCreate(const char *path) { + mkdir(path, 0700); +} + +#endif + +#include + +bool dirRemove(const char *path) { + dir_t dir = dirOpen(path); + if (!dirValid(dir)) return false; + dir_entry_t *it = NULL; + while((it = dirNext(dir))) { + if (it->type == FS_TYPE_FILE) { + str_t file_path = strFromFmt("%s/%s", path, it->name.buf); + if (remove(file_path.buf)) { + err("couldn't remove %s > %s", file_path.buf, strerror(errno)); + } + strFree(file_path); + } + else if (it->type == FS_TYPE_DIR) { + if (strcmp(it->name.buf, ".") == 0 || strcmp(it->name.buf, "..") == 0) { + continue; + } + str_t new_path = strFromFmt("%s/%s", path, it->name.buf); + info("new path: %s--%s -> %s", path, it->name.buf, new_path.buf); + if (!dirRemove(new_path.buf)) { + err("couldn't delete folder %s", new_path.buf); + break; + } + strFree(new_path); + } + else { + err("%d -> %s", it->type, it->name.buf); + } + } + dirClose(dir); +#if COLLA_WIN + return RemoveDirectoryA(path); +#else + return rmdir(path) == 0; +#endif +} diff --git a/colla/dir.h b/deprecated/dir.h similarity index 93% rename from colla/dir.h rename to deprecated/dir.h index 372b01d..90a5871 100644 --- a/colla/dir.h +++ b/deprecated/dir.h @@ -1,34 +1,34 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include "str.h" - -typedef void *dir_t; - -typedef enum { - FS_TYPE_UNKNOWN, - FS_TYPE_FILE, - FS_TYPE_DIR, -} fs_type_t; - -typedef struct { - fs_type_t type; - str_t name; -} dir_entry_t; - -dir_t dirOpen(const char *path); -void dirClose(dir_t ctx); - -bool dirValid(dir_t ctx); - -dir_entry_t *dirNext(dir_t ctx); - -void dirCreate(const char *path); -bool dirRemove(const char *path); - -#ifdef __cplusplus -} // extern "C" -#endif +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "str.h" + +typedef void *dir_t; + +typedef enum { + FS_TYPE_UNKNOWN, + FS_TYPE_FILE, + FS_TYPE_DIR, +} fs_type_t; + +typedef struct { + fs_type_t type; + str_t name; +} dir_entry_t; + +dir_t dirOpen(const char *path); +void dirClose(dir_t ctx); + +bool dirValid(dir_t ctx); + +dir_entry_t *dirNext(dir_t ctx); + +void dirCreate(const char *path); +bool dirRemove(const char *path); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/deprecated/dirwatch.c b/deprecated/dirwatch.c index 796dc38..49ff44e 100644 --- a/deprecated/dirwatch.c +++ b/deprecated/dirwatch.c @@ -5,7 +5,7 @@ #include #include "tracelog.h" -#ifdef _WIN32 +#if COLLA_WIN #include "win32_slim.h" #include "str.h" diff --git a/deprecated/dirwatch.h b/deprecated/dirwatch.h index 1926ef1..a2d73aa 100644 --- a/deprecated/dirwatch.h +++ b/deprecated/dirwatch.h @@ -27,7 +27,7 @@ extern "C" { * } */ -#include +#include "collatypes.h" #include "cthreads.h" enum { diff --git a/deprecated/fs.c b/deprecated/fs.c index 880536f..85a6a51 100644 --- a/deprecated/fs.c +++ b/deprecated/fs.c @@ -7,7 +7,7 @@ #include "tracelog.h" -#ifdef _WIN32 +#if COLLA_WIN #include "win32_slim.h" #include diff --git a/deprecated/fs.h b/deprecated/fs.h index e58c403..e917311 100644 --- a/deprecated/fs.h +++ b/deprecated/fs.h @@ -4,8 +4,7 @@ extern "C" { #endif -#include -#include +#include "collatypes.h" #include "file.h" diff --git a/colla/jobpool.c b/deprecated/jobpool.c similarity index 95% rename from colla/jobpool.c rename to deprecated/jobpool.c index 5fdb86f..f4e32bc 100644 --- a/colla/jobpool.c +++ b/deprecated/jobpool.c @@ -1,144 +1,144 @@ -#include "jobpool.h" - -#include - -typedef struct { - cthread_func_t func; - void *arg; -} job_t; - -typedef struct { - vec(job_t) jobs; - uint32 head; - cmutex_t work_mutex; - condvar_t work_cond; - condvar_t working_cond; - int32 working_count; - int32 thread_count; - bool stop; -} _pool_internal_t; - -static job_t _getJob(_pool_internal_t *pool); -static int _poolWorker(void *arg); - -jobpool_t poolInit(uint32 num) { - if (!num) num = 2; - - _pool_internal_t *pool = malloc(sizeof(_pool_internal_t)); - *pool = (_pool_internal_t){ - .work_mutex = mtxInit(), - .work_cond = condInit(), - .working_cond = condInit(), - .thread_count = (int32)num - }; - - for (usize i = 0; i < num; ++i) { - thrDetach(thrCreate(_poolWorker, pool)); - } - - return pool; -} - -void poolFree(jobpool_t pool_in) { - _pool_internal_t *pool = pool_in; - if (!pool) return; - - mtxLock(pool->work_mutex); - pool->stop = true; - condWakeAll(pool->work_cond); - mtxUnlock(pool->work_mutex); - - poolWait(pool); - - vecFree(pool->jobs); - mtxFree(pool->work_mutex); - condFree(pool->work_cond); - condFree(pool->working_cond); - - free(pool); -} - -bool poolAdd(jobpool_t pool_in, cthread_func_t func, void *arg) { - _pool_internal_t *pool = pool_in; - if (!pool) return false; - - mtxLock(pool->work_mutex); - - if (pool->head > vecLen(pool->jobs)) { - vecClear(pool->jobs); - pool->head = 0; - } - - job_t job = { func, arg }; - vecAppend(pool->jobs, job); - - condWake(pool->work_cond); - mtxUnlock(pool->work_mutex); - - return true; -} - -void poolWait(jobpool_t pool_in) { - _pool_internal_t *pool = pool_in; - if (!pool) return; - - mtxLock(pool->work_mutex); - // while its either - // - working and there's still some threads doing some work - // - not working and there's still some threads exiting - while ((!pool->stop && pool->working_count > 0) || - (pool->stop && pool->thread_count > 0) - ) { - condWait(pool->working_cond, pool->work_mutex); - } - mtxUnlock(pool->work_mutex); -} - -// == PRIVATE FUNCTIONS =================================== - -static job_t _getJob(_pool_internal_t *pool) { - if (pool->head >= vecLen(pool->jobs)) { - pool->head = 0; - } - job_t job = pool->jobs[pool->head++]; - return job; -} - -static int _poolWorker(void *arg) { - _pool_internal_t *pool = arg; - - while (true) { - mtxLock(pool->work_mutex); - // wait for a new job - while (pool->head >= vecLen(pool->jobs) && !pool->stop) { - condWait(pool->work_cond, pool->work_mutex); - } - - if (pool->stop) { - break; - } - - job_t job = _getJob(pool); - pool->working_count++; - mtxUnlock(pool->work_mutex); - - if (job.func) { - job.func(job.arg); - } - - mtxLock(pool->work_mutex); - pool->working_count--; - if (!pool->stop && - pool->working_count == 0 && - pool->head == vecLen(pool->jobs) - ) { - condWake(pool->working_cond); - } - mtxUnlock(pool->work_mutex); - } - - pool->thread_count--; - condWake(pool->working_cond); - mtxUnlock(pool->work_mutex); - return 0; -} +#include "jobpool.h" + +#include "vec.h" + +typedef struct { + cthread_func_t func; + void *arg; +} job_t; + +typedef struct { + vec(job_t) jobs; + uint32 head; + cmutex_t work_mutex; + condvar_t work_cond; + condvar_t working_cond; + int32 working_count; + int32 thread_count; + bool stop; +} _pool_internal_t; + +static job_t _getJob(_pool_internal_t *pool); +static int _poolWorker(void *arg); + +jobpool_t poolInit(uint32 num) { + if (!num) num = 2; + + _pool_internal_t *pool = malloc(sizeof(_pool_internal_t)); + *pool = (_pool_internal_t){ + .work_mutex = mtxInit(), + .work_cond = condInit(), + .working_cond = condInit(), + .thread_count = (int32)num + }; + + for (usize i = 0; i < num; ++i) { + thrDetach(thrCreate(_poolWorker, pool)); + } + + return pool; +} + +void poolFree(jobpool_t pool_in) { + _pool_internal_t *pool = pool_in; + if (!pool) return; + + mtxLock(pool->work_mutex); + pool->stop = true; + condWakeAll(pool->work_cond); + mtxUnlock(pool->work_mutex); + + poolWait(pool); + + vecFree(pool->jobs); + mtxFree(pool->work_mutex); + condFree(pool->work_cond); + condFree(pool->working_cond); + + free(pool); +} + +bool poolAdd(jobpool_t pool_in, cthread_func_t func, void *arg) { + _pool_internal_t *pool = pool_in; + if (!pool) return false; + + mtxLock(pool->work_mutex); + + if (pool->head > vecLen(pool->jobs)) { + vecClear(pool->jobs); + pool->head = 0; + } + + job_t job = { func, arg }; + vecAppend(pool->jobs, job); + + condWake(pool->work_cond); + mtxUnlock(pool->work_mutex); + + return true; +} + +void poolWait(jobpool_t pool_in) { + _pool_internal_t *pool = pool_in; + if (!pool) return; + + mtxLock(pool->work_mutex); + // while its either + // - working and there's still some threads doing some work + // - not working and there's still some threads exiting + while ((!pool->stop && pool->working_count > 0) || + (pool->stop && pool->thread_count > 0) + ) { + condWait(pool->working_cond, pool->work_mutex); + } + mtxUnlock(pool->work_mutex); +} + +// == PRIVATE FUNCTIONS =================================== + +static job_t _getJob(_pool_internal_t *pool) { + if (pool->head >= vecLen(pool->jobs)) { + pool->head = 0; + } + job_t job = pool->jobs[pool->head++]; + return job; +} + +static int _poolWorker(void *arg) { + _pool_internal_t *pool = arg; + + while (true) { + mtxLock(pool->work_mutex); + // wait for a new job + while (pool->head >= vecLen(pool->jobs) && !pool->stop) { + condWait(pool->work_cond, pool->work_mutex); + } + + if (pool->stop) { + break; + } + + job_t job = _getJob(pool); + pool->working_count++; + mtxUnlock(pool->work_mutex); + + if (job.func) { + job.func(job.arg); + } + + mtxLock(pool->work_mutex); + pool->working_count--; + if (!pool->stop && + pool->working_count == 0 && + pool->head == vecLen(pool->jobs) + ) { + condWake(pool->working_cond); + } + mtxUnlock(pool->work_mutex); + } + + pool->thread_count--; + condWake(pool->working_cond); + mtxUnlock(pool->work_mutex); + return 0; +} diff --git a/colla/jobpool.h b/deprecated/jobpool.h similarity index 77% rename from colla/jobpool.h rename to deprecated/jobpool.h index e33c2dc..401aa60 100644 --- a/colla/jobpool.h +++ b/deprecated/jobpool.h @@ -1,12 +1,12 @@ -#pragma once - -#include -#include - -typedef void *jobpool_t; - -jobpool_t poolInit(uint32 num); -void poolFree(jobpool_t pool); - -bool poolAdd(jobpool_t pool, cthread_func_t func, void *arg); -void poolWait(jobpool_t pool); +#pragma once + +#include "collatypes.h" +#include "cthreads.h" + +typedef void *jobpool_t; + +jobpool_t poolInit(uint32 num); +void poolFree(jobpool_t pool); + +bool poolAdd(jobpool_t pool, cthread_func_t func, void *arg); +void poolWait(jobpool_t pool); diff --git a/deprecated/os.c b/deprecated/os.c index 87207be..7586088 100644 --- a/deprecated/os.c +++ b/deprecated/os.c @@ -4,7 +4,7 @@ #include #include -#ifdef _WIN32 +#if COLLA_WIN #define _BUFSZ 128 #include diff --git a/deprecated/os.h b/deprecated/os.h index dee2347..160f092 100644 --- a/deprecated/os.h +++ b/deprecated/os.h @@ -9,7 +9,7 @@ extern "C" { #include "str.h" #include "collatypes.h" -#ifdef _WIN32 +#if COLLA_WIN #include #include "win32_slim.h" isize getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp); diff --git a/gen.lua b/gen.lua deleted file mode 100644 index bf4e3ca..0000000 --- a/gen.lua +++ /dev/null @@ -1,104 +0,0 @@ -local outfile = "colla.h" -local colladir = "src/" - -local function hasArg(value) - for k,v in pairs(arg) do - if v == value then - return true - end - end - return false -end - -local use_namespace = hasArg("-namespace") - -os.remove(outfile) - -local function cat(f) - local fp = io.open(f) - local text = fp:read("a") - fp:close() - return text -end - -str = [[ -/* - colla.h -- All colla libraries in a single header - Do the following in *one* C file to create the implementation - #define COLLA_IMPL - Use the following in the same C file for options - #define COLLA_NO_THREADS // don't include the threads module - #define COLLA_NO_NET // don't include networking stuff -*/ -]] - -str = str .. cat(colladir .. "collatypes.h") .. "\n" -str = str .. cat(colladir .. "tracelog.h") .. "\n" -str = str .. cat(colladir .. "str.h") .. "\n" -str = str .. cat(colladir .. "vec.h") .. "\n" -str = str .. cat(colladir .. "hashmap.h") .. "\n" -str = str .. cat(colladir .. "utf8.h") .. "\n" -str = str .. cat(colladir .. "ini.h") .. "\n" -str = str .. cat(colladir .. "strstream.h") .. "\n" -str = str .. cat(colladir .. "win32_slim.h") .. "\n" -str = str .. cat(colladir .. "file.h") .. "\n" -str = str .. cat(colladir .. "dir.h") .. "\n" - -str = str .. "#ifndef COLLA_NO_NET\n" - str = str .. cat(colladir .. "socket.h") .. "\n" - str = str .. cat(colladir .. "http.h") .. "\n" -str = str .. "#endif // COLLA_NO_NET\n" - -str = str .. "#if !defined(__TINYC__) && !defined(COLLA_NO_THREADS)\n" - str = str .. cat(colladir .. "cthreads.h") .. "\n" -str = str .. "#endif // !defined(__TINYC__) && !defined(COLLA_NO_THREADS)\n" - -str = str .. "#ifdef COLLA_IMPL\n" - str = str .. cat(colladir .. "tracelog.c") .. "\n" - str = str .. cat(colladir .. "strstream.c") .. "\n" - str = str .. cat(colladir .. "str.c") .. "\n" - str = str .. cat(colladir .. "hashmap.c") .. "\n" - str = str .. cat(colladir .. "utf8.c") .. "\n" - str = str .. cat(colladir .. "ini.c") .. "\n" - str = str .. cat(colladir .. "file.c") .. "\n" - str = str .. cat(colladir .. "dir.c") .. "\n" - - str = str .. "#ifndef COLLA_NO_NET\n" - str = str .. cat(colladir .. "socket.c") .. "\n" - str = str .. cat(colladir .. "http.c") .. "\n" - str = str .. "#endif // COLLA_NO_NET\n" - - str = str .. "#if !defined(__TINYC__) && !defined(COLLA_NO_THREADS)\n" - str = str .. cat(colladir .. "cthreads.c") .. "\n" - str = str .. "#endif // !defined(__TINYC__) && !defined(COLLA_NO_THREADS)\n" -str = str .. "#endif /* COLLA_IMPL */\n" - --- remove includes -str = str:gsub('#include "([^"]+)"', '/* #include "%1" */') - -str = str .. string.format([[ -/* - MIT License - Copyright (c) 1994-2019 Lua.org, PUC-Rio. - Copyright (c) 2020-%s snarmph. - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -*/ -]], os.date("%Y")) - -local f = io.open(outfile, "w") -f:write(str) -f:close() \ No newline at end of file