a lot of stuff tbh
This commit is contained in:
parent
d80773bb00
commit
8ddd9186b8
42 changed files with 6463 additions and 1593 deletions
21
src/collatypes.h
Normal file
21
src/collatypes.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
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;
|
||||
198
src/cthreads.c
Normal file
198
src/cthreads.c
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
#include "cthreads.h"
|
||||
|
||||
typedef struct {
|
||||
cthread_func_t func;
|
||||
void *arg;
|
||||
} _thr_internal_t;
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "win32_slim.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
// == 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 <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
// == 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
|
||||
67
src/cthreads.h
Normal file
67
src/cthreads.h
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// == 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
|
||||
166
src/dir.c
Normal file
166
src/dir.c
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
#include "dir.h"
|
||||
#include "tracelog.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "win32_slim.h"
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "strstream.h"
|
||||
|
||||
typedef struct {
|
||||
dir_entry_t cur;
|
||||
dir_entry_t next;
|
||||
HANDLE handle;
|
||||
} _dir_internal_t;
|
||||
|
||||
static dir_entry_t _fillDirEntry(WIN32_FIND_DATAW *data) {
|
||||
return (dir_entry_t) {
|
||||
.type =
|
||||
data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ?
|
||||
FS_TYPE_DIR : FS_TYPE_FILE,
|
||||
.name = strFromWCHAR(data->cFileName, 0)
|
||||
};
|
||||
}
|
||||
|
||||
static _dir_internal_t _getFirst(const wchar_t *path) {
|
||||
_dir_internal_t res = {0};
|
||||
WIN32_FIND_DATAW data = {0};
|
||||
|
||||
res.handle = FindFirstFileW(path, &data);
|
||||
|
||||
if(res.handle != INVALID_HANDLE_VALUE) {
|
||||
res.next = _fillDirEntry(&data);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void _getNext(_dir_internal_t *ctx) {
|
||||
WIN32_FIND_DATAW data = {0};
|
||||
|
||||
BOOL result = FindNextFileW(ctx->handle, &data);
|
||||
if(!result) {
|
||||
if(GetLastError() == ERROR_NO_MORE_FILES) {
|
||||
FindClose(ctx->handle);
|
||||
ctx->handle = NULL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
ctx->next = _fillDirEntry(&data);
|
||||
}
|
||||
|
||||
dir_t dirOpen(const char *path) {
|
||||
DWORD n = GetFullPathName(path, 0, NULL, NULL);
|
||||
str_ostream_t out = ostrInitLen(n + 3);
|
||||
n = GetFullPathName(path, n, out.buf, NULL);
|
||||
assert(n > 0);
|
||||
out.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 <dirent.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
// taken from https://sites.uclouvain.be/SystInfo/usr/include/dirent.h.html
|
||||
// hopefully shouldn't be needed
|
||||
#ifndef DT_DIR
|
||||
#define DT_DIR 4
|
||||
#endif
|
||||
#ifndef DT_REG
|
||||
#define DT_REG 8
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
DIR *dir;
|
||||
dir_entry_t next;
|
||||
} _dir_internal_t;
|
||||
|
||||
dir_t dirOpen(const char *path) {
|
||||
_dir_internal_t *ctx = (_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
|
||||
33
src/dir.h
Normal file
33
src/dir.h
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#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
|
||||
292
src/file.c
Normal file
292
src/file.c
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
#include "file.h"
|
||||
|
||||
#include "tracelog.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "win32_slim.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
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 });
|
||||
}
|
||||
59
src/file.h
Normal file
59
src/file.h
Normal file
|
|
@ -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
|
||||
130
src/hashmap.c
Normal file
130
src/hashmap.c
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
#include "hashmap.h"
|
||||
|
||||
#include <string.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));
|
||||
}
|
||||
49
src/hashmap.h
Normal file
49
src/hashmap.h
Normal file
|
|
@ -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);
|
||||
372
src/http.c
Normal file
372
src/http.c
Normal file
|
|
@ -0,0 +1,372 @@
|
|||
#include "http.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "os.h"
|
||||
#include "tracelog.h"
|
||||
|
||||
#include "vec.h"
|
||||
|
||||
// == INTERNAL ================================================================
|
||||
|
||||
static void _setField(vec(http_field_t) *fields_vec, const char *key, const char *value) {
|
||||
vec(http_field_t) fields = *fields_vec;
|
||||
|
||||
for (uint32 i = 0; i < vecLen(fields); ++i) {
|
||||
if (stricmp(fields[i].key, key) == 0) {
|
||||
char **curval = &fields[i].value;
|
||||
usize curlen = strlen(*curval);
|
||||
usize newlen = strlen(value);
|
||||
if(newlen > curlen) {
|
||||
*curval = (char *)realloc(*curval, newlen + 1);
|
||||
}
|
||||
memcpy(*curval, value, newlen);
|
||||
(*curval)[newlen] = '\0';
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise, add it to the list
|
||||
http_field_t field;
|
||||
usize klen = strlen(key);
|
||||
usize vlen = strlen(value);
|
||||
field.key = (char *)malloc(klen + 1);
|
||||
field.value = (char *)malloc(vlen + 1);
|
||||
memcpy(field.key, key, klen);
|
||||
memcpy(field.value, value, vlen);
|
||||
field.key[klen] = field.value[vlen] = '\0';
|
||||
|
||||
vecAppend(*fields_vec, field);
|
||||
}
|
||||
|
||||
// == HTTP VERSION ============================================================
|
||||
|
||||
int httpVerNumber(http_version_t ver) {
|
||||
return (ver.major * 10) + ver.minor;
|
||||
}
|
||||
|
||||
// == HTTP REQUEST ============================================================
|
||||
|
||||
http_request_t reqInit() {
|
||||
http_request_t req;
|
||||
memset(&req, 0, sizeof(req));
|
||||
reqSetUri(&req, strvInit("/"));
|
||||
req.version = (http_version_t){1, 1};
|
||||
return req;
|
||||
}
|
||||
|
||||
void reqFree(http_request_t *ctx) {
|
||||
for (uint32 i = 0; i < vecLen(ctx->fields); ++i) {
|
||||
free(ctx->fields[i].key);
|
||||
free(ctx->fields[i].value);
|
||||
}
|
||||
vecFree(ctx->fields);
|
||||
free(ctx->uri);
|
||||
free(ctx->body);
|
||||
memset(ctx, 0, sizeof(http_request_t));
|
||||
}
|
||||
|
||||
bool reqHasField(http_request_t *ctx, const char *key) {
|
||||
for(uint32 i = 0; i < vecLen(ctx->fields); ++i) {
|
||||
if(stricmp(ctx->fields[i].key, key) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void reqSetField(http_request_t *ctx, const char *key, const char *value) {
|
||||
_setField(&ctx->fields, key, value);
|
||||
}
|
||||
|
||||
void reqSetUri(http_request_t *ctx, strview_t uri) {
|
||||
if (strvIsEmpty(uri)) return;
|
||||
free(ctx->uri);
|
||||
if (uri.buf[0] != '/') {
|
||||
ctx->uri = (char *)realloc(ctx->uri, uri.len + 1);
|
||||
ctx->uri[0] = '/';
|
||||
memcpy(ctx->uri + 1, uri.buf, uri.len);
|
||||
ctx->uri[uri.len] = '\0';
|
||||
}
|
||||
else {
|
||||
ctx->uri = strvCopy(uri).buf;
|
||||
}
|
||||
}
|
||||
|
||||
str_ostream_t reqPrepare(http_request_t *ctx) {
|
||||
str_ostream_t out = ostrInitLen(1024);
|
||||
|
||||
const char *method = NULL;
|
||||
switch(ctx->method) {
|
||||
case REQ_GET: method = "GET"; break;
|
||||
case REQ_POST: method = "POST"; break;
|
||||
case REQ_HEAD: method = "HEAD"; break;
|
||||
case REQ_PUT: method = "PUT"; break;
|
||||
case REQ_DELETE: method = "DELETE"; break;
|
||||
default: err("unrecognized method: %d", method); goto error;
|
||||
}
|
||||
|
||||
ostrPrintf(&out, "%s %s HTTP/%hhu.%hhu\r\n",
|
||||
method, ctx->uri, ctx->version.major, ctx->version.minor
|
||||
);
|
||||
|
||||
for(uint32 i = 0; i < vecLen(ctx->fields); ++i) {
|
||||
ostrPrintf(&out, "%s: %s\r\n", ctx->fields[i].key, ctx->fields[i].value);
|
||||
}
|
||||
|
||||
ostrAppendview(&out, strvInit("\r\n"));
|
||||
if(ctx->body) {
|
||||
ostrAppendview(&out, strvInit(ctx->body));
|
||||
}
|
||||
|
||||
error:
|
||||
return out;
|
||||
}
|
||||
|
||||
str_t reqString(http_request_t *ctx) {
|
||||
str_ostream_t out = reqPrepare(ctx);
|
||||
return ostrAsStr(out);
|
||||
}
|
||||
|
||||
// == HTTP RESPONSE ===========================================================
|
||||
|
||||
http_response_t resInit() {
|
||||
return (http_response_t) {0};
|
||||
}
|
||||
|
||||
void resFree(http_response_t *ctx) {
|
||||
for(uint32 i = 0; i < vecLen(ctx->fields); ++i) {
|
||||
free(ctx->fields[i].key);
|
||||
free(ctx->fields[i].value);
|
||||
}
|
||||
vecFree(ctx->fields);
|
||||
vecFree(ctx->body);
|
||||
memset(ctx, 0, sizeof(http_response_t));
|
||||
}
|
||||
|
||||
bool resHasField(http_response_t *ctx, const char *key) {
|
||||
for(uint32 i = 0; i < vecLen(ctx->fields); ++i) {
|
||||
if(stricmp(ctx->fields[i].key, key) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *resGetField(http_response_t *ctx, const char *field) {
|
||||
for(uint32 i = 0; i < vecLen(ctx->fields); ++i) {
|
||||
if(stricmp(ctx->fields[i].key, field) == 0) {
|
||||
return ctx->fields[i].value;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void resParse(http_response_t *ctx, const char *data) {
|
||||
str_istream_t in = istrInit(data);
|
||||
|
||||
char hp[5];
|
||||
istrGetstringBuf(&in, hp, 5);
|
||||
if(stricmp(hp, "http") != 0) {
|
||||
err("response doesn't start with 'HTTP', instead with %c%c%c%c", hp[0], hp[1], hp[2], hp[3]);
|
||||
return;
|
||||
}
|
||||
istrSkip(&in, 1); // skip /
|
||||
istrGetu8(&in, &ctx->version.major);
|
||||
istrSkip(&in, 1); // skip .
|
||||
istrGetu8(&in, &ctx->version.minor);
|
||||
istrGeti32(&in, (int32*)&ctx->status_code);
|
||||
|
||||
istrIgnore(&in, '\n');
|
||||
istrSkip(&in, 1); // skip \n
|
||||
|
||||
resParseFields(ctx, &in);
|
||||
|
||||
const char *tran_encoding = resGetField(ctx, "transfer-encoding");
|
||||
if(tran_encoding == NULL || stricmp(tran_encoding, "chunked") != 0) {
|
||||
strview_t body = istrGetviewLen(&in, 0, SIZE_MAX);
|
||||
vecClear(ctx->body);
|
||||
vecReserve(ctx->body, body.len);
|
||||
memcpy(ctx->body, body.buf, body.len);
|
||||
}
|
||||
else {
|
||||
fatal("chunked encoding not implemented yet");
|
||||
}
|
||||
}
|
||||
|
||||
void resParseFields(http_response_t *ctx, str_istream_t *in) {
|
||||
strview_t line;
|
||||
|
||||
do {
|
||||
line = istrGetview(in, '\r');
|
||||
|
||||
usize pos = strvFind(line, ':', 0);
|
||||
if(pos != STRV_NOT_FOUND) {
|
||||
strview_t key = strvSub(line, 0, pos);
|
||||
strview_t value = strvSub(line, pos + 2, SIZE_MAX);
|
||||
|
||||
char *key_str = NULL;
|
||||
char *value_str = NULL;
|
||||
|
||||
key_str = strvCopy(key).buf;
|
||||
value_str = strvCopy(value).buf;
|
||||
|
||||
_setField(&ctx->fields, key_str, value_str);
|
||||
|
||||
free(key_str);
|
||||
free(value_str);
|
||||
}
|
||||
|
||||
istrSkip(in, 2); // skip \r\n
|
||||
} while(line.len > 2);
|
||||
}
|
||||
|
||||
// == HTTP CLIENT =============================================================
|
||||
|
||||
http_client_t hcliInit() {
|
||||
return (http_client_t) {
|
||||
.port = 80,
|
||||
};
|
||||
}
|
||||
|
||||
void hcliFree(http_client_t *ctx) {
|
||||
strFree(ctx->host_name);
|
||||
memset(ctx, 0, sizeof(http_client_t));
|
||||
}
|
||||
|
||||
void hcliSetHost(http_client_t *ctx, strview_t hostname) {
|
||||
// if the hostname starts with http:// (case insensitive)
|
||||
if(strvICompare(strvSub(hostname, 0, 7), strvInit("http://")) == 0) {
|
||||
ctx->host_name = strvCopy(strvSub(hostname, 7, SIZE_MAX));
|
||||
}
|
||||
else if(strvICompare(strvSub(hostname, 0, 8), strvInit("https://")) == 0) {
|
||||
err("HTTPS protocol not yet supported");
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// undefined protocol, use HTTP
|
||||
ctx->host_name = strvCopy(hostname);
|
||||
}
|
||||
}
|
||||
|
||||
http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *req) {
|
||||
if (strBack(ctx->host_name) == '/') {
|
||||
ctx->host_name.buf[--ctx->host_name.len] = '\0';
|
||||
}
|
||||
if(!reqHasField(req, "Host")) {
|
||||
reqSetField(req, "Host", ctx->host_name.buf);
|
||||
}
|
||||
if(!reqHasField(req, "Content-Length")) {
|
||||
if(req->body) {
|
||||
str_ostream_t out = ostrInitLen(20);
|
||||
ostrAppendu64(&out, strlen(req->body));
|
||||
reqSetField(req, "Content-Length", out.buf);
|
||||
ostrFree(out);
|
||||
}
|
||||
else {
|
||||
reqSetField(req, "Content-Length", "0");
|
||||
}
|
||||
}
|
||||
if(req->method == REQ_POST && !reqHasField(req, "Content-Type")) {
|
||||
reqSetField(req, "Content-Type", "application/x-www-form-urlencoded");
|
||||
}
|
||||
if(httpVerNumber(req->version) >= 11 && !reqHasField(req, "Connection")) {
|
||||
reqSetField(req, "Connection", "close");
|
||||
}
|
||||
|
||||
http_response_t res = resInit();
|
||||
str_t req_str = strInit();
|
||||
str_ostream_t received = ostrInitLen(1024);
|
||||
|
||||
if(!skInit()) {
|
||||
err("couldn't initialize sockets %s", skGetErrorString());
|
||||
goto skopen_error;
|
||||
}
|
||||
|
||||
ctx->socket = skOpen(SOCK_TCP);
|
||||
if(ctx->socket == INVALID_SOCKET) {
|
||||
err("couldn't open socket %s", skGetErrorString());
|
||||
goto error;
|
||||
}
|
||||
|
||||
if(skConnect(ctx->socket, ctx->host_name.buf, ctx->port)) {
|
||||
req_str = reqString(req);
|
||||
if(req_str.len == 0) {
|
||||
err("couldn't get string from request");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if(skSend(ctx->socket, req_str.buf, (int)req_str.len) == SOCKET_ERROR) {
|
||||
err("couldn't send request to socket: %s", skGetErrorString());
|
||||
goto error;
|
||||
}
|
||||
|
||||
char buffer[1024];
|
||||
int read = 0;
|
||||
do {
|
||||
read = skReceive(ctx->socket, buffer, sizeof(buffer));
|
||||
if(read == -1) {
|
||||
err("couldn't get the data from the server: %s", skGetErrorString());
|
||||
goto error;
|
||||
}
|
||||
ostrAppendview(&received, strvInitLen(buffer, read));
|
||||
} while(read != 0);
|
||||
|
||||
// if the data received is not null terminated
|
||||
if(*(received.buf + received.len) != '\0') {
|
||||
ostrPutc(&received, '\0');
|
||||
received.len--;
|
||||
}
|
||||
|
||||
resParse(&res, received.buf);
|
||||
}
|
||||
else {
|
||||
err("Couldn't connect to host %s -> %s", ctx->host_name, skGetErrorString());
|
||||
}
|
||||
|
||||
if(!skClose(ctx->socket)) {
|
||||
err("Couldn't close socket");
|
||||
}
|
||||
|
||||
error:
|
||||
if(!skCleanup()) {
|
||||
err("couldn't clean up sockets %s", skGetErrorString());
|
||||
}
|
||||
skopen_error:
|
||||
strFree(req_str);
|
||||
ostrFree(received);
|
||||
return res;
|
||||
}
|
||||
|
||||
http_response_t httpGet(strview_t hostname, strview_t uri) {
|
||||
http_request_t request = reqInit();
|
||||
request.method = REQ_GET;
|
||||
reqSetUri(&request, uri);
|
||||
|
||||
http_client_t client = hcliInit();
|
||||
hcliSetHost(&client, hostname);
|
||||
|
||||
http_response_t res = hcliSendRequest(&client, &request);
|
||||
|
||||
reqFree(&request);
|
||||
hcliFree(&client);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
url_split_t urlSplit(strview_t uri) {
|
||||
url_split_t out = {0};
|
||||
|
||||
if (strvStartsWithView(uri, strvInit("https://"))) {
|
||||
uri = strvRemovePrefix(uri, 8);
|
||||
}
|
||||
else if (strvStartsWithView(uri, strvInit("http://"))) {
|
||||
uri = strvRemovePrefix(uri, 7);
|
||||
}
|
||||
|
||||
out.host = strvSub(uri, 0, strvFind(uri, '/', 0));
|
||||
out.uri = strvSub(uri, out.host.len, SIZE_MAX);
|
||||
return out;
|
||||
}
|
||||
137
src/http.h
Normal file
137
src/http.h
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#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
|
||||
323
src/ini.c
Normal file
323
src/ini.c
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
86
src/ini.h
Normal file
86
src/ini.h
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#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
|
||||
107
src/os.c
Normal file
107
src/os.c
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
#include "os.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define _BUFSZ 128
|
||||
|
||||
#include <lmcons.h>
|
||||
|
||||
// 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 <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
|
||||
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
|
||||
30
src/os.h
Normal file
30
src/os.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include "str.h"
|
||||
#include "collatypes.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <stdio.h>
|
||||
#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 <stdio.h>
|
||||
int stricmp(const char *a, const char *b);
|
||||
#endif
|
||||
|
||||
str_t getUserName();
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
336
src/socket.c
Normal file
336
src/socket.c
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
#include "socket.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#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 <netdb.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <string.h> // 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
|
||||
117
src/socket.h
Normal file
117
src/socket.h
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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 <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
typedef SOCKET socket_t;
|
||||
typedef int sk_len_t;
|
||||
#elif SOCK_POSIX
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
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
|
||||
629
src/str.c
Normal file
629
src/str.c
Normal file
|
|
@ -0,0 +1,629 @@
|
|||
#include "str.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "tracelog.h"
|
||||
#include "strstream.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "win32_slim.h"
|
||||
#else
|
||||
#include <iconv.h>
|
||||
#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 <stdio.h>
|
||||
#include "tracelog.h"
|
||||
|
||||
void strTest(void) {
|
||||
str_t s;
|
||||
debug("== testing init =================");
|
||||
{
|
||||
s = strInit();
|
||||
printf("%s %zu\n", s.buf, s.len);
|
||||
strFree(&s);
|
||||
s = strInitStr("hello world");
|
||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
||||
strFree(&s);
|
||||
uint8 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
|
||||
122
src/str.h
Normal file
122
src/str.h
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <limits.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#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
|
||||
555
src/strstream.c
Normal file
555
src/strstream.c
Normal file
|
|
@ -0,0 +1,555 @@
|
|||
#include "strstream.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <math.h> // 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 (usize)(ctx.cur - ctx.start) >= ctx.size;
|
||||
}
|
||||
|
||||
bool istrGetbool(str_istream_t *ctx, bool *val) {
|
||||
usize remaining = ctx->size - (ctx->cur - ctx->start);
|
||||
if(strncmp(ctx->cur, "true", remaining) == 0) {
|
||||
*val = true;
|
||||
return true;
|
||||
}
|
||||
if(strncmp(ctx->cur, "false", remaining) == 0) {
|
||||
*val = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool istrGetu8(str_istream_t *ctx, uint8 *val) {
|
||||
char *end = NULL;
|
||||
*val = (uint8) strtoul(ctx->cur, &end, 0);
|
||||
|
||||
if(ctx->cur == end) {
|
||||
warn("istrGetu8: no valid conversion could be performed");
|
||||
return false;
|
||||
}
|
||||
else if(*val == UINT8_MAX) {
|
||||
warn("istrGetu8: value read is out of the range of representable values");
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx->cur = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool istrGetu16(str_istream_t *ctx, uint16 *val) {
|
||||
char *end = NULL;
|
||||
*val = (uint16) strtoul(ctx->cur, &end, 0);
|
||||
|
||||
if(ctx->cur == end) {
|
||||
warn("istrGetu16: no valid conversion could be performed");
|
||||
return false;
|
||||
}
|
||||
else if(*val == UINT16_MAX) {
|
||||
warn("istrGetu16: value read is out of the range of representable values");
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx->cur = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool istrGetu32(str_istream_t *ctx, uint32 *val) {
|
||||
char *end = NULL;
|
||||
*val = (uint32) strtoul(ctx->cur, &end, 0);
|
||||
|
||||
if(ctx->cur == end) {
|
||||
warn("istrGetu32: no valid conversion could be performed");
|
||||
return false;
|
||||
}
|
||||
else if(*val == UINT32_MAX) {
|
||||
warn("istrGetu32: value read is out of the range of representable values");
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx->cur = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool istrGetu64(str_istream_t *ctx, uint64 *val) {
|
||||
char *end = NULL;
|
||||
*val = strtoull(ctx->cur, &end, 0);
|
||||
|
||||
if(ctx->cur == end) {
|
||||
warn("istrGetu64: no valid conversion could be performed");
|
||||
return false;
|
||||
}
|
||||
else if(*val == ULLONG_MAX) {
|
||||
warn("istrGetu64: value read is out of the range of representable values");
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx->cur = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool istrGeti8(str_istream_t *ctx, int8 *val) {
|
||||
char *end = NULL;
|
||||
*val = (int8) strtol(ctx->cur, &end, 0);
|
||||
|
||||
if(ctx->cur == end) {
|
||||
warn("istrGeti8: no valid conversion could be performed");
|
||||
return false;
|
||||
}
|
||||
else if(*val == INT8_MAX || *val == INT8_MIN) {
|
||||
warn("istrGeti8: value read is out of the range of representable values");
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx->cur = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool istrGeti16(str_istream_t *ctx, int16 *val) {
|
||||
char *end = NULL;
|
||||
*val = (int16) strtol(ctx->cur, &end, 0);
|
||||
|
||||
if(ctx->cur == end) {
|
||||
warn("istrGeti16: no valid conversion could be performed");
|
||||
return false;
|
||||
}
|
||||
else if(*val == INT16_MAX || *val == INT16_MIN) {
|
||||
warn("istrGeti16: value read is out of the range of representable values");
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx->cur = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool istrGeti32(str_istream_t *ctx, int32 *val) {
|
||||
char *end = NULL;
|
||||
*val = (int32) strtol(ctx->cur, &end, 0);
|
||||
|
||||
if(ctx->cur == end) {
|
||||
warn("istrGeti32: no valid conversion could be performed");
|
||||
return false;
|
||||
}
|
||||
else if(*val == INT32_MAX || *val == INT32_MIN) {
|
||||
warn("istrGeti32: value read is out of the range of representable values");
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx->cur = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool istrGeti64(str_istream_t *ctx, int64 *val) {
|
||||
char *end = NULL;
|
||||
*val = strtoll(ctx->cur, &end, 0);
|
||||
|
||||
if(ctx->cur == end) {
|
||||
warn("istrGeti64: no valid conversion could be performed");
|
||||
return false;
|
||||
}
|
||||
else if(*val == INT64_MAX || *val == INT64_MIN) {
|
||||
warn("istrGeti64: value read is out of the range of representable values");
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx->cur = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool istrGetfloat(str_istream_t *ctx, float *val) {
|
||||
char *end = NULL;
|
||||
*val = strtof(ctx->cur, &end);
|
||||
|
||||
if(ctx->cur == end) {
|
||||
warn("istrGetfloat: no valid conversion could be performed");
|
||||
return false;
|
||||
}
|
||||
else if(*val == HUGE_VALF || *val == -HUGE_VALF) {
|
||||
warn("istrGetfloat: value read is out of the range of representable values");
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx->cur = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool istrGetdouble(str_istream_t *ctx, double *val) {
|
||||
char *end = NULL;
|
||||
*val = strtod(ctx->cur, &end);
|
||||
|
||||
if(ctx->cur == end) {
|
||||
warn("istrGetdouble: no valid conversion could be performed");
|
||||
return false;
|
||||
}
|
||||
else if(*val == HUGE_VAL || *val == -HUGE_VAL) {
|
||||
warn("istrGetdouble: value read is out of the range of representable values");
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx->cur = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
usize istrGetstring(str_istream_t *ctx, char **val, char delim) {
|
||||
const char *from = ctx->cur;
|
||||
istrIgnore(ctx, delim);
|
||||
// if it didn't actually find it, it just reached the end of the string
|
||||
if(*ctx->cur != delim) {
|
||||
*val = NULL;
|
||||
return 0;
|
||||
}
|
||||
usize len = ctx->cur - from;
|
||||
*val = (char *)malloc(len + 1);
|
||||
memcpy(*val, from, len);
|
||||
(*val)[len] = '\0';
|
||||
return len;
|
||||
}
|
||||
|
||||
usize istrGetstringBuf(str_istream_t *ctx, char *val, usize len) {
|
||||
usize remaining = ctx->size - (ctx->cur - ctx->start);
|
||||
len -= 1;
|
||||
len = remaining < len ? remaining : len;
|
||||
memcpy(val, ctx->cur, len);
|
||||
val[len] = '\0';
|
||||
ctx->cur += len;
|
||||
return len;
|
||||
}
|
||||
|
||||
strview_t istrGetview(str_istream_t *ctx, char delim) {
|
||||
const char *from = ctx->cur;
|
||||
istrIgnore(ctx, delim);
|
||||
usize len = ctx->cur - from;
|
||||
return strvInitLen(from, len);
|
||||
}
|
||||
|
||||
strview_t istrGetviewLen(str_istream_t *ctx, usize from, usize to) {
|
||||
usize len = ctx->size - (ctx->cur - ctx->start) - from;
|
||||
if (to > len) to = len;
|
||||
if (from > to) from = to;
|
||||
return strvInitLen(ctx->cur + from, to - from);
|
||||
}
|
||||
|
||||
/* == OUTPUT STREAM =========================================== */
|
||||
|
||||
static void _ostrRealloc(str_ostream_t *ctx, usize needed) {
|
||||
ctx->cap = (ctx->cap * 2) + needed;
|
||||
ctx->buf = (char *)realloc(ctx->buf, ctx->cap);
|
||||
}
|
||||
|
||||
str_ostream_t ostrInit() {
|
||||
return ostrInitLen(1);
|
||||
}
|
||||
|
||||
str_ostream_t ostrInitLen(usize initial_alloc) {
|
||||
str_ostream_t stream;
|
||||
stream.buf = (char *)calloc(initial_alloc, 1);
|
||||
stream.len = 0;
|
||||
stream.cap = initial_alloc;
|
||||
return stream;
|
||||
}
|
||||
|
||||
str_ostream_t ostrInitStr(const char *cstr, usize len) {
|
||||
str_ostream_t stream;
|
||||
stream.buf = (char *)malloc(len + 1);
|
||||
memcpy(stream.buf, cstr, len);
|
||||
stream.len = len;
|
||||
stream.cap = len + 1;
|
||||
return stream;
|
||||
}
|
||||
|
||||
void ostrFree(str_ostream_t ctx) {
|
||||
free(ctx.buf);
|
||||
}
|
||||
|
||||
void ostrClear(str_ostream_t *ctx) {
|
||||
ctx->len = 0;
|
||||
}
|
||||
|
||||
char ostrBack(str_ostream_t ctx) {
|
||||
if(ctx.len == 0) return '\0';
|
||||
return ctx.buf[ctx.len - 1];
|
||||
}
|
||||
|
||||
str_t ostrAsStr(str_ostream_t ctx) {
|
||||
return (str_t) {
|
||||
.buf = ctx.buf,
|
||||
.len = ctx.len
|
||||
};
|
||||
}
|
||||
|
||||
strview_t ostrAsView(str_ostream_t ctx) {
|
||||
return (strview_t) {
|
||||
.buf = ctx.buf,
|
||||
.len = ctx.len
|
||||
};
|
||||
}
|
||||
|
||||
void ostrReplace(str_ostream_t *ctx, char from, char to) {
|
||||
for(usize i = 0; i < ctx->len; ++i) {
|
||||
if(ctx->buf[i] == from) {
|
||||
ctx->buf[i] = to;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ostrPrintf(str_ostream_t *ctx, const char *fmt, ...) {
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
ostrPrintfV(ctx, fmt, va);
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
void ostrPrintfV(str_ostream_t *ctx, const char *fmt, va_list args) {
|
||||
va_list vtemp;
|
||||
int len;
|
||||
usize remaining;
|
||||
|
||||
// vsnprintf returns the length of the formatted string, even if truncated
|
||||
// we use this to get the actual length of the formatted string
|
||||
va_copy(vtemp, args);
|
||||
len = vsnprintf(NULL, 0, fmt, vtemp);
|
||||
va_end(vtemp);
|
||||
if(len < 0) {
|
||||
err("couldn't format string \"%s\"", fmt);
|
||||
goto error;
|
||||
}
|
||||
|
||||
remaining = ctx->cap - ctx->len;
|
||||
if(remaining < (usize)len) {
|
||||
_ostrRealloc(ctx, len + 1);
|
||||
remaining = ctx->cap - ctx->len;
|
||||
}
|
||||
|
||||
// actual formatting here
|
||||
va_copy(vtemp, args);
|
||||
len = vsnprintf(ctx->buf + ctx->len, remaining, fmt, vtemp);
|
||||
va_end(vtemp);
|
||||
if(len < 0) {
|
||||
err("couldn't format stringh \"%s\"", fmt);
|
||||
goto error;
|
||||
}
|
||||
ctx->len += len;
|
||||
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
#define APPEND_BUF_LEN 20
|
||||
|
||||
void ostrPutc(str_ostream_t *ctx, char c) {
|
||||
ostrAppendchar(ctx, c);
|
||||
}
|
||||
|
||||
void ostrPuts(str_ostream_t *ctx, const char *str) {
|
||||
ostrAppendview(ctx, strvInit(str));
|
||||
}
|
||||
|
||||
void ostrAppendbool(str_ostream_t *ctx, bool val) {
|
||||
ostrAppendview(ctx, strvInit(val ? "true" : "false"));
|
||||
}
|
||||
|
||||
void ostrAppendchar(str_ostream_t *ctx, char val) {
|
||||
if(ctx->len >= ctx->cap) {
|
||||
_ostrRealloc(ctx, 1);
|
||||
}
|
||||
ctx->buf[ctx->len++] = val;
|
||||
ctx->buf[ctx->len] = '\0';
|
||||
}
|
||||
|
||||
void ostrAppendu8(str_ostream_t *ctx, uint8 val) {
|
||||
char buf[APPEND_BUF_LEN];
|
||||
int len = snprintf(buf, sizeof(buf), "%hhu", val);
|
||||
if(len <= 0) {
|
||||
err("ostrAppendu8: couldn't write %hhu", val);
|
||||
return;
|
||||
}
|
||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
||||
}
|
||||
|
||||
void ostrAppendu16(str_ostream_t *ctx, uint16 val) {
|
||||
char buf[APPEND_BUF_LEN];
|
||||
int len = snprintf(buf, sizeof(buf), "%hu", val);
|
||||
if(len <= 0) {
|
||||
err("ostrAppendu16: couldn't write %hu", val);
|
||||
return;
|
||||
}
|
||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
||||
}
|
||||
|
||||
void ostrAppendu32(str_ostream_t *ctx, uint32 val) {
|
||||
char buf[APPEND_BUF_LEN];
|
||||
int len = snprintf(buf, sizeof(buf), "%u", val);
|
||||
if(len <= 0) {
|
||||
err("ostrAppendu32: couldn't write %u", val);
|
||||
return;
|
||||
}
|
||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
||||
}
|
||||
|
||||
void ostrAppendu64(str_ostream_t *ctx, uint64 val) {
|
||||
char buf[APPEND_BUF_LEN];
|
||||
#if _WIN32
|
||||
int len = snprintf(buf, sizeof(buf), "%llu", val);
|
||||
#else
|
||||
int len = snprintf(buf, sizeof(buf), "%lu", val);
|
||||
#endif
|
||||
if(len <= 0) {
|
||||
err("ostrAppendu64: couldn't write %lu", val);
|
||||
return;
|
||||
}
|
||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
||||
}
|
||||
|
||||
void ostrAppendi8(str_ostream_t *ctx, int8 val) {
|
||||
char buf[APPEND_BUF_LEN];
|
||||
int len = snprintf(buf, sizeof(buf), "%hhi", val);
|
||||
if(len <= 0) {
|
||||
err("ostrAppendi8: couldn't write %hhi", val);
|
||||
return;
|
||||
}
|
||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
||||
}
|
||||
|
||||
void ostrAppendi16(str_ostream_t *ctx, int16 val) {
|
||||
char buf[APPEND_BUF_LEN];
|
||||
int len = snprintf(buf, sizeof(buf), "%hi", val);
|
||||
if(len <= 0) {
|
||||
err("ostrAppendi16: couldn't write %hi", val);
|
||||
return;
|
||||
}
|
||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
||||
}
|
||||
|
||||
void ostrAppendi32(str_ostream_t *ctx, int32 val) {
|
||||
char buf[APPEND_BUF_LEN];
|
||||
int len = snprintf(buf, sizeof(buf), "%i", val);
|
||||
if(len <= 0) {
|
||||
err("ostrAppendi32: couldn't write %i", val);
|
||||
return;
|
||||
}
|
||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
||||
}
|
||||
|
||||
void ostrAppendi64(str_ostream_t *ctx, int64 val) {
|
||||
char buf[APPEND_BUF_LEN];
|
||||
#if _WIN32
|
||||
int len = snprintf(buf, sizeof(buf), "%lli", val);
|
||||
#else
|
||||
int len = snprintf(buf, sizeof(buf), "%li", val);
|
||||
#endif
|
||||
if(len <= 0) {
|
||||
err("ostrAppendi64: couldn't write %li", val);
|
||||
return;
|
||||
}
|
||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
||||
}
|
||||
|
||||
void ostrAppendfloat(str_ostream_t *ctx, float val) {
|
||||
char buf[APPEND_BUF_LEN * 3];
|
||||
int len = snprintf(buf, sizeof(buf), "%g", (double)val);
|
||||
if(len <= 0) {
|
||||
err("ostrAppendfloat: couldn't write %g", (double)val);
|
||||
return;
|
||||
}
|
||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
||||
}
|
||||
|
||||
void ostrAppenddouble(str_ostream_t *ctx, double val) {
|
||||
char buf[APPEND_BUF_LEN * 3];
|
||||
int len = snprintf(buf, sizeof(buf), "%g", val);
|
||||
if(len <= 0) {
|
||||
err("ostrAppenddouble: couldn't write %g", val);
|
||||
return;
|
||||
}
|
||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
||||
}
|
||||
|
||||
void ostrAppendview(str_ostream_t *ctx, strview_t view) {
|
||||
if((ctx->cap - ctx->len) <= view.len) {
|
||||
_ostrRealloc(ctx, view.len + 1);
|
||||
}
|
||||
memcpy(ctx->buf + ctx->len, view.buf, view.len);
|
||||
ctx->len += view.len;
|
||||
ctx->buf[ctx->len] = '\0';
|
||||
}
|
||||
110
src/strstream.h
Normal file
110
src/strstream.h
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#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
|
||||
122
src/tracelog.c
Normal file
122
src/tracelog.c
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
#include "tracelog.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
34
src/tracelog.h
Normal file
34
src/tracelog.h
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#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 <stdbool.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
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
|
||||
172
src/utf8.c
Normal file
172
src/utf8.c
Normal file
|
|
@ -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;
|
||||
}
|
||||
19
src/utf8.h
Normal file
19
src/utf8.h
Normal file
|
|
@ -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);
|
||||
69
src/vec.h
Normal file
69
src/vec.h
Normal file
|
|
@ -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 <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
||||
15
src/win32_slim.h
Normal file
15
src/win32_slim.h
Normal file
|
|
@ -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 <windows.h>
|
||||
|
||||
#endif // _WIN32
|
||||
Loading…
Add table
Add a link
Reference in a new issue