diff --git a/CMakeLists.txt b/CMakeLists.txt index 6dc1d03..54979f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,16 +1,19 @@ add_library(colla STATIC - socket.c - tracelog.c - http.c - strstream.c - str.c - coroutine.c - os.c - fs.c - file.c - dir.c - dirwatch.c - cthreads.c + 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) @@ -20,4 +23,6 @@ else() target_link_libraries(colla pthread) target_compile_options(colla PRIVATE -Wall -Wextra -Wpedantic) target_compile_definitions(colla PUBLIC _DEFAULT_SOURCE) -endif() \ No newline at end of file +endif() + +target_include_directories(colla PUBLIC src) \ No newline at end of file diff --git a/colla.h b/colla.h new file mode 100644 index 0000000..18a1c68 --- /dev/null +++ b/colla.h @@ -0,0 +1,4431 @@ +/* + 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); + +// == 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); +// 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 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 +#include +/* #include "str.h" */ +/* #include "collatypes.h" */ + +#ifdef _WIN32 + #include + /* #include "win32_slim.h" */ + isize getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp); + isize getline(char **line_ptr, size_t *n, FILE *stream); + #define stricmp _stricmp +#else + #ifndef _GNU_SOURCE + #define _GNU_SOURCE + #endif + #include + int stricmp(const char *a, const char *b); +#endif + +str_t getUserName(); + +#ifdef __cplusplus +} // extern "C" +#endif + +#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" */ + +/* == 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); +} + +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; +} + +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 (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" */ + +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] = 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" */ + +// == 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, -1)); + 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, -1)); + 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; +} + +// == 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 "os.h" */ + +#include +#include +#include + +#ifdef _WIN32 +#define _BUFSZ 128 + +#include + +// modified from netbsd source http://cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/pkgtools/libnbcompat/files/getdelim.c?only_with_tag=MAIN +isize getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp) { + char *ptr, *eptr; + + if(*buf == NULL || *bufsz == 0) { + *bufsz = _BUFSZ; + if((*buf = malloc(*bufsz)) == NULL) { + return -1; + } + } + + isize result = -1; + // usually fgetc locks every read, using windows-specific + // _lock_file and _unlock_file will be faster + _lock_file(fp); + + for(ptr = *buf, eptr = *buf + *bufsz;;) { + int c = _getc_nolock(fp); + if(c == -1) { + if(feof(fp)) { + isize diff = (isize)(ptr - *buf); + if(diff != 0) { + *ptr = '\0'; + result = diff; + break; + } + } + break; + } + *ptr++ = (char)c; + if(c == delimiter) { + *ptr = '\0'; + result = ptr - *buf; + break; + } + if((ptr + 2) >= eptr) { + char *nbuf; + size_t nbufsz = *bufsz * 2; + isize d = ptr - *buf; + if((nbuf = realloc(*buf, nbufsz)) == NULL) { + break; + } + *buf = nbuf; + *bufsz = nbufsz; + eptr = nbuf + nbufsz; + ptr = nbuf + d; + } + } + + _unlock_file(fp); + return result; +} + +// taken from netbsd source http://cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/pkgtools/libnbcompat/files/getline.c?only_with_tag=MAIN +isize getline(char **line_ptr, size_t *n, FILE *stream) { + return getdelim(line_ptr, n, '\n', stream); +} + +str_t getUserName() { + char buf[UNLEN + 1]; + DWORD sz = sizeof(buf); + BOOL res = GetUserNameA(buf, &sz); + if(!res) { + return strInit(); + } + return strFromBuf(buf, sz); +} + +#else + +#include +#include +#include +#include + +int stricmp(const char *a, const char *b) { + int result; + + if (a == b) { + return 0; + } + + while ((result = tolower(*a) - tolower(*b++)) == 0) { + if (*a++ == '\0') { + break; + } + } + + return result; +} + +str_t getUserName() { + return strFromStr(getlogin()); +} + +#endif + +/* #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; + } + +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, -1); + 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/coroutine.c b/deprecated/coroutine.c similarity index 92% rename from coroutine.c rename to deprecated/coroutine.c index 76b8a53..661b143 100644 --- a/coroutine.c +++ b/deprecated/coroutine.c @@ -8,4 +8,4 @@ coroutine_t coInit() { bool coIsDead(coroutine_t co) { return co.state == -1; -} \ No newline at end of file +} diff --git a/coroutine.h b/deprecated/coroutine.h similarity index 96% rename from coroutine.h rename to deprecated/coroutine.h index 92bf65c..ab5905b 100644 --- a/coroutine.h +++ b/deprecated/coroutine.h @@ -130,4 +130,4 @@ __INC_COUNTER; #ifdef __cplusplus } // extern "C" -#endif \ No newline at end of file +#endif diff --git a/dirwatch.c b/deprecated/dirwatch.c similarity index 96% rename from dirwatch.c rename to deprecated/dirwatch.c index 93660a1..796dc38 100644 --- a/dirwatch.c +++ b/deprecated/dirwatch.c @@ -292,4 +292,4 @@ void stopWatchDir(dirwatch_t *ctx, bool immediately) { free(opts); } -#endif \ No newline at end of file +#endif diff --git a/dirwatch.h b/deprecated/dirwatch.h similarity index 95% rename from dirwatch.h rename to deprecated/dirwatch.h index f4feeb7..1926ef1 100644 --- a/dirwatch.h +++ b/deprecated/dirwatch.h @@ -64,4 +64,4 @@ void stopWatchDir(dirwatch_t *ctx, bool immediately); #ifdef __cplusplus } // extern "C" -#endif \ No newline at end of file +#endif diff --git a/fs.c b/deprecated/fs.c similarity index 90% rename from fs.c rename to deprecated/fs.c index 5f6c075..880536f 100644 --- a/fs.c +++ b/deprecated/fs.c @@ -12,7 +12,7 @@ #include -static int _modeToType(unsigned int mode) { +static fsmode_t _modeToType(unsigned int mode) { switch(mode & _S_IFMT) { case _S_IFDIR: return FS_MODE_DIR; case _S_IFCHR: return FS_MODE_CHARACTER_DEVICE; @@ -79,7 +79,7 @@ bool fsIsDir(const char *path) { #else #include -static int _modeToType(unsigned int mode) { +static fsmode_t _modeToType(unsigned int mode) { switch(mode & __S_IFMT) { case __S_IFDIR: return FS_MODE_DIR; case __S_IFCHR: return FS_MODE_CHARACTER_DEVICE; @@ -90,13 +90,13 @@ static int _modeToType(unsigned int mode) { } fs_stat_t fsStat(file_t fp) { - int fd = fileno((FILE*)fp.handle); + int fd = fileno((FILE*)fp); struct stat statbuf; int res = fstat(fd, &statbuf); if(res == 0) { return (fs_stat_t) { .type = _modeToType(statbuf.st_mode), - .size = statbuf.st_size, + .size = (uint64_t)statbuf.st_size, .last_access = statbuf.st_atime, .last_modif = statbuf.st_mtime }; @@ -131,4 +131,4 @@ bool fsIsDir(const char *path) { return stat(path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode); } -#endif \ No newline at end of file +#endif diff --git a/fs.h b/deprecated/fs.h similarity index 86% rename from fs.h rename to deprecated/fs.h index 52d74d1..e58c403 100644 --- a/fs.h +++ b/deprecated/fs.h @@ -9,16 +9,16 @@ extern "C" { #include "file.h" -enum { +typedef enum { FS_MODE_FILE, FS_MODE_DIR, FS_MODE_CHARACTER_DEVICE, FS_MODE_FIFO, FS_MODE_UKNOWN, -}; +} fsmode_t; typedef struct { - int type; + fsmode_t type; uint64_t size; int64_t last_access; int64_t last_modif; @@ -41,4 +41,4 @@ bool fsIsDir(const char *path); #ifdef __cplusplus } // extern "C" -#endif \ No newline at end of file +#endif diff --git a/file.c b/file.c deleted file mode 100644 index cadb8b3..0000000 --- a/file.c +++ /dev/null @@ -1,237 +0,0 @@ -#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(int 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, int mode) { - return (file_t) { - .handle = CreateFileA(fname, - _toWin32Access(mode), - 0, - NULL, - _toWin32Creation(mode), - FILE_ATTRIBUTE_NORMAL, - NULL) - }; -} - -void fileClose(file_t *ctx) { - if(ctx->handle) { - CloseHandle((HANDLE)ctx->handle); - ctx->handle = NULL; - } -} - -bool fileIsValid(file_t *ctx) { - return ctx->handle != INVALID_HANDLE_VALUE; -} - -bool filePutc(file_t *ctx, char c) { - return fileWrite(ctx, &c, 1) == 1; -} - -bool filePuts(file_t *ctx, const char *str) { - size_t 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; -} - -size_t fileRead(file_t *ctx, void *buf, size_t len) { - DWORD bytes_read = 0; - BOOL result = ReadFile((HANDLE)ctx->handle, buf, (DWORD)len, &bytes_read, NULL); - return result == TRUE ? (size_t)bytes_read : 0; -} - -size_t fileWrite(file_t *ctx, const void *buf, size_t len) { - DWORD bytes_read = 0; - BOOL result = WriteFile((HANDLE)ctx->handle, buf, (DWORD)len, &bytes_read, NULL); - return result == TRUE ? (size_t)bytes_read : 0; -} - -bool fileSeekEnd(file_t *ctx) { - return SetFilePointerEx((HANDLE)ctx->handle, (LARGE_INTEGER){0}, NULL, FILE_END) == TRUE; -} - -void fileRewind(file_t *ctx) { - SetFilePointerEx((HANDLE)ctx->handle, (LARGE_INTEGER){0}, NULL, FILE_BEGIN); -} - -uint64_t fileTell(file_t *ctx) { - LARGE_INTEGER tell; - BOOL result = SetFilePointerEx((HANDLE)ctx->handle, (LARGE_INTEGER){0}, &tell, FILE_CURRENT); - return result == TRUE ? (uint64_t)tell.QuadPart : 0; -} - -#else -#include -#include -#include -#include -#include - -const char *_toStdioMode(int 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, int mode) { - return (file_t) { - .handle = (void*) fopen(fname, _toStdioMode(mode)), - }; -} - -void fileClose(file_t *ctx) { - if(ctx->handle) { - fclose((FILE*)ctx->handle); - ctx->handle = NULL; - } -} - -bool fileIsValid(file_t *ctx) { - return ctx->handle != NULL; -} - -bool filePutc(file_t *ctx, char c) { - return fputc(c, (FILE*)ctx->handle) == c; -} - -bool filePuts(file_t *ctx, const char *str) { - return fputs(str, (FILE*)ctx->handle) != 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; -} - -size_t fileRead(file_t *ctx, void *buf, size_t len) { - return fread(buf, 1, len, (FILE*)ctx->handle); -} - -size_t fileWrite(file_t *ctx, const void *buf, size_t len) { - return fwrite(buf, 1, len, (FILE*)ctx->handle); -} - -bool fileSeekEnd(file_t *ctx) { - return fseek((FILE*)ctx->handle, 0, SEEK_END) == 0; -} - -void fileRewind(file_t *ctx) { - rewind((FILE*)ctx->handle); -} - -uint64_t fileTell(file_t *ctx) { - return (uint64_t)ftell((FILE*)ctx->handle); -} -#endif - -static fread_buf_t _readWholeInternal(file_t *ctx, bool null_terminated) { - fread_buf_t contents = {0}; - uint64_t fsize = 0; - - if(!fileSeekEnd(ctx)) { - err("file: couldn't read until end"); - goto failed; - } - - fsize = fileTell(ctx); - fileRewind(ctx); - - contents.len = fsize; - contents.buf = malloc(fsize + null_terminated); - if(!contents.buf) { - err("file: couldn't allocate buffer"); - goto failed; - } - - size_t 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; - } - - if(null_terminated) { - contents.buf[contents.len] = '\0'; - } - -failed: - return contents; -failed_free: - free(contents.buf); - return contents; -} - -fread_buf_t fileReadWhole(const char *fname) { - file_t fp = fileOpen(fname, FILE_READ); - fread_buf_t contents = fileReadWholeFP(&fp); - fileClose(&fp); - return contents; -} - -fread_buf_t fileReadWholeFP(file_t *ctx) { - return _readWholeInternal(ctx, false); -} - -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 = fileReadWholeFPText(&fp); - fileClose(&fp); - return contents; -} - -str_t fileReadWholeFPText(file_t *ctx) { - fread_buf_t contents = _readWholeInternal(ctx, true); - return (str_t) { - .buf = contents.buf, - .len = contents.len - }; -} \ No newline at end of file diff --git a/file.h b/file.h deleted file mode 100644 index 8ffd885..0000000 --- a/file.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include "str.h" - -enum { - FILE_READ = 1 << 0, - FILE_WRITE = 1 << 1, - FILE_CLEAR = 1 << 2, - FILE_BOTH = 1 << 3 -}; - -typedef struct { - void *handle; -} file_t; - -typedef struct { - char *buf; - size_t len; -} fread_buf_t; - -bool fileExists(const char *fname); - -file_t fileOpen(const char *fname, int 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); - -size_t fileRead(file_t *ctx, void *buf, size_t len); -size_t fileWrite(file_t *ctx, const void *buf, size_t len); - -bool fileSeekEnd(file_t *ctx); -void fileRewind(file_t *ctx); - -uint64_t fileTell(file_t *ctx); - -fread_buf_t fileReadWhole(const char *fname); -fread_buf_t fileReadWholeFP(file_t *ctx); - -str_t fileReadWholeText(const char *fname); -str_t fileReadWholeFPText(file_t *ctx); - -#ifdef __cplusplus -} // extern "C" -#endif \ No newline at end of file diff --git a/gen.lua b/gen.lua new file mode 100644 index 0000000..d76125a --- /dev/null +++ b/gen.lua @@ -0,0 +1,106 @@ +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 .. "os.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 .. "os.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 diff --git a/src/collatypes.h b/src/collatypes.h new file mode 100644 index 0000000..f8d5401 --- /dev/null +++ b/src/collatypes.h @@ -0,0 +1,21 @@ +#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; diff --git a/cthreads.c b/src/cthreads.c similarity index 95% rename from cthreads.c rename to src/cthreads.c index 8edd97c..f680421 100644 --- a/cthreads.c +++ b/src/cthreads.c @@ -195,4 +195,4 @@ bool mtxUnlock(cmutex_t ctx) { return pthread_mutex_unlock((pthread_mutex_t *)ctx) == 0; } -#endif \ No newline at end of file +#endif diff --git a/cthreads.h b/src/cthreads.h similarity index 94% rename from cthreads.h rename to src/cthreads.h index dbab431..52b187c 100644 --- a/cthreads.h +++ b/src/cthreads.h @@ -4,8 +4,8 @@ extern "C" { #endif -#include #include +#include // == THREAD =========================================== @@ -64,4 +64,4 @@ struct lock_t { #ifdef __cplusplus } // extern "C" -#endif \ No newline at end of file +#endif diff --git a/dir.c b/src/dir.c similarity index 82% rename from dir.c rename to src/dir.c index d1790d3..deaab1d 100644 --- a/dir.c +++ b/src/dir.c @@ -55,8 +55,8 @@ dir_t dirOpen(const char *path) { str_ostream_t out = ostrInitLen(n + 3); n = GetFullPathName(path, n, out.buf, NULL); assert(n > 0); - out.size += n; - switch(ostrBack(&out)) { + out.len += n; + switch(ostrBack(out)) { case '\\': case '/': case ':': @@ -70,12 +70,12 @@ dir_t dirOpen(const char *path) { _dir_internal_t *dir = malloc(sizeof(_dir_internal_t)); if(dir) { - wchar_t *wpath = strToWCHAR(ostrAsStr(&out)); + wchar_t *wpath = strToWCHAR(ostrAsStr(out)); assert(wpath); *dir = _getFirst(wpath); free(wpath); } - ostrFree(&out); + ostrFree(out); return dir; } @@ -91,17 +91,23 @@ bool dirValid(dir_t ctx) { dir_entry_t *dirNext(dir_t ctx) { _dir_internal_t *dir = (_dir_internal_t*)ctx; - strFree(&dir->cur.name); + 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 @@ -118,7 +124,7 @@ typedef struct { } _dir_internal_t; dir_t dirOpen(const char *path) { - _dir_internal_t *ctx = calloc(1, sizeof(_dir_internal_t)); + _dir_internal_t *ctx = (_dir_internal_t *)calloc(1, sizeof(_dir_internal_t)); if(ctx) ctx->dir = opendir(path); return ctx; } @@ -139,7 +145,7 @@ bool dirValid(dir_t ctx) { dir_entry_t *dirNext(dir_t ctx) { if(!ctx) return NULL; _dir_internal_t *in = (_dir_internal_t *)ctx; - strFree(&in->next.name); + strFree(in->next.name); struct dirent *dp = readdir(in->dir); if(!dp) return NULL; @@ -149,8 +155,12 @@ dir_entry_t *dirNext(dir_t ctx) { default: in->next.type = FS_TYPE_UNKNOWN; break; } - in->next.name = strInitStr(dp->d_name); + in->next.name = strFromStr(dp->d_name); return &in->next; } -#endif \ No newline at end of file +void dirCreate(const char *path) { + mkdir(path, 0700); +} + +#endif diff --git a/dir.h b/src/dir.h similarity index 74% rename from dir.h rename to src/dir.h index 4d91bae..d3011ed 100644 --- a/dir.h +++ b/src/dir.h @@ -8,16 +8,16 @@ extern "C" { typedef void *dir_t; -typedef struct { - int type; - str_t name; -} dir_entry_t; - -enum { +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); @@ -26,6 +26,8 @@ bool dirValid(dir_t ctx); dir_entry_t *dirNext(dir_t ctx); +void dirCreate(const char *path); + #ifdef __cplusplus } // extern "C" -#endif \ No newline at end of file +#endif diff --git a/src/file.c b/src/file.c new file mode 100644 index 0000000..ccfe62a --- /dev/null +++ b/src/file.c @@ -0,0 +1,292 @@ +#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; + } + +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 }); +} diff --git a/src/file.h b/src/file.h new file mode 100644 index 0000000..98e0497 --- /dev/null +++ b/src/file.h @@ -0,0 +1,59 @@ +#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 diff --git a/src/hashmap.c b/src/hashmap.c new file mode 100644 index 0000000..bf47e1b --- /dev/null +++ b/src/hashmap.c @@ -0,0 +1,130 @@ +#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)); +} diff --git a/src/hashmap.h b/src/hashmap.h new file mode 100644 index 0000000..1efdd37 --- /dev/null +++ b/src/hashmap.h @@ -0,0 +1,49 @@ +#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); diff --git a/http.c b/src/http.c similarity index 65% rename from http.c rename to src/http.c index a4809ce..3fb5f06 100644 --- a/http.c +++ b/src/http.c @@ -7,41 +7,38 @@ #include "os.h" #include "tracelog.h" -#define T http_field_t -#define VEC_SHORT_NAME field_vec -#define VEC_DISABLE_ERASE_WHEN -#define VEC_NO_DECLARATION #include "vec.h" // == INTERNAL ================================================================ -static void _setField(field_vec_t *fields, const char *key, const char *value) { - // search if the field already exists - for(size_t i = 0; i < fields->size; ++i) { - if(stricmp(fields->buf[i].key, key) == 0) { - // replace value - char **curval = &fields->buf[i].value; - size_t cur = strlen(*curval); - size_t new = strlen(value); - if(new > cur) { - *curval = realloc(*curval, new + 1); +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, new); - (*curval)[new] = '\0'; + memcpy(*curval, value, newlen); + (*curval)[newlen] = '\0'; return; } } + // otherwise, add it to the list http_field_t field; - size_t klen = strlen(key); - size_t vlen = strlen(value); - field.key = malloc(klen + 1); - field.value = malloc(vlen + 1); + 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'; - field_vecPush(fields, field); + vecAppend(*fields_vec, field); } // == HTTP VERSION ============================================================ @@ -55,25 +52,25 @@ int httpVerNumber(http_version_t ver) { http_request_t reqInit() { http_request_t req; memset(&req, 0, sizeof(req)); - reqSetUri(&req, "/"); + reqSetUri(&req, strvInit("/")); req.version = (http_version_t){1, 1}; return req; } void reqFree(http_request_t *ctx) { - for(size_t i = 0; i < ctx->fields.size; ++i) { - free(ctx->fields.buf[i].key); - free(ctx->fields.buf[i].value); + for (uint32 i = 0; i < vecLen(ctx->fields); ++i) { + free(ctx->fields[i].key); + free(ctx->fields[i].value); } - field_vecFree(&ctx->fields); + vecFree(ctx->fields); free(ctx->uri); free(ctx->body); memset(ctx, 0, sizeof(http_request_t)); } bool reqHasField(http_request_t *ctx, const char *key) { - for(size_t i = 0; i < ctx->fields.size; ++i) { - if(stricmp(ctx->fields.buf[i].key, key) == 0) { + for(uint32 i = 0; i < vecLen(ctx->fields); ++i) { + if(stricmp(ctx->fields[i].key, key) == 0) { return true; } } @@ -84,20 +81,17 @@ void reqSetField(http_request_t *ctx, const char *key, const char *value) { _setField(&ctx->fields, key, value); } -void reqSetUri(http_request_t *ctx, const char *uri) { - if (uri == NULL) return; - size_t len = strlen(uri); - if(uri[0] != '/') { - len += 1; - ctx->uri = realloc(ctx->uri, len + 1); +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, len); - ctx->uri[len] = '\0'; + memcpy(ctx->uri + 1, uri.buf, uri.len); + ctx->uri[uri.len] = '\0'; } else { - ctx->uri = realloc(ctx->uri, len + 1); - memcpy(ctx->uri, uri, len); - ctx->uri[len] = '\0'; + ctx->uri = strvCopy(uri).buf; } } @@ -118,8 +112,8 @@ str_ostream_t reqPrepare(http_request_t *ctx) { method, ctx->uri, ctx->version.major, ctx->version.minor ); - for(size_t i = 0; i < ctx->fields.size; ++i) { - ostrPrintf(&out, "%s: %s\r\n", ctx->fields.buf[i].key, ctx->fields.buf[i].value); + 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")); @@ -133,7 +127,7 @@ error: str_t reqString(http_request_t *ctx) { str_ostream_t out = reqPrepare(ctx); - return ostrMove(&out); + return ostrAsStr(out); } // == HTTP RESPONSE =========================================================== @@ -143,18 +137,18 @@ http_response_t resInit() { } void resFree(http_response_t *ctx) { - for(size_t i = 0; i < ctx->fields.size; ++i) { - free(ctx->fields.buf[i].key); - free(ctx->fields.buf[i].value); + for(uint32 i = 0; i < vecLen(ctx->fields); ++i) { + free(ctx->fields[i].key); + free(ctx->fields[i].value); } - field_vecFree(&ctx->fields); - strFree(&ctx->body); + vecFree(ctx->fields); + vecFree(ctx->body); memset(ctx, 0, sizeof(http_response_t)); } bool resHasField(http_response_t *ctx, const char *key) { - for(size_t i = 0; i < ctx->fields.size; ++i) { - if(stricmp(ctx->fields.buf[i].key, key) == 0) { + for(uint32 i = 0; i < vecLen(ctx->fields); ++i) { + if(stricmp(ctx->fields[i].key, key) == 0) { return true; } } @@ -162,9 +156,9 @@ bool resHasField(http_response_t *ctx, const char *key) { } const char *resGetField(http_response_t *ctx, const char *field) { - for(size_t i = 0; i < ctx->fields.size; ++i) { - if(stricmp(ctx->fields.buf[i].key, field) == 0) { - return ctx->fields.buf[i].value; + for(uint32 i = 0; i < vecLen(ctx->fields); ++i) { + if(stricmp(ctx->fields[i].key, field) == 0) { + return ctx->fields[i].value; } } return NULL; @@ -183,7 +177,7 @@ void resParse(http_response_t *ctx, const char *data) { istrGetu8(&in, &ctx->version.major); istrSkip(&in, 1); // skip . istrGetu8(&in, &ctx->version.minor); - istrGeti32(&in, &ctx->status_code); + istrGeti32(&in, (int32*)&ctx->status_code); istrIgnore(&in, '\n'); istrSkip(&in, 1); // skip \n @@ -193,8 +187,9 @@ void resParse(http_response_t *ctx, const char *data) { 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); - strFree(&ctx->body); - ctx->body = strvCopy(body); + vecClear(ctx->body); + vecReserve(ctx->body, body.len); + memcpy(ctx->body, body.buf, body.len); } else { fatal("chunked encoding not implemented yet"); @@ -207,10 +202,10 @@ void resParseFields(http_response_t *ctx, str_istream_t *in) { do { line = istrGetview(in, '\r'); - size_t pos = strvFind(line, ':', 0); + usize pos = strvFind(line, ':', 0); if(pos != STRV_NOT_FOUND) { - strview_t key = strvSubstr(line, 0, pos); - strview_t value = strvSubstr(line, pos + 2, SIZE_MAX); + strview_t key = strvSub(line, 0, pos); + strview_t value = strvSub(line, pos + 2, SIZE_MAX); char *key_str = NULL; char *value_str = NULL; @@ -237,29 +232,28 @@ http_client_t hcliInit() { } void hcliFree(http_client_t *ctx) { - strFree(&ctx->host_name); + strFree(ctx->host_name); memset(ctx, 0, sizeof(http_client_t)); } -void hcliSetHost(http_client_t *ctx, const char *hostname) { - strview_t hostview = strvInit(hostname); +void hcliSetHost(http_client_t *ctx, strview_t hostname) { // if the hostname starts with http:// (case insensitive) - if(strvICompare(strvSubstr(hostview, 0, 7), strvInit("http://")) == 0) { - ctx->host_name = strvCopy(strvSubstr(hostview, 7, SIZE_MAX)); + if(strvICompare(strvSub(hostname, 0, 7), strvInit("http://")) == 0) { + ctx->host_name = strvCopy(strvSub(hostname, 7, SIZE_MAX)); } - else if(strvICompare(strvSubstr(hostview, 0, 8), strvInit("https://")) == 0) { + 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(hostview); + ctx->host_name = strvCopy(hostname); } } http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *req) { - if (strBack(&ctx->host_name) == '/') { - strPop(&ctx->host_name); + 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); @@ -269,7 +263,7 @@ http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *req) { str_ostream_t out = ostrInitLen(20); ostrAppendu64(&out, strlen(req->body)); reqSetField(req, "Content-Length", out.buf); - ostrFree(&out); + ostrFree(out); } else { reqSetField(req, "Content-Length", "0"); @@ -321,9 +315,9 @@ http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *req) { } while(read != 0); // if the data received is not null terminated - if(*(received.buf + received.size) != '\0') { + if(*(received.buf + received.len) != '\0') { ostrPutc(&received, '\0'); - received.size--; + received.len--; } resParse(&res, received.buf); @@ -341,12 +335,12 @@ error: err("couldn't clean up sockets %s", skGetErrorString()); } skopen_error: - strFree(&req_str); - ostrFree(&received); + strFree(req_str); + ostrFree(received); return res; } -http_response_t httpGet(const char *hostname, const char *uri) { +http_response_t httpGet(strview_t hostname, strview_t uri) { http_request_t request = reqInit(); request.method = REQ_GET; reqSetUri(&request, uri); @@ -362,3 +356,17 @@ http_response_t httpGet(const char *hostname, const char *uri) { 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; +} diff --git a/http.h b/src/http.h similarity index 77% rename from http.h rename to src/http.h index 7c70deb..9e9f327 100644 --- a/http.h +++ b/src/http.h @@ -4,23 +4,23 @@ extern "C" { #endif -#include #include #include +#include "collatypes.h" #include "str.h" #include "strstream.h" #include "socket.h" -enum { +typedef enum { REQ_GET, REQ_POST, REQ_HEAD, REQ_PUT, REQ_DELETE -}; +} reqtype_t; -enum { +typedef enum { // 2xx: success STATUS_OK = 200, STATUS_CREATED = 201, @@ -49,11 +49,11 @@ enum { STATUS_SERVICE_NOT_AVAILABLE = 503, STATUS_GATEWAY_TIMEOUT = 504, STATUS_VERSION_NOT_SUPPORTED = 505, -}; +} resstatus_t; typedef struct { - uint8_t major; - uint8_t minor; + 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) @@ -64,18 +64,14 @@ typedef struct { char *value; } http_field_t; -#define T http_field_t -#define VEC_SHORT_NAME field_vec -#define VEC_DISABLE_ERASE_WHEN -#define VEC_NO_IMPLEMENTATION #include "vec.h" // == HTTP REQUEST ============================================================ typedef struct { - int method; + reqtype_t method; http_version_t version; - field_vec_t fields; + vec(http_field_t) fields; char *uri; char *body; } http_request_t; @@ -86,7 +82,7 @@ void reqFree(http_request_t *ctx); bool reqHasField(http_request_t *ctx, const char *key); void reqSetField(http_request_t *ctx, const char *key, const char *value); -void reqSetUri(http_request_t *ctx, const char *uri); +void reqSetUri(http_request_t *ctx, strview_t uri); str_ostream_t reqPrepare(http_request_t *ctx); str_t reqString(http_request_t *ctx); @@ -94,10 +90,10 @@ str_t reqString(http_request_t *ctx); // == HTTP RESPONSE =========================================================== typedef struct { - int status_code; - field_vec_t fields; + resstatus_t status_code; + vec(http_field_t) fields; http_version_t version; - str_t body; + vec(uint8) body; } http_response_t; http_response_t resInit(void); @@ -113,20 +109,29 @@ void resParseFields(http_response_t *ctx, str_istream_t *in); typedef struct { str_t host_name; - uint16_t port; + 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, const char *hostname); +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(const char *hostname, const char *uri); +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 \ No newline at end of file +#endif diff --git a/src/ini.c b/src/ini.c new file mode 100644 index 0000000..b679df9 --- /dev/null +++ b/src/ini.c @@ -0,0 +1,323 @@ +#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; +} + +// == 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; + } +} diff --git a/src/ini.h b/src/ini.h new file mode 100644 index 0000000..687d446 --- /dev/null +++ b/src/ini.h @@ -0,0 +1,86 @@ +#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); + +// == 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 diff --git a/os.c b/src/os.c similarity index 72% rename from os.c rename to src/os.c index 64506d3..87207be 100644 --- a/os.c +++ b/src/os.c @@ -10,7 +10,7 @@ #include // modified from netbsd source http://cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/pkgtools/libnbcompat/files/getdelim.c?only_with_tag=MAIN -ssize_t getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp) { +isize getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp) { char *ptr, *eptr; if(*buf == NULL || *bufsz == 0) { @@ -20,7 +20,7 @@ ssize_t getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp) { } } - ssize_t result = -1; + isize result = -1; // usually fgetc locks every read, using windows-specific // _lock_file and _unlock_file will be faster _lock_file(fp); @@ -29,7 +29,7 @@ ssize_t getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp) { int c = _getc_nolock(fp); if(c == -1) { if(feof(fp)) { - ssize_t diff = (ssize_t)(ptr - *buf); + isize diff = (isize)(ptr - *buf); if(diff != 0) { *ptr = '\0'; result = diff; @@ -47,7 +47,7 @@ ssize_t getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp) { if((ptr + 2) >= eptr) { char *nbuf; size_t nbufsz = *bufsz * 2; - ssize_t d = ptr - *buf; + isize d = ptr - *buf; if((nbuf = realloc(*buf, nbufsz)) == NULL) { break; } @@ -63,7 +63,7 @@ ssize_t getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp) { } // taken from netbsd source http://cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/pkgtools/libnbcompat/files/getline.c?only_with_tag=MAIN -ssize_t getline(char **line_ptr, size_t *n, FILE *stream) { +isize getline(char **line_ptr, size_t *n, FILE *stream) { return getdelim(line_ptr, n, '\n', stream); } @@ -74,7 +74,7 @@ str_t getUserName() { if(!res) { return strInit(); } - return strInitBuf(buf, sz); + return strFromBuf(buf, sz); } #else @@ -82,14 +82,26 @@ str_t getUserName() { #include #include #include +#include -str_t getUserName() { - char buf[255]; - int res = getlogin_r(buf, sizeof(buf)); - if(res) { - return strInit(); +int stricmp(const char *a, const char *b) { + int result; + + if (a == b) { + return 0; } - return strInitStr(buf); + + while ((result = tolower(*a) - tolower(*b++)) == 0) { + if (*a++ == '\0') { + break; + } + } + + return result; } -#endif \ No newline at end of file +str_t getUserName() { + return strFromStr(getlogin()); +} + +#endif diff --git a/os.h b/src/os.h similarity index 57% rename from os.h rename to src/os.h index ddbbd11..dee2347 100644 --- a/os.h +++ b/src/os.h @@ -7,25 +7,24 @@ extern "C" { #include #include #include "str.h" +#include "collatypes.h" #ifdef _WIN32 #include #include "win32_slim.h" - typedef SSIZE_T ssize_t; - ssize_t getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp); - ssize_t getline(char **line_ptr, size_t *n, FILE *stream); + isize getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp); + isize getline(char **line_ptr, size_t *n, FILE *stream); #define stricmp _stricmp #else - #define stricmp strcasecmp #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include - #include // ssize_t + int stricmp(const char *a, const char *b); #endif str_t getUserName(); #ifdef __cplusplus } // extern "C" -#endif \ No newline at end of file +#endif diff --git a/socket.c b/src/socket.c similarity index 96% rename from socket.c rename to src/socket.c index 91ebf2c..02727a7 100644 --- a/socket.c +++ b/src/socket.c @@ -48,7 +48,7 @@ bool skCleanup() { return SOCK_CALL(skCleanup()); } -socket_t skOpen(skType type) { +socket_t skOpen(sktype_t type) { int sock_type = 0; switch(type) { @@ -333,4 +333,4 @@ static int _posix_skGetError() { static const char *_posix_skGetErrorString() { return strerror(errno); } -#endif \ No newline at end of file +#endif diff --git a/socket.h b/src/socket.h similarity index 95% rename from socket.h rename to src/socket.h index f446070..1256ed0 100644 --- a/socket.h +++ b/src/socket.h @@ -36,7 +36,7 @@ typedef struct sockaddr_in sk_addrin_t; typedef enum { SOCK_TCP, SOCK_UDP, -} skType; +} sktype_t; // == RAW SOCKETS ========================================== @@ -46,7 +46,7 @@ bool skInit(void); bool skCleanup(void); // Opens a socket, check socket_t with skValid -socket_t skOpen(skType type); +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 @@ -114,4 +114,4 @@ typedef socket_t udpsock_t; #ifdef __cplusplus } // extern "C" -#endif \ No newline at end of file +#endif diff --git a/str.c b/src/str.c similarity index 62% rename from str.c rename to src/str.c index 18fdd3c..da33fca 100644 --- a/str.c +++ b/src/str.c @@ -8,6 +8,7 @@ #include #include "tracelog.h" +#include "strstream.h" #ifdef _WIN32 #include "win32_slim.h" @@ -28,30 +29,38 @@ str_t strInit(void) { }; } -str_t strInitStr(const char *cstr) { - return strInitBuf(cstr, strlen(cstr)); +str_t strFromStr(const char *cstr) { + return cstr ? strFromBuf(cstr, strlen(cstr)) : strInit(); } -str_t strInitView(strview_t view) { - return strInitBuf(view.buf, view.len); +str_t strFromView(strview_t view) { + return strFromBuf(view.buf, view.len); } -str_t strInitBuf(const char *buf, size_t len) { +str_t strFromBuf(const char *buf, usize len) { + if (!buf) return strInit(); str_t str; str.len = len; - str.buf = malloc(len + 1); + str.buf = (char *)malloc(len + 1); memcpy(str.buf, buf, len); str.buf[len] = '\0'; return str; } -void strFree(str_t *ctx) { - free(ctx->buf); - ctx->buf = NULL; - ctx->len = 0; +str_t 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); } -str_t strFromWCHAR(const wchar_t *src, size_t len) { +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 @@ -62,7 +71,7 @@ str_t strFromWCHAR(const wchar_t *src, size_t len) { NULL, 0, NULL, NULL ); - char *buf = malloc(result_len + 1); + char *buf = (char *)malloc(result_len + 1); if(buf) { WideCharToMultiByte( CP_ACP, 0, @@ -77,23 +86,23 @@ str_t strFromWCHAR(const wchar_t *src, size_t len) { .len = result_len }; #else - size_t actual_len = len * sizeof(wchar_t); + usize actual_len = len * sizeof(wchar_t); - size_t dest_len = len * 6; - char *dest = malloc(dest_len); + usize dest_len = len * 6; + char *dest = (char *)malloc(dest_len); iconv_t cd = iconv_open("UTF-8", "WCHAR_T"); assert(cd); - size_t dest_left = dest_len; + usize dest_left = dest_len; char *dest_temp = dest; char *src_temp = (char*)src; - size_t lost = iconv(cd, &src_temp, &actual_len, &dest_temp, &dest_left); - assert(lost != ((size_t)-1)); + usize lost = iconv(cd, &src_temp, &actual_len, &dest_temp, &dest_left); + assert(lost != ((usize)-1)); (void)lost; dest_len -= dest_left; - dest = realloc(dest, dest_len + 1); + dest = (char *)realloc(dest, dest_len + 1); dest[dest_len] = '\0'; iconv_close(cd); @@ -113,7 +122,7 @@ wchar_t *strToWCHAR(str_t ctx) { ctx.buf, (int)ctx.len, NULL, 0 ); - wchar_t *str = malloc(sizeof(wchar_t) * (len + 1)); + wchar_t *str = (wchar_t *)malloc(sizeof(wchar_t) * (len + 1)); if(!str) return NULL; len = MultiByteToWideChar( codepage, 0, @@ -123,21 +132,21 @@ wchar_t *strToWCHAR(str_t ctx) { str[len] = '\0'; return str; #else - size_t dest_len = ctx.len * sizeof(wchar_t); - char *dest = malloc(dest_len); + 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); - size_t dest_left = dest_len; + usize dest_left = dest_len; char *dest_temp = dest; char *src_temp = ctx.buf; - size_t lost = iconv(cd, &src_temp, &ctx.len, &dest_temp, &dest_left); - assert(lost != ((size_t)-1)); + usize lost = iconv(cd, &src_temp, &ctx.len, &dest_temp, &dest_left); + assert(lost != ((usize)-1)); (void)lost; dest_len -= dest_left; - dest = realloc(dest, dest_len + 1); + dest = (char *)realloc(dest, dest_len + 1); dest[dest_len] = '\0'; iconv_close(cd); @@ -146,83 +155,43 @@ wchar_t *strToWCHAR(str_t ctx) { #endif } +str_t strDup(str_t ctx) { + return strFromBuf(ctx.buf, ctx.len); +} + str_t strMove(str_t *ctx) { - str_t str = strInitBuf(ctx->buf, ctx->len); + str_t out = *ctx; ctx->buf = NULL; ctx->len = 0; - return str; + return out; } -str_t strDup(str_t ctx) { - return strInitBuf(ctx.buf, ctx.len); -} - -strview_t strGetView(str_t *ctx) { +strview_t strGetView(str_t ctx) { return (strview_t) { - .buf = ctx->buf, - .len = ctx->len + .buf = ctx.buf, + .len = ctx.len }; } -char *strBegin(str_t *ctx) { - return ctx->buf; +char *strBegin(str_t ctx) { + return ctx.buf; } -char *strEnd(str_t *ctx) { - return ctx->buf ? ctx->buf + ctx->len : NULL; +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'; +char strBack(str_t ctx) { + return ctx.buf ? ctx.buf[ctx.len - 1] : '\0'; } -bool strIsEmpty(str_t *ctx) { - return ctx->len == 0; -} - -void strAppend(str_t *ctx, const char *str) { - strAppendBuf(ctx, str, strlen(str)); -} - -void strAppendStr(str_t *ctx, str_t str) { - strAppendBuf(ctx, str.buf, str.len); -} - -void strAppendView(str_t *ctx, strview_t view) { - strAppendBuf(ctx, view.buf, view.len); -} - -void strAppendBuf(str_t *ctx, const char *buf, size_t len) { - size_t oldlen = ctx->len; - ctx->len += len; - ctx->buf = realloc(ctx->buf, ctx->len + 1); - memcpy(ctx->buf + oldlen, buf, len); - ctx->buf[ctx->len] = '\0'; -} - -void strAppendChars(str_t *ctx, char c, size_t count) { - size_t oldlen = ctx->len; - ctx->len += count; - ctx->buf = realloc(ctx->buf, ctx->len + 1); - memset(ctx->buf + oldlen, c, count); - ctx->buf[ctx->len] = '\0'; -} - -void strPush(str_t *ctx, char c) { - strAppendChars(ctx, c, 1); -} - -char strPop(str_t *ctx) { - char c = strBack(ctx); - ctx->buf = realloc(ctx->buf, ctx->len); - ctx->len -= 1; - ctx->buf[ctx->len] = '\0'; - return c; +bool strIsEmpty(str_t ctx) { + return ctx.len == 0; } void strSwap(str_t *ctx, str_t *other) { char *buf = other->buf; - size_t len = other->len; + usize len = other->len; other->buf = ctx->buf; other->len = ctx->len; ctx->buf = buf; @@ -230,30 +199,29 @@ void strSwap(str_t *ctx, str_t *other) { } void strReplace(str_t *ctx, char from, char to) { - for(size_t i = 0; i < ctx->len; ++i) { + for(usize i = 0; i < ctx->len; ++i) { if(ctx->buf[i] == from) { ctx->buf[i] = to; } } } -str_t strSubstr(str_t *ctx, size_t pos, size_t len) { +str_t strSubstr(str_t ctx, usize from, usize to) { if(strIsEmpty(ctx)) return strInit(); - if(len == SIZE_MAX || (pos + len) > ctx->len) len = ctx->len - pos; - return strInitBuf(ctx->buf + pos, len); + 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, size_t pos, size_t len) { +strview_t strSubview(str_t ctx, usize from, usize to) { if(strIsEmpty(ctx)) return strvInit(NULL); - if(len == SIZE_MAX || (pos + len) > ctx->len) len = ctx->len - pos; - return (strview_t) { - .buf = ctx->buf + pos, - .len = len - }; + 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(size_t i = 0; i < ctx->len; ++i) { + for(usize i = 0; i < ctx->len; ++i) { ctx->buf[i] = (char)tolower(ctx->buf[i]); } } @@ -265,7 +233,7 @@ str_t strToLower(str_t ctx) { } void strUpper(str_t *ctx) { - for(size_t i = 0; i < ctx->len; ++i) { + for(usize i = 0; i < ctx->len; ++i) { ctx->buf[i] = (char)toupper(ctx->buf[i]); } } @@ -286,7 +254,7 @@ strview_t strvInitStr(str_t str) { return strvInitLen(str.buf, str.len); } -strview_t strvInitLen(const char *buf, size_t size) { +strview_t strvInitLen(const char *buf, usize size) { return (strview_t) { .buf = buf, .len = size @@ -301,49 +269,77 @@ char strvBack(strview_t ctx) { return ctx.buf[ctx.len - 1]; } -const char *strvBegin(strview_t *ctx) { - return ctx->buf; +const char *strvBegin(strview_t ctx) { + return ctx.buf; } -const char *strvEnd(strview_t *ctx) { - return ctx->buf + ctx->len; +const char *strvEnd(strview_t ctx) { + return ctx.buf + ctx.len; } bool strvIsEmpty(strview_t ctx) { return ctx.len == 0; } -void strvRemovePrefix(strview_t *ctx, size_t n) { - ctx->buf += n; - ctx->len -= n; +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 + }; } -void strvRemoveSuffix(strview_t *ctx, size_t n) { - 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 strInitView(ctx); + return strFromView(ctx); } -str_t strvCopyN(strview_t ctx, size_t count, size_t from) { - size_t sz = ctx.len + 1 - from; +str_t strvCopyN(strview_t ctx, usize count, usize from) { + usize sz = ctx.len + 1 - from; count = min(count, sz); - return strInitBuf(ctx.buf + from, count); + return strFromBuf(ctx.buf + from, count); } -size_t strvCopyBuf(strview_t ctx, char *buf, size_t len, size_t from) { - size_t sz = ctx.len + 1 - from; +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 strvSubstr(strview_t ctx, size_t from, size_t len) { - if(from > ctx.len) from = ctx.len - len; - size_t sz = ctx.len - from; - return strvInitLen(ctx.buf + from, min(len, sz)); +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) { @@ -355,7 +351,7 @@ int strvCompare(strview_t ctx, strview_t other) { int strvICompare(strview_t ctx, strview_t other) { if(ctx.len < other.len) return -1; if(ctx.len > other.len) return 1; - for(size_t i = 0; i < ctx.len; ++i) { + 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; @@ -382,7 +378,7 @@ bool strvEndsWithView(strview_t ctx, strview_t view) { } bool strvContains(strview_t ctx, char c) { - for(size_t i = 0; i < ctx.len; ++i) { + for(usize i = 0; i < ctx.len; ++i) { if(ctx.buf[i] == c) return true; } return false; @@ -390,30 +386,30 @@ bool strvContains(strview_t ctx, char c) { bool strvContainsView(strview_t ctx, strview_t view) { if(ctx.len < view.len) return false; - size_t end = ctx.len - view.len; - for(size_t i = 0; i < end; ++i) { + 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; } -size_t strvFind(strview_t ctx, char c, size_t from) { - for(size_t i = from; i < ctx.len; ++i) { +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; } -size_t strvFindView(strview_t ctx, strview_t view, size_t from) { +usize strvFindView(strview_t ctx, strview_t view, usize from) { if(ctx.len < view.len) return SIZE_MAX; - size_t end = ctx.len - view.len; - for(size_t i = from; i < end; ++i) { + 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; } -size_t strvRFind(strview_t ctx, char c, size_t from) { +usize strvRFind(strview_t ctx, char c, usize from) { if(from >= ctx.len) { from = ctx.len - 1; } @@ -426,7 +422,7 @@ size_t strvRFind(strview_t ctx, char c, size_t from) { return SIZE_MAX; } -size_t strvRFindView(strview_t ctx, strview_t view, size_t from) { +usize strvRFindView(strview_t ctx, strview_t view, usize from) { from = min(from, ctx.len); if(view.len > ctx.len) { @@ -440,24 +436,24 @@ size_t strvRFindView(strview_t ctx, strview_t view, size_t from) { return SIZE_MAX; } -size_t strvFindFirstOf(strview_t ctx, strview_t view, size_t from) { +usize strvFindFirstOf(strview_t ctx, strview_t view, usize from) { if(ctx.len < view.len) return SIZE_MAX; - for(size_t i = from; i < ctx.len; ++i) { - for(size_t j = 0; j < view.len; ++j) { + 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; } -size_t strvFindLastOf(strview_t ctx, strview_t view, size_t from) { +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(size_t j = 0; j < view.len; ++j) { + for(usize j = 0; j < view.len; ++j) { if(*buf == view.buf[j]) return (buf - ctx.buf); } } @@ -465,16 +461,16 @@ size_t strvFindLastOf(strview_t ctx, strview_t view, size_t from) { return SIZE_MAX; } -size_t strvFindFirstNot(strview_t ctx, char c, size_t from) { - size_t end = ctx.len - 1; - for(size_t i = from; i < end; ++i) { +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; } -size_t strvFindFirstNotOf(strview_t ctx, strview_t view, size_t from) { - for(size_t i = from; i < ctx.len; ++i) { +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; } @@ -482,7 +478,7 @@ size_t strvFindFirstNotOf(strview_t ctx, strview_t view, size_t from) { return SIZE_MAX; } -size_t strvFindLastNot(strview_t ctx, char c, size_t from) { +usize strvFindLastNot(strview_t ctx, char c, usize from) { if(from >= ctx.len) { from = ctx.len - 1; } @@ -497,7 +493,7 @@ size_t strvFindLastNot(strview_t ctx, char c, size_t from) { return SIZE_MAX; } -size_t strvFindLastNotOf(strview_t ctx, strview_t view, size_t from) { +usize strvFindLastNotOf(strview_t ctx, strview_t view, usize from) { if(from >= ctx.len) { from = ctx.len - 1; } @@ -526,8 +522,8 @@ void strTest(void) { s = strInitStr("hello world"); printf("\"%s\" %zu\n", s.buf, s.len); strFree(&s); - uint8_t buf[] = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' }; - s = strInitBuf((char *)buf, sizeof(buf)); + 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); } @@ -569,7 +565,7 @@ void strTest(void) { printf("\"%s\" %zu\n", s.buf, s.len); strAppendView(&s, strvInit(", how is it ")); printf("\"%s\" %zu\n", s.buf, s.len); - uint8_t buf[] = { 'g', 'o', 'i', 'n', 'g' }; + uint8 buf[] = { 'g', 'o', 'i', 'n', 'g' }; strAppendBuf(&s, (char*)buf, sizeof(buf)); printf("\"%s\" %zu\n", s.buf, s.len); strAppendChars(&s, '?', 2); @@ -630,4 +626,4 @@ void strTest(void) { strFree(&s); } -#endif \ No newline at end of file +#endif diff --git a/src/str.h b/src/str.h new file mode 100644 index 0000000..c2e1a0a --- /dev/null +++ b/src/str.h @@ -0,0 +1,122 @@ +#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 diff --git a/strstream.c b/src/strstream.c similarity index 67% rename from strstream.c rename to src/strstream.c index 8639f52..aa3485c 100644 --- a/strstream.c +++ b/src/strstream.c @@ -5,6 +5,7 @@ #include #include #include +#include #include // HUGE_VALF #include "tracelog.h" @@ -14,7 +15,7 @@ str_istream_t istrInit(const char *str) { return istrInitLen(str, strlen(str)); } -str_istream_t istrInitLen(const char *str, size_t len) { +str_istream_t istrInitLen(const char *str, usize len) { str_istream_t res; res.start = res.cur = str; res.size = len; @@ -26,8 +27,8 @@ char istrGet(str_istream_t *ctx) { } void istrIgnore(str_istream_t *ctx, char delim) { - size_t position = ctx->cur - ctx->start; - size_t i; + usize position = ctx->cur - ctx->start; + usize i; for(i = position; i < ctx->size && *ctx->cur != delim; ++i, ++ctx->cur); @@ -37,8 +38,8 @@ char istrPeek(str_istream_t *ctx) { return *ctx->cur; } -void istrSkip(str_istream_t *ctx, size_t n) { - size_t remaining = ctx->size - (ctx->cur - ctx->start); +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; @@ -46,8 +47,14 @@ void istrSkip(str_istream_t *ctx, size_t n) { ctx->cur += n; } -void istrRead(str_istream_t *ctx, char *buf, size_t len) { - size_t remaining = ctx->size - (ctx->cur - ctx->start); +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; @@ -56,8 +63,8 @@ void istrRead(str_istream_t *ctx, char *buf, size_t len) { ctx->cur += len; } -size_t istrReadMax(str_istream_t *ctx, char *buf, size_t len) { - size_t remaining = ctx->size - (ctx->cur - ctx->start); +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; @@ -68,16 +75,20 @@ void istrRewind(str_istream_t *ctx) { ctx->cur = ctx->start; } -size_t istrTell(str_istream_t *ctx) { - return ctx->cur - ctx->start; +usize istrTell(str_istream_t ctx) { + return ctx.cur - ctx.start; } -bool istrIsFinished(str_istream_t *ctx) { - return ctx->cur == (ctx->start + ctx->size + 1); +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) { - size_t remaining = ctx->size - (ctx->cur - ctx->start); + usize remaining = ctx->size - (ctx->cur - ctx->start); if(strncmp(ctx->cur, "true", remaining) == 0) { *val = true; return true; @@ -89,9 +100,9 @@ bool istrGetbool(str_istream_t *ctx, bool *val) { return false; } -bool istrGetu8(str_istream_t *ctx, uint8_t *val) { +bool istrGetu8(str_istream_t *ctx, uint8 *val) { char *end = NULL; - *val = (uint8_t) strtoul(ctx->cur, &end, 0); + *val = (uint8) strtoul(ctx->cur, &end, 0); if(ctx->cur == end) { warn("istrGetu8: no valid conversion could be performed"); @@ -106,9 +117,9 @@ bool istrGetu8(str_istream_t *ctx, uint8_t *val) { return true; } -bool istrGetu16(str_istream_t *ctx, uint16_t *val) { +bool istrGetu16(str_istream_t *ctx, uint16 *val) { char *end = NULL; - *val = (uint16_t) strtoul(ctx->cur, &end, 0); + *val = (uint16) strtoul(ctx->cur, &end, 0); if(ctx->cur == end) { warn("istrGetu16: no valid conversion could be performed"); @@ -123,9 +134,9 @@ bool istrGetu16(str_istream_t *ctx, uint16_t *val) { return true; } -bool istrGetu32(str_istream_t *ctx, uint32_t *val) { +bool istrGetu32(str_istream_t *ctx, uint32 *val) { char *end = NULL; - *val = (uint32_t) strtoul(ctx->cur, &end, 0); + *val = (uint32) strtoul(ctx->cur, &end, 0); if(ctx->cur == end) { warn("istrGetu32: no valid conversion could be performed"); @@ -140,7 +151,7 @@ bool istrGetu32(str_istream_t *ctx, uint32_t *val) { return true; } -bool istrGetu64(str_istream_t *ctx, uint64_t *val) { +bool istrGetu64(str_istream_t *ctx, uint64 *val) { char *end = NULL; *val = strtoull(ctx->cur, &end, 0); @@ -157,9 +168,9 @@ bool istrGetu64(str_istream_t *ctx, uint64_t *val) { return true; } -bool istrGeti8(str_istream_t *ctx, int8_t *val) { +bool istrGeti8(str_istream_t *ctx, int8 *val) { char *end = NULL; - *val = (int8_t) strtol(ctx->cur, &end, 0); + *val = (int8) strtol(ctx->cur, &end, 0); if(ctx->cur == end) { warn("istrGeti8: no valid conversion could be performed"); @@ -174,9 +185,9 @@ bool istrGeti8(str_istream_t *ctx, int8_t *val) { return true; } -bool istrGeti16(str_istream_t *ctx, int16_t *val) { +bool istrGeti16(str_istream_t *ctx, int16 *val) { char *end = NULL; - *val = (int16_t) strtol(ctx->cur, &end, 0); + *val = (int16) strtol(ctx->cur, &end, 0); if(ctx->cur == end) { warn("istrGeti16: no valid conversion could be performed"); @@ -191,9 +202,9 @@ bool istrGeti16(str_istream_t *ctx, int16_t *val) { return true; } -bool istrGeti32(str_istream_t *ctx, int32_t *val) { +bool istrGeti32(str_istream_t *ctx, int32 *val) { char *end = NULL; - *val = (int32_t) strtol(ctx->cur, &end, 0); + *val = (int32) strtol(ctx->cur, &end, 0); if(ctx->cur == end) { warn("istrGeti32: no valid conversion could be performed"); @@ -208,7 +219,7 @@ bool istrGeti32(str_istream_t *ctx, int32_t *val) { return true; } -bool istrGeti64(str_istream_t *ctx, int64_t *val) { +bool istrGeti64(str_istream_t *ctx, int64 *val) { char *end = NULL; *val = strtoll(ctx->cur, &end, 0); @@ -259,7 +270,7 @@ bool istrGetdouble(str_istream_t *ctx, double *val) { return true; } -size_t istrGetstring(str_istream_t *ctx, char **val, char delim) { +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 @@ -267,15 +278,15 @@ size_t istrGetstring(str_istream_t *ctx, char **val, char delim) { *val = NULL; return 0; } - size_t len = ctx->cur - from; - *val = malloc(len + 1); + usize len = ctx->cur - from; + *val = (char *)malloc(len + 1); memcpy(*val, from, len); (*val)[len] = '\0'; return len; } -size_t istrGetstringBuf(str_istream_t *ctx, char *val, size_t len) { - size_t remaining = ctx->size - (ctx->cur - ctx->start); +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); @@ -287,86 +298,74 @@ size_t istrGetstringBuf(str_istream_t *ctx, char *val, size_t len) { strview_t istrGetview(str_istream_t *ctx, char delim) { const char *from = ctx->cur; istrIgnore(ctx, delim); - // if it didn't actually find it, it just reached the end of the string - if(*ctx->cur != delim) { - return strvInitLen(NULL, 0); - } - size_t len = ctx->cur - from; + usize len = ctx->cur - from; return strvInitLen(from, len); } -strview_t istrGetviewLen(str_istream_t *ctx, size_t off, size_t len) { - size_t remaining = ctx->size - (ctx->cur - ctx->start) - off; - if(len > remaining) len = remaining; - return strvInitLen(ctx->cur + off, len); +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, size_t needed) { - ctx->allocated = (ctx->allocated * 2) + needed; - ctx->buf = realloc(ctx->buf, ctx->allocated); +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(size_t initial_alloc) { +str_ostream_t ostrInitLen(usize initial_alloc) { str_ostream_t stream; - stream.buf = calloc(initial_alloc, 1); - stream.size = 0; - stream.allocated = initial_alloc; + stream.buf = (char *)calloc(initial_alloc, 1); + stream.len = 0; + stream.cap = initial_alloc; return stream; } -str_ostream_t ostrInitStr(const char *cstr, size_t len) { +str_ostream_t ostrInitStr(const char *cstr, usize len) { str_ostream_t stream; - stream.buf = malloc(len + 1); + stream.buf = (char *)malloc(len + 1); memcpy(stream.buf, cstr, len); - stream.size = len; - stream.allocated = len + 1; + stream.len = len; + stream.cap = len + 1; return stream; } -void ostrFree(str_ostream_t *ctx) { - free(ctx->buf); - ctx->buf = NULL; - ctx->size = 0; - ctx->allocated = 0; +void ostrFree(str_ostream_t ctx) { + free(ctx.buf); } void ostrClear(str_ostream_t *ctx) { - ctx->size = 0; + ctx->len = 0; } -str_t ostrMove(str_ostream_t *ctx) { - str_t str = ostrAsStr(ctx); - *ctx = (str_ostream_t){0}; - return str; +char ostrBack(str_ostream_t ctx) { + if(ctx.len == 0) return '\0'; + return ctx.buf[ctx.len - 1]; } -char ostrBack(str_ostream_t *ctx) { - if(ctx->size == 0) return '\0'; - return ctx->buf[ctx->size - 1]; -} - -str_t ostrAsStr(str_ostream_t *ctx) { +str_t ostrAsStr(str_ostream_t ctx) { return (str_t) { - .buf = ctx->buf, - .len = ctx->size + .buf = ctx.buf, + .len = ctx.len }; } -strview_t ostrAsView(str_ostream_t *ctx) { +strview_t ostrAsView(str_ostream_t ctx) { return (strview_t) { - .buf = ctx->buf, - .len = ctx->size + .buf = ctx.buf, + .len = ctx.len }; } void ostrReplace(str_ostream_t *ctx, char from, char to) { - for(size_t i = 0; i < ctx->size; ++i) { + for(usize i = 0; i < ctx->len; ++i) { if(ctx->buf[i] == from) { ctx->buf[i] = to; } @@ -376,56 +375,69 @@ void ostrReplace(str_ostream_t *ctx, char from, char 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 - char buf[1]; - va_list vtemp; - va_copy(vtemp, va); - int len = vsnprintf(buf, sizeof(buf), fmt, vtemp); + 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; } - size_t remaining = ctx->allocated - ctx->size; - if(remaining < (size_t)len) { + remaining = ctx->cap - ctx->len; + if(remaining < (usize)len) { _ostrRealloc(ctx, len + 1); - remaining = ctx->allocated - ctx->size; + remaining = ctx->cap - ctx->len; } // actual formatting here - len = vsnprintf(ctx->buf + ctx->size, remaining, fmt, va); + 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->size += len; + ctx->len += len; error: - va_end(va); + 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->size >= ctx->allocated) { + if(ctx->len >= ctx->cap) { _ostrRealloc(ctx, 1); } - ctx->buf[ctx->size++] = val; - ctx->buf[ctx->size] = '\0'; + ctx->buf[ctx->len++] = val; + ctx->buf[ctx->len] = '\0'; } -void ostrAppendu8(str_ostream_t *ctx, uint8_t 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) { @@ -435,7 +447,7 @@ void ostrAppendu8(str_ostream_t *ctx, uint8_t val) { ostrAppendview(ctx, strvInitLen(buf, len)); } -void ostrAppendu16(str_ostream_t *ctx, uint16_t 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) { @@ -445,7 +457,7 @@ void ostrAppendu16(str_ostream_t *ctx, uint16_t val) { ostrAppendview(ctx, strvInitLen(buf, len)); } -void ostrAppendu32(str_ostream_t *ctx, uint32_t 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) { @@ -455,7 +467,7 @@ void ostrAppendu32(str_ostream_t *ctx, uint32_t val) { ostrAppendview(ctx, strvInitLen(buf, len)); } -void ostrAppendu64(str_ostream_t *ctx, uint64_t val) { +void ostrAppendu64(str_ostream_t *ctx, uint64 val) { char buf[APPEND_BUF_LEN]; #if _WIN32 int len = snprintf(buf, sizeof(buf), "%llu", val); @@ -469,7 +481,7 @@ void ostrAppendu64(str_ostream_t *ctx, uint64_t val) { ostrAppendview(ctx, strvInitLen(buf, len)); } -void ostrAppendi8(str_ostream_t *ctx, int8_t val) { +void ostrAppendi8(str_ostream_t *ctx, int8 val) { char buf[APPEND_BUF_LEN]; int len = snprintf(buf, sizeof(buf), "%hhi", val); if(len <= 0) { @@ -479,7 +491,7 @@ void ostrAppendi8(str_ostream_t *ctx, int8_t val) { ostrAppendview(ctx, strvInitLen(buf, len)); } -void ostrAppendi16(str_ostream_t *ctx, int16_t val) { +void ostrAppendi16(str_ostream_t *ctx, int16 val) { char buf[APPEND_BUF_LEN]; int len = snprintf(buf, sizeof(buf), "%hi", val); if(len <= 0) { @@ -489,7 +501,7 @@ void ostrAppendi16(str_ostream_t *ctx, int16_t val) { ostrAppendview(ctx, strvInitLen(buf, len)); } -void ostrAppendi32(str_ostream_t *ctx, int32_t val) { +void ostrAppendi32(str_ostream_t *ctx, int32 val) { char buf[APPEND_BUF_LEN]; int len = snprintf(buf, sizeof(buf), "%i", val); if(len <= 0) { @@ -499,7 +511,7 @@ void ostrAppendi32(str_ostream_t *ctx, int32_t val) { ostrAppendview(ctx, strvInitLen(buf, len)); } -void ostrAppendi64(str_ostream_t *ctx, int64_t val) { +void ostrAppendi64(str_ostream_t *ctx, int64 val) { char buf[APPEND_BUF_LEN]; #if _WIN32 int len = snprintf(buf, sizeof(buf), "%lli", val); @@ -534,10 +546,10 @@ void ostrAppenddouble(str_ostream_t *ctx, double val) { } void ostrAppendview(str_ostream_t *ctx, strview_t view) { - if((ctx->allocated - ctx->size) <= view.len) { + if((ctx->cap - ctx->len) <= view.len) { _ostrRealloc(ctx, view.len + 1); } - memcpy(ctx->buf + ctx->size, view.buf, view.len); - ctx->size += view.len; - ctx->buf[ctx->size] = '\0'; -} \ No newline at end of file + memcpy(ctx->buf + ctx->len, view.buf, view.len); + ctx->len += view.len; + ctx->buf[ctx->len] = '\0'; +} diff --git a/src/strstream.h b/src/strstream.h new file mode 100644 index 0000000..63e91e9 --- /dev/null +++ b/src/strstream.h @@ -0,0 +1,110 @@ +#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); +// 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 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 diff --git a/tracelog.c b/src/tracelog.c similarity index 96% rename from tracelog.c rename to src/tracelog.c index 14e519c..6a97034 100644 --- a/tracelog.c +++ b/src/tracelog.c @@ -119,4 +119,4 @@ void traceLogVaList(int level, const char *fmt, va_list args) { void traceUseNewline(bool newline) { use_newline = newline; -} \ No newline at end of file +} diff --git a/tracelog.h b/src/tracelog.h similarity index 96% rename from tracelog.h rename to src/tracelog.h index 1628423..f7fa192 100644 --- a/tracelog.h +++ b/src/tracelog.h @@ -31,4 +31,4 @@ void traceUseNewline(bool use_newline); #ifdef __cplusplus } // extern "C" -#endif \ No newline at end of file +#endif diff --git a/src/utf8.c b/src/utf8.c new file mode 100644 index 0000000..4a18df1 --- /dev/null +++ b/src/utf8.c @@ -0,0 +1,172 @@ +#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; +} diff --git a/src/utf8.h b/src/utf8.h new file mode 100644 index 0000000..bef77a0 --- /dev/null +++ b/src/utf8.h @@ -0,0 +1,19 @@ +#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); diff --git a/src/vec.h b/src/vec.h new file mode 100644 index 0000000..0085a33 --- /dev/null +++ b/src/vec.h @@ -0,0 +1,69 @@ +#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 diff --git a/src/win32_slim.h b/src/win32_slim.h new file mode 100644 index 0000000..9991c7d --- /dev/null +++ b/src/win32_slim.h @@ -0,0 +1,15 @@ +#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/str.h b/str.h deleted file mode 100644 index 9ce04ee..0000000 --- a/str.h +++ /dev/null @@ -1,128 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include -#include -#include - -#define STRV_NOT_FOUND SIZE_MAX - -typedef struct str_t { - char *buf; - size_t len; -} str_t; - -typedef struct { - const char *buf; - size_t len; -} strview_t; - -// == STR_T ======================================================== - -str_t strInit(void); -str_t strInitStr(const char *cstr); -str_t strInitView(strview_t view); -str_t strInitBuf(const char *buf, size_t len); - -str_t strFromWCHAR(const wchar_t *src, size_t len); -wchar_t *strToWCHAR(str_t ctx); - -void strFree(str_t *ctx); - -str_t strMove(str_t *ctx); -str_t strDup(str_t ctx); - -strview_t strGetView(str_t *ctx); - -char *strBegin(str_t *ctx); -char *strEnd(str_t *ctx); - -char strBack(str_t *ctx); - -bool strIsEmpty(str_t *ctx); - -void strAppend(str_t *ctx, const char *str); -void strAppendStr(str_t *ctx, str_t str); -void strAppendView(str_t *ctx, strview_t view); -void strAppendBuf(str_t *ctx, const char *buf, size_t len); -void strAppendChars(str_t *ctx, char c, size_t count); - -void strPush(str_t *ctx, char c); -char strPop(str_t *ctx); - -void strSwap(str_t *ctx, str_t *other); - -void strReplace(str_t *ctx, char from, char to); - -// if len == SIZE_MAX, copies until end -str_t strSubstr(str_t *ctx, size_t pos, size_t len); -// if len == SIZE_MAX, returns until end -strview_t strSubview(str_t *ctx, size_t pos, size_t len); - -void strLower(str_t *ctx); -str_t strToLower(str_t ctx); - -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, size_t size); - -char strvFront(strview_t ctx); -char strvBack(strview_t ctx); -const char *strvBegin(strview_t *ctx); -const char *strvEnd(strview_t *ctx); -// move view forward by n characters -void strvRemovePrefix(strview_t *ctx, size_t n); -// move view backwards by n characters -void strvRemoveSuffix(strview_t *ctx, size_t n); - -bool strvIsEmpty(strview_t ctx); - -str_t strvCopy(strview_t ctx); -str_t strvCopyN(strview_t ctx, size_t count, size_t from); -size_t strvCopyBuf(strview_t ctx, char *buf, size_t len, size_t from); - -strview_t strvSubstr(strview_t ctx, size_t from, size_t len); -int strvCompare(strview_t ctx, strview_t other); -int strvICompare(strview_t ctx, strview_t other); - -bool strvStartsWith(strview_t ctx, char c); -bool strvStartsWithView(strview_t ctx, strview_t view); - -bool strvEndsWith(strview_t ctx, char c); -bool strvEndsWithView(strview_t ctx, strview_t view); - -bool strvContains(strview_t ctx, char c); -bool strvContainsView(strview_t ctx, strview_t view); - -size_t strvFind(strview_t ctx, char c, size_t from); -size_t strvFindView(strview_t ctx, strview_t view, size_t from); - -size_t strvRFind(strview_t ctx, char c, size_t from); -size_t strvRFindView(strview_t ctx, strview_t view, size_t from); - -// Finds the first occurrence of any of the characters of 'view' in this view -size_t strvFindFirstOf(strview_t ctx, strview_t view, size_t from); -size_t strvFindLastOf(strview_t ctx, strview_t view, size_t from); - -size_t strvFindFirstNot(strview_t ctx, char c, size_t from); -size_t strvFindFirstNotOf(strview_t ctx, strview_t view, size_t from); -size_t strvFindLastNot(strview_t ctx, char c, size_t from); -size_t strvFindLastNotOf(strview_t ctx, strview_t view, size_t from); - -#ifdef __cplusplus -} // extern "C" -#endif \ No newline at end of file diff --git a/strstream.h b/strstream.h deleted file mode 100644 index c4edb89..0000000 --- a/strstream.h +++ /dev/null @@ -1,104 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include - -#include "str.h" - -/* == INPUT STREAM ============================================ */ - -typedef struct { - const char *start; - const char *cur; - size_t size; -} str_istream_t; - -// initialize with null-terminated string -str_istream_t istrInit(const char *str); -str_istream_t istrInitLen(const char *str, size_t len); - -// get the current character and advance -char istrGet(str_istream_t *ctx); -// get the current character but don't advance -char istrPeek(str_istream_t *ctx); -// ignore characters until the delimiter -void istrIgnore(str_istream_t *ctx, char delim); -// skip n characters -void istrSkip(str_istream_t *ctx, size_t n); -// read len bytes into buffer, the buffer will not be null terminated -void istrRead(str_istream_t *ctx, char *buf, size_t len); -// read a maximum of len bytes into buffer, the buffer will not be null terminated -// returns the number of bytes read -size_t istrReadMax(str_istream_t *ctx, char *buf, size_t len); -// return to the beginning of the stream -void istrRewind(str_istream_t *ctx); -// return the number of bytes read from beginning of stream -size_t istrTell(str_istream_t *ctx); -// return true if the stream doesn't have any new bytes to read -bool istrIsFinished(str_istream_t *ctx); - -bool istrGetbool(str_istream_t *ctx, bool *val); -bool istrGetu8(str_istream_t *ctx, uint8_t *val); -bool istrGetu16(str_istream_t *ctx, uint16_t *val); -bool istrGetu32(str_istream_t *ctx, uint32_t *val); -bool istrGetu64(str_istream_t *ctx, uint64_t *val); -bool istrGeti8(str_istream_t *ctx, int8_t *val); -bool istrGeti16(str_istream_t *ctx, int16_t *val); -bool istrGeti32(str_istream_t *ctx, int32_t *val); -bool istrGeti64(str_istream_t *ctx, int64_t *val); -bool istrGetfloat(str_istream_t *ctx, float *val); -bool istrGetdouble(str_istream_t *ctx, double *val); -// get a string until a delimiter, the string is allocated by the function and should be freed -size_t istrGetstring(str_istream_t *ctx, char **val, char delim); -// get a string of maximum size len, the string is not allocated by the function and will be null terminated -size_t istrGetstringBuf(str_istream_t *ctx, char *val, size_t len); -strview_t istrGetview(str_istream_t *ctx, char delim); -strview_t istrGetviewLen(str_istream_t *ctx, size_t off, size_t len); - -/* == OUTPUT STREAM =========================================== */ - -typedef struct { - char *buf; - size_t size; - size_t allocated; -} str_ostream_t; - -str_ostream_t ostrInit(void); -str_ostream_t ostrInitLen(size_t initial_alloc); -str_ostream_t ostrInitStr(const char *buf, size_t len); - -void ostrFree(str_ostream_t *ctx); -void ostrClear(str_ostream_t *ctx); -str_t ostrMove(str_ostream_t *ctx); - -char ostrBack(str_ostream_t *ctx); -str_t ostrAsStr(str_ostream_t *ctx); -strview_t ostrAsView(str_ostream_t *ctx); - -void ostrReplace(str_ostream_t *ctx, char from, char to); - -void ostrPrintf(str_ostream_t *ctx, const char *fmt, ...); -void ostrPutc(str_ostream_t *ctx, char c); - -void ostrAppendbool(str_ostream_t *ctx, bool val); -void ostrAppendchar(str_ostream_t *ctx, char val); -void ostrAppendu8(str_ostream_t *ctx, uint8_t val); -void ostrAppendu16(str_ostream_t *ctx, uint16_t val); -void ostrAppendu32(str_ostream_t *ctx, uint32_t val); -void ostrAppendu64(str_ostream_t *ctx, uint64_t val); -void ostrAppendi8(str_ostream_t *ctx, int8_t val); -void ostrAppendi16(str_ostream_t *ctx, int16_t val); -void ostrAppendi32(str_ostream_t *ctx, int32_t val); -void ostrAppendi64(str_ostream_t *ctx, int64_t val); -void ostrAppendfloat(str_ostream_t *ctx, float val); -void ostrAppenddouble(str_ostream_t *ctx, double val); -void ostrAppendview(str_ostream_t *ctx, strview_t view); - -#ifdef __cplusplus -} // extern "C" -#endif \ No newline at end of file diff --git a/vec.h b/vec.h deleted file mode 100644 index ffdeda7..0000000 --- a/vec.h +++ /dev/null @@ -1,603 +0,0 @@ -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include -#include - -/* - * Basic usage: - * #define T int - * // optional, if not defined all the names will be longer, e.g. - * // vec_int_t v = vec_intInit(); - * #define VEC_SHORT_NAME veci - * #include // undefines T - * [...] - * veci_t v = veciInit(); - * veciPush(&v, 10); - * veciEraseAt(&v, 0); - * veciFree(&v); - */ - -typedef int( *vec_cmp_fn_t)(const void *, const void *); - -#ifndef T -#error "Must define T before including vec.h" -#endif - -#define VEC_CAT(a, b) a ## b -#define VEC_PASTE(a, b) VEC_CAT(a, b) -#define TYPE(prefix, type) VEC_PASTE(VEC_PASTE(prefix, _), VEC_PASTE(type, _t)) -#define ITER(prefix, type) VEC_PASTE(VEC_PASTE(prefix, _), VEC_PASTE(type, _it_t)) - -#ifdef VEC_SHORT_NAME - #define VEC_T VEC_PASTE(VEC_SHORT_NAME, _t) - #define VEC_IT_T VEC_PASTE(VEC_SHORT_NAME, _it_t) - #define VEC VEC_SHORT_NAME -#else - #define VEC_T TYPE(vec, T) - #define VEC_IT_T ITER(vec, T) - #define VEC VEC_PASTE(vec, VEC_PASTE(_, T)) -#endif - -#define FUN(postfix) VEC_PASTE(VEC, postfix) - -#ifndef VEC_NO_DECLARATION - -typedef struct { - T *buf; - size_t size; - size_t allocated; -} VEC_T; - -#define vec_foreach(type, name, it, vec) \ - for(type *it = VEC_PASTE(name, Beg)(&vec); it < VEC_PASTE(name, End)(&vec); ++it) - -VEC_T FUN(Init)(void); -VEC_T FUN(InitArr)(T *arr, size_t len); -void FUN(Free)(VEC_T *ctx); - -VEC_T FUN(Move)(VEC_T *ctx); -VEC_T FUN(Copy)(VEC_T *ctx); - -T *FUN(Beg)(VEC_T *ctx); -T *FUN(End)(VEC_T *ctx); -T *FUN(Back)(VEC_T *ctx); -bool FUN(Empty)(VEC_T *ctx); - -void FUN(Realloc)(VEC_T *ctx, size_t needed); -void FUN(Reserve)(VEC_T *ctx, size_t newsize); -void FUN(ShrinkToFit)(VEC_T *ctx); - -void FUN(Clear)(VEC_T *ctx); -void FUN(ClearZero)(VEC_T *ctx); - -void FUN(InsertAt)(VEC_T *ctx, size_t index, T val); -void FUN(InsertAfter)(VEC_T *ctx, T *it, T val); -void FUN(InsertBefore)(VEC_T *ctx, T *it, T val); - -void FUN(InsertAtSw)(VEC_T *ctx, size_t index, T val); -void FUN(InsertAfterSw)(VEC_T *ctx, T *it, T val); -void FUN(InsertBeforeSw)(VEC_T *ctx, T *it, T val); - -// swaps with last -void FUN(Erase)(VEC_T *ctx, T *it); -void FUN(EraseAt)(VEC_T *ctx, size_t index); -// moves whole array back one -void FUN(EraseMv)(VEC_T *ctx, T *it); -void FUN(EraseAtMv)(VEC_T *ctx, size_t index); - -void FUN(Push)(VEC_T *ctx, T val); -void FUN(PushRef)(VEC_T *ctx, T *val); -T FUN(Pop)(VEC_T *ctx); - -void FUN(Resize)(VEC_T *ctx, size_t newcount, T val); -void FUN(ResizeRef)(VEC_T *ctx, size_t newcount, T *val); - -void FUN(EraseWhen)(VEC_T *ctx, T val); - -typedef bool (*TYPE(vec_erase_fn, T))(const T *val, void *udata); -void FUN(EraseIf)(VEC_T *ctx, TYPE(vec_erase_fn, T) cmp, void *udata); -void FUN(EraseIfMv)(VEC_T *ctx, TYPE(vec_erase_fn, T) cmp, void *udata); - -// typedef int (*TYPE(vec_sort_fn, T))(const T *a, const T *b); -void FUN(Sort)(VEC_T *ctx, vec_cmp_fn_t cmp); - -#endif // VEC_NO_DECLARATION - -#ifndef VEC_NO_IMPLEMENTATION - -inline -VEC_T FUN(Init)(void) { - return (VEC_T){ - .buf = NULL, - .size = 0, - .allocated = 0 - }; -} - -inline -VEC_T FUN(InitArr)(T *arr, size_t len) { - VEC_T v = FUN(Init)(); - FUN(Realloc)(&v, len); - memcpy(v.buf, arr, len * sizeof(T)); - v.size = len; - return v; -} - -inline -void FUN(Free)(VEC_T *ctx) { - free(ctx->buf); - ctx->buf = NULL; - ctx->size = 0; - ctx->allocated = 0; -} - -inline -VEC_T FUN(Move)(VEC_T *ctx) { - VEC_T mv = *ctx; - ctx->buf = NULL; - FUN(Free)(ctx); - return mv; -} - -inline -VEC_T FUN(Copy)(VEC_T *ctx) { - VEC_T cp = FUN(Init)(); - if(ctx->buf) { - FUN(Reserve)(&cp, ctx->size); - memcpy(cp.buf, ctx->buf, ctx->size * sizeof(T)); - cp.size = ctx->size; - } - return cp; -} - -inline -T *FUN(Beg)(VEC_T *ctx) { - return ctx->buf; -} - -inline -T *FUN(End)(VEC_T *ctx) { - return ctx->buf + ctx->size; -} - -inline -T *FUN(Back)(VEC_T *ctx) { - return ctx->buf ? &ctx->buf[ctx->size - 1] : NULL; -} - -inline -bool FUN(Empty)(VEC_T *ctx) { - return ctx->buf ? ctx->size == 0 : true; -} - -inline -void FUN(Realloc)(VEC_T *ctx, size_t needed) { - if((ctx->size + needed) >= ctx->allocated) { - ctx->allocated = (ctx->allocated * 2) + needed; - ctx->buf = (T *)realloc(ctx->buf, ctx->allocated * sizeof(T)); - } -} - -inline -void FUN(Reserve)(VEC_T *ctx, size_t newsize) { - if(ctx->allocated < newsize) { - ctx->allocated = newsize; - ctx->buf = (T *)realloc(ctx->buf, ctx->allocated * sizeof(T)); - } -} - -inline -void FUN(ShrinkToFit)(VEC_T *ctx) { - ctx->allocated = ctx->size; - ctx->buf = (T *)realloc(ctx->buf, ctx->allocated * sizeof(T)); -} - -inline -void FUN(Clear)(VEC_T *ctx) { - ctx->size = 0; -} - -inline -void FUN(ClearZero)(VEC_T *ctx) { - ctx->size = 0; - memset(ctx->buf, 0, ctx->allocated * sizeof(T)); -} - -inline -void FUN(InsertAt)(VEC_T *ctx, size_t index, T val) { - FUN(Realloc)(ctx, 1); - for(size_t i = ctx->size; i > index; --i) { - ctx->buf[i] = ctx->buf[i - 1]; - } - ctx->buf[index] = val; - ctx->size++; -} - -inline -void FUN(InsertAfter)(VEC_T *ctx, T *it, T val) { - size_t index = it - ctx->buf; - // insertAt acts as insertBefore, so we just add 1 - FUN(InsertAt)(ctx, index + 1, val); -} - -inline -void FUN(InsertBefore)(VEC_T *ctx, T *it, T val) { - size_t index = it - ctx->buf; - FUN(InsertAt)(ctx, index, val); -} - -inline -void FUN(InsertAtSw)(VEC_T *ctx, size_t index, T val) { - FUN(Realloc)(ctx, 1); - ctx->buf[ctx->size] = ctx->buf[index]; - ctx->buf[index] = val; - ctx->size++; -} - -inline -void FUN(InsertAfterSw)(VEC_T *ctx, T *it, T val) { - size_t index = it - ctx->buf; - // insertAt acts as insertBefore, so we just add 1 - FUN(InsertAtSw)(ctx, index + 1, val); -} - -inline -void FUN(InsertBeforeSw)(VEC_T *ctx, T *it, T val) { - size_t index = it - ctx->buf; - FUN(InsertAtSw)(ctx, index, val); -} - -inline -void FUN(Erase)(VEC_T *ctx, T *it) { - size_t index = it - ctx->buf; - FUN(EraseAt)(ctx, index); -} - -inline -void FUN(EraseAt)(VEC_T *ctx, size_t index) { - ctx->size--; - ctx->buf[index] = ctx->buf[ctx->size]; -} - -inline -void FUN(EraseMv)(VEC_T *ctx, T *it) { - size_t index = it - ctx->buf; - FUN(EraseAtMv)(ctx, index); -} - -inline -void FUN(EraseAtMv)(VEC_T *ctx, size_t index) { - ctx->size--; - for(size_t i = index; i < ctx->size; ++i) { - ctx->buf[i] = ctx->buf[i + 1]; - } -} - -inline -void FUN(Push)(VEC_T *ctx, T val) { - FUN(Realloc)(ctx, 1); - ctx->buf[ctx->size] = val; - ctx->size++; -} - -inline -void FUN(PushRef)(VEC_T *ctx, T *val) { - FUN(Realloc)(ctx, 1); - ctx->buf[ctx->size] = *val; - ctx->size++; -} - -inline -T FUN(Pop)(VEC_T *ctx) { - ctx->size--; - return ctx->buf[ctx->size]; -} - -inline -void FUN(Resize)(VEC_T *ctx, size_t newcount, T val) { - if(newcount <= ctx->size) { - ctx->size = newcount; - return; - } - FUN(Realloc)(ctx, newcount - ctx->size); - for(size_t i = ctx->size; i < newcount; ++i) { - ctx->buf[i] = val; - } - ctx->size = newcount; -} - -inline -void FUN(ResizeRef)(VEC_T *ctx, size_t newcount, T *val) { - if(newcount <= ctx->size) { - ctx->size = newcount; - } - FUN(Realloc)(ctx, newcount - ctx->size); - if(val) { - for(size_t i = ctx->size; i < newcount; ++i) { - ctx->buf[i] = *val; - } - } - ctx->size = newcount; -} - -#ifndef VEC_DISABLE_ERASE_WHEN - -inline -void FUN(EraseWhen)(VEC_T *ctx, T val) { - for(size_t i = 0; i < ctx->size; ++i) { - if(ctx->buf[i] == val) { - FUN(EraseAt)(ctx, i); - --i; - } - } -} - -#endif - -inline -void FUN(EraseIf)(VEC_T *ctx, TYPE(vec_erase_fn, T) cmp, void *udata) { - for(size_t i = 0; i < ctx->size; ++i) { - if(cmp(&ctx->buf[i], udata)) { - FUN(EraseAt)(ctx, i); - --i; - } - } -} - -inline -void FUN(EraseIfMv)(VEC_T *ctx, TYPE(vec_erase_fn, T) cmp, void *udata) { - for(size_t i = 0; i < ctx->size; ++i) { - if(cmp(&ctx->buf[i], udata)) { - FUN(EraseAtMv)(ctx, i); - --i; - } - } -} - -inline -void FUN(Sort)(VEC_T *ctx, vec_cmp_fn_t cmp) { - qsort(ctx->buf, ctx->size, sizeof(T), cmp); -} - -#endif // VEC_NO_IMPLEMENTATION - -#undef FUN -#undef VEC -#undef VEC_T -#undef TYPE -#undef VEC_SHORT_NAME -#undef T -#undef VEC_DISABLE_ERASE_WHEN -#undef VEC_NO_DECLARATION -#undef VEC_NO_IMPLEMENTATION - -#if 0 -// vec.h testing: - -#define T int -#define VEC_SHORT_NAME veci -#include -#define foreach(it, vec) vec_foreach(int, veci, it, vec) - -#define T char -#define VEC_SHORT_NAME vecc -#include -#include - - -#define PRINTVALS(v, s) \ - printf("{ "); \ - for(size_t i = 0; i < v.size; ++i) \ - printf(s " ", v.buf[i]); \ - printf("}\n"); - -#define PRINTVEC(v, s) \ - printf(#v ": {\n"); \ - printf("\tsize: %zu\n", v.size); \ - printf("\tallocated: %zu\n", v.allocated); \ - printf("\tvalues:"); \ - PRINTVALS(v, s); \ - printf("}\n"); \ - -#define PRINTVECI(v) PRINTVEC(v, "%d") -#define PRINTVALSI(v) PRINTVALS(v, "%d") - -bool veciEraseEven(const int *val, void *udata) { - return *val % 2 == 0; -} - -bool veciEraseHigher(const int *val, void *udata) { - return *val > 8; -} - -int main() { - debug("== TESTING INIT ==========================="); - { - veci_t v = veciInit(); - PRINTVECI(v); - veciFree(&v); - - v = veciInitArr((int[]){1, 2, 3, 4}, 4); - veciPush(&v, 25); - veciPush(&v, 13); - PRINTVECI(v); - veciFree(&v); - } - debug("== TESTING MOVE/COPY ======================"); - { - veci_t a = veciInitArr((int[]){1, 2, 3, 4}, 4); - info("before move"); - PRINTVECI(a); - info("after move"); - veci_t b = veciMove(&a); - PRINTVECI(a); - PRINTVECI(b); - veciFree(&a); - veciFree(&b); - - a = veciInitArr((int[]){1, 2, 3, 4}, 4); - b = veciCopy(&a); - info("copied"); - PRINTVECI(a); - PRINTVECI(b); - info("modified b"); - b.buf[2] = 9; - PRINTVECI(a); - PRINTVECI(b); - veciFree(&a); - veciFree(&b); - } - debug("== TESTING BACK ==========================="); - { - vecc_t v = veccInitArr((char[]){'a', 'b', 'c', 'd', 'e', 'f'}, 6); - - PRINTVEC(v, "%c"); - info("The last character is '%c'.", *veccBack(&v)); - - veccFree(&v); - } - debug("== TESTING EMPTY =========================="); - { - veci_t v = veciInit(); - info("Initially, vecEmpty(): %s", veciEmpty(&v) ? "true":"false"); - veciPush(&v, 42); - info("After adding elements, vecEmpty(): %s", veciEmpty(&v) ? "true":"false"); - veciFree(&v); - } - debug("== TESTING RESERVE/SHRINK_TO_FIT/CLEAR ===="); - { - veci_t v = veciInit(); - info("Default capacity: %zu", v.allocated); - veciResize(&v, 100, 0); - info("100 elements: %zu", v.allocated); - veciResize(&v, 50, 0); - info("after resize(50): %zu", v.allocated); - veciShrinkToFit(&v); - info("after shrinkToFit(): %zu", v.allocated); - veciClear(&v); - info("after clear(): %zu", v.allocated); - veciShrinkToFit(&v); - info("after shrinkToFit(): %zu", v.allocated); - for(int i = 1000; i < 1300; ++i) { - veciPush(&v, i); - } - info("after adding 300 elements: %zu", v.allocated); - veciShrinkToFit(&v); - info("after shrinkToFit(): %zu", v.allocated); - veciFree(&v); - } - debug("== TESTING ITERATORS ======================"); - { - veci_t v = veciInitArr((int[]){1, 2, 3, 4, 5}, 5); - PRINTVECI(v); - info("foreach:"); - for(int *it = veciBeg(&v); it != veciEnd(&v); ++it) { - printf("\t*it: %d\n", *it); - } - veciFree(&v); - } - debug("== TESTING INSERT ========================="); - { - veci_t v = veciInit(); - info("init with 3 100"); - veciResize(&v, 3, 100); - PRINTVALSI(v); - info("insert 200 at 0"); - veciInsertAt(&v, 0, 200); - PRINTVALSI(v); - info("insert 300 before beginning"); - veciInsertBefore(&v, veciBeg(&v), 300); - PRINTVALSI(v); - info("insert 400 after beg + 1"); - veciInsertAfter(&v, veciBeg(&v) + 1, 400); - PRINTVALSI(v); - info("insert swap 500 at 3"); - veciInsertAtSw(&v, 3, 500); - PRINTVALSI(v); - info("insert swap 600 before beg"); - veciInsertBeforeSw(&v, veciBeg(&v), 600); - PRINTVALSI(v); - info("insert swap 700 after end - 4"); - veciInsertAfterSw(&v, veciEnd(&v) - 4, 700); - PRINTVALSI(v); - - veciFree(&v); - } - debug("== TESTING ERASE =========================="); - { - veci_t v = veciInitArr((int[]){0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, 10); - info("initializing with number from 0 to 9"); - PRINTVALSI(v); - info("erasing beginning"); - veciErase(&v, veciBeg(&v)); - PRINTVALSI(v); - info("erasing index 5"); - veciEraseAt(&v, 5); - PRINTVALSI(v); - info("erasing mv end - 3"); - veciEraseMv(&v, veciEnd(&v) - 3); - PRINTVALSI(v); - info("erasing mv index 1"); - veciEraseAtMv(&v, 1); - PRINTVALSI(v); - info("erasing mv all even numbers"); - veciEraseIfMv(&v, veciEraseEven, NULL); - PRINTVALSI(v); - info("erasing numbers higher than 8"); - veciEraseIf(&v, veciEraseHigher, NULL); - PRINTVALSI(v); - - veciFree(&v); - } - debug("== TESTING CLEAR_ZERO ====================="); - { - veci_t v = veciInitArr((int[]){0, 1, 2, 3, 4, 5}, 6); - info("initialized from 0 to 6"); - PRINTVECI(v); - info("clearZero"); - size_t oldsize = v.size; - veciClearZero(&v); - for(int i = 0; i < oldsize; ++i) { - printf("\t%d > %d\n", i, v.buf[i]); - } - } - debug("== TESTING PUSH/PUSH_REF =================="); - { - veci_t v = veciInit(); - - info("pushing 10"); - veciPush(&v, 10); - int value = 50; - info("pushing reference to value: %d", value); - veciPushRef(&v, &value); - - info("vector holds: "); - printf("> "); - foreach(it, v) { - printf("%d ", *it); - } - printf("\n"); - - veciFree(&v); - } - debug("== TESTING POP ============================"); - { - vecc_t v = veccInitArr("hello world!", 12); - info("initialized with %.*s", (int)v.size, v.buf); - while(!veccEmpty(&v)) { - printf("pooped '%c'\n", veccPop(&v)); - printf("> [%.*s]\n", (int)v.size, v.buf); - } - veccFree(&v); - } -} - -#endif - -#ifdef __cplusplus -} // extern "C" -#endif \ No newline at end of file diff --git a/win32_slim.h b/win32_slim.h deleted file mode 100644 index 7e80944..0000000 --- a/win32_slim.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#ifndef WIN32_LEAN_AND_MEAN - #define WIN32_LEAN_AND_MEAN -#endif - -#ifndef WIN32_EXTRA_LEAN - #define WIN32_EXTRA_LEAN -#endif - -#define NOIME -#define NOWINRES -#define NOGDICAPMASKS -#define NOVIRTUALKEYCODES -#define NOWINMESSAGES -#define NOWINSTYLES -#define NOSYSMETRICS -#define NOMENUS -#define NOICONS -#define NOKEYSTATES -#define NOSYSCOMMANDS -#define NORASTEROPS -#define NOSHOWWINDOW -#define OEMRESOURCE -#define NOATOM -#define NOCLIPBOARD -#define NOCOLOR -#define NOCTLMGR -#define NODRAWTEXT -#define NOGDI -#define NOUSER -#define NOMB -#define NOMEMMGR -#define NOMETAFILE -#define NOMINMAX -#define NOMSG -#define NOOPENFILE -#define NOSCROLL -#define NOSERVICE -#define NOSOUND -#define NOTEXTMETRIC -#define NOWH -#define NOWINOFFSETS -#define NOCOMM -#define NOKANJI -#define NOHELP -#define NOPROFILER -#define NODEFERWINDOWPOS -#define NOMCX -#define NOIME -#define NOPROXYSTUB -#define NOIMAGE -#define NO -#define NOTAPE -#define ANSI_ONLY - -#include \ No newline at end of file