use arena everywhere + bunch of cool new stuff

This commit is contained in:
snarmph 2024-04-18 14:29:36 +01:00
parent 8fdecd89a5
commit ae59f269c2
51 changed files with 5443 additions and 7233 deletions

View file

@ -1,28 +0,0 @@
add_library(colla STATIC
src/collatypes.h
src/vec.h
src/win32_slim.h
src/tracelog.h src/tracelog.c
src/str.h src/str.c
src/hashmap.h src/hashmap.c
src/utf8.h src/utf8.c
src/ini.h src/ini.c
src/strstream.h src/strstream.c
src/os.h src/os.c
src/file.h src/file.c
src/dir.h src/dir.c
src/socket.h src/socket.c
src/http.h src/http.c
src/cthreads.h src/cthreads.c
)
if(MSVC)
target_link_libraries(colla ws2_32.lib)
target_compile_options(colla PRIVATE /W4)
else()
target_link_libraries(colla pthread)
target_compile_options(colla PRIVATE -Wall -Wextra -Wpedantic)
target_compile_definitions(colla PUBLIC _DEFAULT_SOURCE)
endif()
target_include_directories(colla PUBLIC src)

1
build.bat Normal file
View file

@ -0,0 +1 @@
zig cc -o test.exe main.c -lWs2_32 -lWininet -Wall -Wpedantic -Wno-newline-eof

17
build.c Normal file
View file

@ -0,0 +1,17 @@
// remember to link Ws2_32 (sockets, server, http) and Wininet (https) if you want to do networking stuff!
#include "colla/arena.c"
#include "colla/base64.c"
#include "colla/cthreads.c"
#include "colla/file.c"
#include "colla/format.c"
#include "colla/http.c"
#include "colla/ini.c"
#include "colla/json.c"
#include "colla/server.c"
#include "colla/socket.c"
#include "colla/str.c"
#include "colla/strstream.c"
#include "colla/tracelog.c"
#include "colla/utf8.c"
#include "colla/vmem.c"

4325
colla.h

File diff suppressed because it is too large Load diff

198
colla/arena.c Normal file
View file

@ -0,0 +1,198 @@
#include "arena.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "vmem.h"
#include "tracelog.h"
static uintptr_t arena__align(uintptr_t ptr, usize align) {
return (ptr + (align - 1)) & ~(align - 1);
}
static arena_t arena__make_virtual(usize size);
static arena_t arena__make_malloc(usize size);
static arena_t arena__make_static(byte *buf, usize len);
static void arena__free_virtual(arena_t *arena);
static void arena__free_malloc(arena_t *arena);
arena_t arenaInit(const arena_desc_t *desc) {
if (desc) {
switch (desc->type) {
case ARENA_VIRTUAL: return arena__make_virtual(desc->allocation);
case ARENA_MALLOC: return arena__make_malloc(desc->allocation);
case ARENA_STATIC: return arena__make_static(desc->static_buffer, desc->allocation);
}
}
debug("couldn't init arena: %p %d\n", desc, desc ? desc->type : 0);
return (arena_t){0};
}
void arenaCleanup(arena_t *arena) {
if (!arena) {
return;
}
switch (arena->type) {
case ARENA_VIRTUAL: arena__free_virtual(arena); break;
case ARENA_MALLOC: arena__free_malloc(arena); break;
// ARENA_STATIC does not need to be freed
case ARENA_STATIC: break;
}
memset(arena, 0, sizeof(arena_t));
}
arena_t arenaScratch(arena_t *arena) {
if (!arena) {
return (arena_t){0};
}
return (arena_t) {
.start = arena->current,
.current = arena->current,
.end = arena->end,
.type = arena->type,
};
}
void *arenaAlloc(const arena_alloc_desc_t *desc) {
if (!desc || !desc->arena) {
return NULL;
}
usize total = desc->size * desc->count;
arena_t *arena = desc->arena;
arena->current = (byte *)arena__align((uintptr_t)arena->current, desc->align);
if (total > arenaRemaining(arena)) {
if (desc->flags & ALLOC_SOFT_FAIL) {
return NULL;
}
printf("finished space in arena, tried to allocate %zu bytes out of %zu\n", total, arenaRemaining(arena));
abort();
}
if (arena->type == ARENA_VIRTUAL) {
usize allocated = arenaTell(arena);
usize page_end = vmemPadToPage(allocated);
usize new_cur = allocated + total;
if (new_cur > page_end) {
usize extra_mem = vmemPadToPage(new_cur - page_end);
usize page_size = vmemGetPageSize();
// TODO is this really correct?
usize num_of_pages = (extra_mem / page_size) + 1;
assert(num_of_pages > 0);
if (!vmemCommit(arena->current, num_of_pages + 1)) {
if (desc->flags & ALLOC_SOFT_FAIL) {
return NULL;
}
printf("failed to commit memory for virtual arena, tried to commit %zu pages\n", num_of_pages);
exit(1);
}
}
}
byte *ptr = arena->current;
arena->current += total;
if (desc->flags & ALLOC_NOZERO) return ptr;
memset(ptr, 0, total);
return ptr;
return desc->flags & ALLOC_NOZERO ? ptr : memset(ptr, 0, total);
}
usize arenaTell(arena_t *arena) {
return arena ? arena->current - arena->start : 0;
}
usize arenaRemaining(arena_t *arena) {
return arena && (arena->current < arena->end) ? arena->end - arena->current : 0;
}
void arenaRewind(arena_t *arena, usize from_start) {
if (!arena) {
return;
}
assert(arenaTell(arena) >= from_start);
arena->current = arena->start + from_start;
}
void arenaPop(arena_t *arena, usize amount) {
if (!arena) {
return;
}
usize position = arenaTell(arena);
if (!position) {
return;
}
arenaRewind(arena, position - amount);
}
// == VIRTUAL ARENA ====================================================================================================
static arena_t arena__make_virtual(usize size) {
usize alloc_size = 0;
byte *ptr = vmemInit(size, &alloc_size);
if (!vmemCommit(ptr, 1)) {
vmemRelease(ptr);
ptr = NULL;
}
return (arena_t){
.start = ptr,
.current = ptr,
.end = ptr ? ptr + alloc_size : NULL,
.type = ARENA_VIRTUAL,
};
}
static void arena__free_virtual(arena_t *arena) {
if (!arena->start) {
return;
}
bool success = vmemRelease(arena->start);
assert(success && "Failed arena free");
}
// == MALLOC ARENA =====================================================================================================
static arena_t arena__make_malloc(usize size) {
byte *ptr = malloc(size);
assert(ptr);
return (arena_t) {
.start = ptr,
.current = ptr,
.end = ptr ? ptr + size : NULL,
.type = ARENA_MALLOC,
};
}
static void arena__free_malloc(arena_t *arena) {
free(arena->start);
}
// == STATIC ARENA =====================================================================================================
static arena_t arena__make_static(byte *buf, usize len) {
return (arena_t) {
.start = buf,
.current = buf,
.end = buf ? buf + len : NULL,
.type = ARENA_STATIC,
};
}

57
colla/arena.h Normal file
View file

@ -0,0 +1,57 @@
#pragma once
#include "collatypes.h"
#define alignof _Alignof
typedef enum {
ARENA_VIRTUAL,
ARENA_MALLOC,
ARENA_STATIC,
} arena_type_e;
typedef enum {
ALLOC_FLAGS_NONE = 0,
ALLOC_NOZERO = 1 << 0,
ALLOC_SOFT_FAIL = 1 << 1,
} alloc_flags_e;
typedef struct arena_t {
uint8 *start;
uint8 *current;
uint8 *end;
arena_type_e type;
} arena_t;
typedef struct {
arena_type_e type;
usize allocation;
byte *static_buffer;
} arena_desc_t;
typedef struct {
arena_t *arena;
usize count;
alloc_flags_e flags;
usize size;
usize align;
} arena_alloc_desc_t;
#define KB(count) ( (count) * 1024)
#define MB(count) (KB(count) * 1024)
#define GB(count) (MB(count) * 1024)
// arena_type_e type, usize allocation, [ byte *static_buffer ]
#define arenaMake(...) arenaInit(&(arena_desc_t){ __VA_ARGS__ })
// arena_t *arena, T type, [ usize count, alloc_flags_e flags, usize size, usize align ]
#define alloc(arenaptr, type, ...) arenaAlloc(&(arena_alloc_desc_t){ .size = sizeof(type), .count = 1, .align = alignof(type), .arena = arenaptr, __VA_ARGS__ })
arena_t arenaInit(const arena_desc_t *desc);
void arenaCleanup(arena_t *arena);
void *arenaAlloc(const arena_alloc_desc_t *desc);
usize arenaTell(arena_t *arena);
usize arenaRemaining(arena_t *arena);
void arenaRewind(arena_t *arena, usize from_start);
void arenaPop(arena_t *arena, usize amount);

100
colla/base64.c Normal file
View file

@ -0,0 +1,100 @@
#include "base64.h"
#include "warnings/colla_warn_beg.h"
#include "arena.h"
static char encoding_table[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'
};
static uint8 decoding_table[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0,
0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
44, 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
buffer_t base64Encode(arena_t *arena, buffer_t buffer) {
usize outlen = ((buffer.len + 2) / 3) * 4;
uint8 *out = alloc(arena, uint8, outlen);
for (usize i = 0, j = 0; i < buffer.len;) {
uint32 a = i < buffer.len ? buffer.data[i++] : 0;
uint32 b = i < buffer.len ? buffer.data[i++] : 0;
uint32 c = i < buffer.len ? buffer.data[i++] : 0;
uint32 triple = (a << 16) | (b << 8) | c;
out[j++] = encoding_table[(triple >> 18) & 0x3F];
out[j++] = encoding_table[(triple >> 12) & 0x3F];
out[j++] = encoding_table[(triple >> 6) & 0x3F];
out[j++] = encoding_table[(triple >> 0) & 0x3F];
}
usize mod = buffer.len % 3;
if (mod) {
mod = 3 - mod;
for (usize i = 0; i < mod; ++i) {
out[outlen - 1 - i] = '=';
}
}
return (buffer_t){
.data = out,
.len = outlen
};
}
buffer_t base64Decode(arena_t *arena, buffer_t buffer) {
uint8 *out = arena->current;
usize start = arenaTell(arena);
for (usize i = 0; i < buffer.len; i += 4) {
uint8 a = decoding_table[buffer.data[i + 0]];
uint8 b = decoding_table[buffer.data[i + 1]];
uint8 c = decoding_table[buffer.data[i + 2]];
uint8 d = decoding_table[buffer.data[i + 3]];
uint32 triple =
((uint32)a << 18) |
((uint32)b << 12) |
((uint32)c << 6) |
((uint32)d);
uint8 *bytes = alloc(arena, uint8, 3);
bytes[0] = (triple >> 16) & 0xFF;
bytes[1] = (triple >> 8) & 0xFF;
bytes[2] = (triple >> 0) & 0xFF;
}
usize outlen = arenaTell(arena) - start;
return (buffer_t){
.data = out,
.len = outlen,
};
}
#include "warnings/colla_warn_end.h"

9
colla/base64.h Normal file
View file

@ -0,0 +1,9 @@
#pragma once
#include "collatypes.h"
#include "str.h"
typedef struct arena_t arena_t;
buffer_t base64Encode(arena_t *arena, buffer_t buffer);
buffer_t base64Decode(arena_t *arena, buffer_t buffer);

79
colla/colladefines.h Normal file
View file

@ -0,0 +1,79 @@
#pragma once
#define arrlen(a) (sizeof(a) / sizeof((a)[0]))
#if defined(_DEBUG) || !defined(NDEBUG)
#define COLLA_DEBUG 1
#define COLLA_RELEASE 0
#else
#define COLLA_DEBUG 0
#define COLLA_RELEASE 1
#endif
#if defined(_WIN32)
#define COLLA_WIN 1
#define COLLA_OSX 0
#define COLLA_LIN 0
#elif defined(__APPLE__)
#define COLLA_WIN 0
#define COLLA_OSX 1
#define COLLA_LIN 0
#elif defined(__linux__)
#define COLLA_WIN 0
#define COLLA_OSX 0
#define COLLA_LIN 1
#endif
#if defined(__clang__)
#define COLLA_CLANG 1
#define COLLA_MSVC 0
#define COLLA_TCC 0
#define COLLA_GCC 0
#elif defined(_MSC_VER)
#define COLLA_CLANG 0
#define COLLA_MSVC 1
#define COLLA_TCC 0
#define COLLA_GCC 0
#elif defined(__TINYC__)
#define COLLA_CLANG 0
#define COLLA_MSVC 0
#define COLLA_TCC 1
#define COLLA_GCC 0
#elif defined(__GNUC__)
#define COLLA_CLANG 0
#define COLLA_MSVC 0
#define COLLA_TCC 0
#define COLLA_GCC 1
#endif
#if COLLA_CLANG
#define COLLA_CMT_LIB 0
#elif COLLA_MSVC
#define COLLA_CMT_LIB 1
#elif COLLA_TCC
#define COLLA_CMT_LIB 1
#elif COLLA_GCC
#define COLLA_CMT_LIB 0
#endif

View file

@ -2,6 +2,9 @@
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include "colladefines.h"
typedef unsigned char uchar;
typedef unsigned short ushort;
@ -19,3 +22,16 @@ typedef int64_t int64;
typedef size_t usize;
typedef ptrdiff_t isize;
typedef uint8 byte;
typedef struct {
uint8 *data;
usize len;
} buffer_t;
#if COLLA_WIN && defined(UNICODE)
typedef wchar_t TCHAR;
#else
typedef char TCHAR;
#endif

View file

@ -1,13 +1,23 @@
#include "cthreads.h"
#include <stdlib.h>
typedef struct {
cthread_func_t func;
void *arg;
} _thr_internal_t;
#ifdef _WIN32
#include "win32_slim.h"
#include <stdlib.h>
#if COLLA_WIN
#define WIN32_LEAN_AND_MEAN
#include <handleapi.h>
#include <processthreadsapi.h>
#include <synchapi.h>
#undef INFINITE
#undef WAIT_FAILED
// couple of defines to avoid including windows.h
#define INFINITE 0xFFFFFFFF // Infinite timeout
#define WAIT_FAILED ((DWORD)0xFFFFFFFF)
// == THREAD ===========================================
@ -21,7 +31,7 @@ static DWORD _thrFuncInternal(void *arg) {
cthread_t thrCreate(cthread_func_t func, void *arg) {
HANDLE thread = INVALID_HANDLE_VALUE;
_thr_internal_t *params = malloc(sizeof(_thr_internal_t));
_thr_internal_t *params = calloc(1, sizeof(_thr_internal_t));
if(params) {
params->func = func;
@ -68,7 +78,7 @@ bool thrJoin(cthread_t ctx, int *code) {
// == MUTEX ============================================
cmutex_t mtxInit(void) {
CRITICAL_SECTION *crit_sec = malloc(sizeof(CRITICAL_SECTION));
CRITICAL_SECTION *crit_sec = calloc(1, sizeof(CRITICAL_SECTION));
if(crit_sec) {
InitializeCriticalSection(crit_sec);
}
@ -100,10 +110,10 @@ bool mtxUnlock(cmutex_t ctx) {
// == CONDITION VARIABLE ===============================
#include <tracelog.h>
#include "tracelog.h"
condvar_t condInit(void) {
CONDITION_VARIABLE *cond = malloc(sizeof(CONDITION_VARIABLE));
CONDITION_VARIABLE *cond = calloc(1, sizeof(CONDITION_VARIABLE));
InitializeConditionVariable(cond);
return (condvar_t)cond;
}
@ -121,7 +131,7 @@ void condWakeAll(condvar_t cond) {
}
void condWait(condvar_t cond, cmutex_t mtx) {
BOOL res = SleepConditionVariableCS((CONDITION_VARIABLE *)cond, (CRITICAL_SECTION *)mtx, INFINITE);
SleepConditionVariableCS((CONDITION_VARIABLE *)cond, (CRITICAL_SECTION *)mtx, INFINITE);
}
void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) {
@ -130,7 +140,6 @@ void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) {
#else
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
@ -150,7 +159,7 @@ static void *_thrFuncInternal(void *arg) {
cthread_t thrCreate(cthread_func_t func, void *arg) {
pthread_t handle = (pthread_t)NULL;
_thr_internal_t *params = malloc(sizeof(_thr_internal_t));
_thr_internal_t *params = calloc(1, sizeof(_thr_internal_t));
if(params) {
params->func = func;
@ -195,7 +204,7 @@ bool thrJoin(cthread_t ctx, int *code) {
// == MUTEX ============================================
cmutex_t mtxInit(void) {
pthread_mutex_t *mutex = malloc(sizeof(pthread_mutex_t));
pthread_mutex_t *mutex = calloc(1, sizeof(pthread_mutex_t));
if(mutex) {
if(pthread_mutex_init(mutex, NULL)) {
@ -230,7 +239,7 @@ bool mtxUnlock(cmutex_t ctx) {
// == CONDITION VARIABLE ===============================
condvar_t condInit(void) {
pthread_cond_t *cond = malloc(sizeof(pthread_cond_t));
pthread_cond_t *cond = calloc(1, sizeof(pthread_cond_t));
if(cond) {
if(pthread_cond_init(cond, NULL)) {

View file

@ -1,11 +1,6 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
#include "collatypes.h"
// == THREAD ===========================================
@ -37,31 +32,6 @@ bool mtxLock(cmutex_t ctx);
bool mtxTryLock(cmutex_t ctx);
bool mtxUnlock(cmutex_t ctx);
#ifdef __cplusplus
// small c++ class to make mutexes easier to use
struct lock_t {
inline lock_t(cmutex_t mutex)
: mutex(mutex) {
if (mtxValid(mutex)) {
mtxLock(mutex);
}
}
inline ~lock_t() {
unlock();
}
inline void unlock() {
if (mtxValid(mutex)) {
mtxUnlock(mutex);
}
mutex = 0;
}
cmutex_t mutex;
};
#endif
// == CONDITION VARIABLE ===============================
typedef uintptr_t condvar_t;
@ -76,7 +46,3 @@ void condWakeAll(condvar_t cond);
void condWait(condvar_t cond, cmutex_t mtx);
void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds);
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -1,311 +1,288 @@
#include "file.h"
#include "warnings/colla_warn_beg.h"
#include "tracelog.h"
#include "format.h"
#ifdef _WIN32
#include "win32_slim.h"
#include <stdlib.h>
#if COLLA_WIN
static DWORD _toWin32Access(int mode) {
if(mode & FILE_READ) return GENERIC_READ;
if(mode & FILE_WRITE) return GENERIC_WRITE;
if(mode & FILE_BOTH) return GENERIC_READ | GENERIC_WRITE;
fatal("unrecognized access mode: %d", mode);
return 0;
#define WIN32_LEAN_AND_MEAN
#include <fileapi.h>
#include <handleapi.h>
#undef FILE_BEGIN
#undef FILE_CURRENT
#undef FILE_END
#define FILE_BEGIN 0
#define FILE_CURRENT 1
#define FILE_END 2
static DWORD file__mode_to_access(filemode_e mode) {
if (mode & FILE_APPEND) return FILE_APPEND_DATA;
DWORD out = 0;
if (mode & FILE_READ) out |= GENERIC_READ;
if (mode & FILE_WRITE) out |= GENERIC_WRITE;
return out;
}
static DWORD _toWin32Creation(filemode_t mode) {
if(mode & FILE_READ) return OPEN_EXISTING;
if(mode == (FILE_WRITE | FILE_CLEAR)) return CREATE_ALWAYS;
if(mode & FILE_WRITE) return OPEN_ALWAYS;
if(mode & FILE_BOTH) return OPEN_ALWAYS;
fatal("unrecognized creation mode: %d", mode);
return 0;
static DWORD file__mode_to_creation(filemode_e mode) {
if (mode == FILE_READ) return OPEN_EXISTING;
if (mode == FILE_WRITE) return CREATE_ALWAYS;
return OPEN_ALWAYS;
}
bool fileExists(const char *fname) {
return GetFileAttributesA(fname) != INVALID_FILE_ATTRIBUTES;
bool fileExists(const char *name) {
return GetFileAttributesA(name) != INVALID_FILE_ATTRIBUTES;
}
file_t fileOpen(const char *fname, filemode_t mode) {
return (file_t)CreateFileA(
fname,
_toWin32Access(mode),
0,
NULL,
_toWin32Creation(mode),
FILE_ATTRIBUTE_NORMAL,
file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode) {
TCHAR long_path_prefix[] = TEXT("\\\\?\\");
const usize prefix_len = arrlen(long_path_prefix) - 1;
TCHAR *rel_path = strvToTChar(&scratch, name);
DWORD pathlen = GetFullPathName(rel_path, 0, NULL, NULL);
TCHAR *full_path = alloc(&scratch, TCHAR, pathlen + prefix_len + 1);
memcpy(full_path, long_path_prefix, prefix_len * sizeof(TCHAR));
GetFullPathName(rel_path, pathlen + 1, full_path + prefix_len, NULL);
HANDLE handle = CreateFile(
full_path,
file__mode_to_access(mode),
0,
NULL,
file__mode_to_creation(mode),
FILE_ATTRIBUTE_NORMAL,
NULL
);
return (file_t){
.handle = (uintptr_t)handle,
};
}
void fileClose(file_t ctx) {
if (ctx) {
CloseHandle((HANDLE)ctx);
if (!fileIsValid(ctx)) return;
CloseHandle((HANDLE)ctx.handle);
}
bool fileIsValid(file_t ctx) {
return (HANDLE)ctx.handle != 0 &&
(HANDLE)ctx.handle != INVALID_HANDLE_VALUE;
}
usize fileRead(file_t ctx, void *buf, usize len) {
if (!fileIsValid(ctx)) return 0;
DWORD read = 0;
ReadFile((HANDLE)ctx.handle, buf, len, &read, NULL);
return (usize)read;
}
usize fileWrite(file_t ctx, const void *buf, usize len) {
if (!fileIsValid(ctx)) return 0;
DWORD written = 0;
WriteFile((HANDLE)ctx.handle, buf, len, &written, NULL);
return (usize)written;
}
bool fileSeekEnd(file_t ctx) {
if (!fileIsValid(ctx)) return false;
DWORD result = SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_END);
return result != INVALID_SET_FILE_POINTER;
}
void fileRewind(file_t ctx) {
if (!fileIsValid(ctx)) return;
SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_BEGIN);
}
usize fileTell(file_t ctx) {
if (!fileIsValid(ctx)) return 0;
LARGE_INTEGER tell = {0};
BOOL result = SetFilePointerEx((HANDLE)ctx.handle, (LARGE_INTEGER){0}, &tell, FILE_CURRENT);
return result == TRUE ? (usize)tell.QuadPart : 0;
}
usize fileSize(file_t ctx) {
if (!fileIsValid(ctx)) return 0;
LARGE_INTEGER size = {0};
BOOL result = GetFileSizeEx((HANDLE)ctx.handle, &size);
return result == TRUE ? (usize)size.QuadPart : 0;
}
uint64 fileGetTimeFP(file_t ctx) {
if (!fileIsValid(ctx)) return 0;
FILETIME time = {0};
GetFileTime((HANDLE)ctx.handle, NULL, NULL, &time);
ULARGE_INTEGER utime = {
.HighPart = time.dwHighDateTime,
.LowPart = time.dwLowDateTime,
};
return (uint64)utime.QuadPart;
}
#else
#include <stdio.h>
static const char *file__mode_to_stdio(filemode_e mode) {
if (mode == FILE_READ) return "rb";
if (mode == FILE_WRITE) return "wb";
if (mode == FILE_APPEND) return "ab";
if (mode == (FILE_READ | FILE_WRITE)) return "rb+";
return "ab+";
}
bool fileExists(const char *name) {
FILE *fp = fopen(name, "rb");
bool exists = fp != NULL;
fclose(fp);
return exists;
}
file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode) {
str_t filename = str(&scratch, name);
return (file_t) {
.handle = (uintptr_t)fopen(filename.buf, file__mode_to_stdio(mode))
};
}
void fileClose(file_t ctx) {
FILE *fp = (FILE *)ctx.handle;
if (fp) {
fclose(fp);
}
}
bool fileIsValid(file_t ctx) {
return (HANDLE)ctx != INVALID_HANDLE_VALUE;
return (FILE *)ctx.handle != NULL;
}
usize fileRead(file_t ctx, void *buf, usize len) {
if (!fileIsValid(ctx)) return 0;
return fread(buf, 1, len, (FILE *)ctx.handle);
}
usize fileWrite(file_t ctx, const void *buf, usize len) {
if (!fileIsValid(ctx)) return 0;
return fwrite(buf, 1, len, (FILE *)ctx.handle);
}
bool fileSeekEnd(file_t ctx) {
if (!fileIsValid(ctx)) return false;
return fseek((FILE *)ctx.handle, 0, SEEK_END) == 0;
}
void fileRewind(file_t ctx) {
if (!fileIsValid(ctx)) return;
return fseek((FILE *)ctx.handle, 0, SEEK_SET) == 0;
}
usize fileTell(file_t ctx) {
if (!fileIsValid(ctx)) return 0;
return ftell((FILE *)ctx.handle);
}
usize fileSize(file_t ctx) {
if (!fileIsValid(ctx)) return 0;
FILE *fp = (FILE *)ctx.handle;
fseek(fp, 0, SEEK_END);
long len = ftell(fp);
fseek(fp, 0, SEEK_SET);
return (usize)len;
}
uint64 fileGetTimeFP(file_t ctx) {
#if COLLA_LIN
#else
fatal("fileGetTime not implemented yet outside of linux and windows");
return 0;
#endif
}
#endif
bool filePutc(file_t ctx, char c) {
return fileWrite(ctx, &c, 1) == 1;
}
bool filePuts(file_t ctx, const char *str) {
usize len = strlen(str);
return fileWrite(ctx, str, len) == len;
bool filePuts(file_t ctx, strview_t v) {
return fileWrite(ctx, v.buf, v.len) == v.len;
}
bool filePutstr(file_t ctx, str_t str) {
return fileWrite(ctx, str.buf, str.len) == str.len;
bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
bool result = filePrintfv(scratch, ctx, fmt, args);
va_end(args);
return result;
}
bool filePutview(file_t ctx, strview_t view) {
return fileWrite(ctx, view.buf, view.len) == view.len;
bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args) {
str_t string = strFmtv(&scratch, fmt, args);
return fileWrite(ctx, string.buf, string.len) == string.len;
}
usize fileRead(file_t ctx, void *buf, usize len) {
DWORD bytes_read = 0;
BOOL result = ReadFile((HANDLE)ctx, buf, (DWORD)len, &bytes_read, NULL);
return result == TRUE ? (usize)bytes_read : 0;
buffer_t fileReadWhole(arena_t *arena, arena_t scratch, strview_t name) {
return fileReadWholeFP(arena, fileOpen(scratch, name, FILE_READ));
}
usize fileWrite(file_t ctx, const void *buf, usize len) {
DWORD bytes_read = 0;
BOOL result = WriteFile((HANDLE)ctx, buf, (DWORD)len, &bytes_read, NULL);
return result == TRUE ? (usize)bytes_read : 0;
}
buffer_t fileReadWholeFP(arena_t *arena, file_t ctx) {
if (!fileIsValid(ctx)) return (buffer_t){0};
buffer_t out = {0};
bool fileSeekEnd(file_t ctx) {
return SetFilePointerEx((HANDLE)ctx, (LARGE_INTEGER){0}, NULL, FILE_END) == TRUE;
}
out.len = fileSize(ctx);
out.data = alloc(arena, uint8, out.len);
usize read = fileRead(ctx, out.data, out.len);
void fileRewind(file_t ctx) {
SetFilePointerEx((HANDLE)ctx, (LARGE_INTEGER){0}, NULL, FILE_BEGIN);
}
uint64 fileTell(file_t ctx) {
LARGE_INTEGER tell;
BOOL result = SetFilePointerEx((HANDLE)ctx, (LARGE_INTEGER){0}, &tell, FILE_CURRENT);
return result == TRUE ? (uint64)tell.QuadPart : 0;
}
uint64 fileGetTime(file_t ctx) {
uint64 fp_time = 0;
GetFileTime((HANDLE)ctx, NULL, NULL, (FILETIME *)&fp_time);
return fp_time;
}
#else
#include <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);
}
uint64 fileGetTime(file_t ctx) {
}
#endif
static str_t _readWholeInternalStr(file_t ctx) {
str_t contents = strInit();
uint64 fsize = 0;
usize read = 0;
if(!fileSeekEnd(ctx)) {
err("file: couldn't read until end");
goto failed;
if (read != out.len) {
err("fileReadWholeFP: fileRead failed, should be %zu but is %zu", out.len, read);
arenaPop(arena, out.len);
return (buffer_t){0};
}
fsize = fileTell(ctx);
fileRewind(ctx);
contents.buf = (char *)malloc(fsize + 1);
contents.len = fsize;
if(!contents.buf) {
err("file: couldn't allocate buffer");
goto failed;
}
read = fileRead(ctx, contents.buf, fsize);
if(read != fsize) {
err("file: read wrong amount of bytes: %zu instead of %zu", read, fsize);
goto failed_free;
}
contents.buf[contents.len] = '\0';
failed:
return contents;
failed_free:
strFree(contents);
return strInit();
return out;
}
static vec(uint8) _readWholeInternalVec(file_t ctx) {
vec(uint8) contents = NULL;
uint64 fsize = 0;
usize read = 0;
str_t fileReadWholeStr(arena_t *arena, arena_t scratch, strview_t name) {
return fileReadWholeStrFP(arena, fileOpen(scratch, name, FILE_READ));
}
if(!fileSeekEnd(ctx)) {
err("file: couldn't read until end");
goto failed;
str_t fileReadWholeStrFP(arena_t *arena, file_t ctx) {
if (!fileIsValid(ctx)) return (str_t){0};
str_t out = {0};
out.len = fileSize(ctx);
out.buf = alloc(arena, uint8, out.len + 1);
usize read = fileRead(ctx, out.buf, out.len);
if (read != out.len) {
err("fileReadWholeFP: fileRead failed, should be %zu but is %zu", out.len, read);
arenaPop(arena, out.len + 1);
return (str_t){0};
}
fsize = fileTell(ctx);
fileRewind(ctx);
vecReserve(contents, fsize);
if(!contents) {
err("file: couldn't allocate buffer");
goto failed;
}
read = fileRead(ctx, contents, fsize);
if(read != fsize) {
err("file: read wrong amount of bytes: %zu instead of %zu", read, fsize);
goto failed_free;
}
_veclen(contents) = read;
failed:
return contents;
failed_free:
vecFree(contents);
return contents;
return out;
}
vec(uint8) fileReadWhole(const char *fname) {
file_t fp = fileOpen(fname, FILE_READ);
if (!fileIsValid(fp)) return NULL;
vec(uint8) contents = fileReadWholeFP(fp);
fileClose(fp);
return contents;
}
vec(uint8) fileReadWholeFP(file_t ctx) {
return _readWholeInternalVec(ctx);
}
str_t fileReadWholeText(const char *fname) {
file_t fp = fileOpen(fname, FILE_READ);
if(!fileIsValid(fp)) {
err("couldn't open file %s", fname);
return strInit();
}
str_t contents = fileReadWholeTextFP(fp);
fileClose(fp);
return contents;
}
str_t fileReadWholeTextFP(file_t ctx) {
return _readWholeInternalStr(ctx);
}
bool fileWriteWhole(const char *fname, filebuf_t data) {
file_t fp = fileOpen(fname, FILE_WRITE);
bool fileWriteWhole(arena_t scratch, strview_t name, const void *buf, usize len) {
file_t fp = fileOpen(scratch, name, FILE_WRITE);
if (!fileIsValid(fp)) {
err("couldn't open file %s", fname);
return false;
}
bool res = fileWriteWholeFP(fp, data);
usize written = fileWrite(fp, buf, len);
fileClose(fp);
return res;
return written == len;
}
bool fileWriteWholeFP(file_t ctx, filebuf_t data) {
usize written = fileWrite(ctx, data.buf, data.len);
return written == data.len;
uint64 fileGetTime(arena_t scratch, strview_t name) {
return fileGetTimeFP(fileOpen(scratch, name, FILE_READ));
}
bool fileWriteWholeText(const char *fname, strview_t string) {
file_t fp = fileOpen(fname, FILE_WRITE);
if (!fileIsValid(fp)) {
err("couldn't open file %s", fname);
return false;
}
bool res = fileWriteWholeTextFP(fp, string);
fileClose(fp);
return res;
}
bool fileWriteWholeTextFP(file_t ctx, strview_t string) {
return fileWriteWholeFP(ctx, (filebuf_t){ (uint8 *)string.buf, string.len });
}
uint64 fileGetTimePath(const char *path) {
file_t fp = fileOpen(path, FILE_READ);
if (!fileIsValid(fp)) {
return 0;
}
uint64 fp_time = fileGetTime(fp);
fileClose(fp);
return fp_time;
}
#include "warnings/colla_warn_end.h"

View file

@ -1,38 +1,32 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdarg.h>
#include "collatypes.h"
#include "str.h"
#include "vec.h"
#include "arena.h"
typedef enum {
FILE_READ = 1 << 0,
FILE_WRITE = 1 << 1,
FILE_CLEAR = 1 << 2,
FILE_BOTH = 1 << 3
} filemode_t;
typedef uintptr_t file_t;
FILE_READ = 1 << 0,
FILE_WRITE = 1 << 1,
FILE_APPEND = 1 << 2,
} filemode_e;
typedef struct {
const uint8 *buf;
usize len;
} filebuf_t;
uintptr_t handle;
} file_t;
bool fileExists(const char *fname);
bool fileExists(const char *name);
file_t fileOpen(const char *fname, filemode_t mode);
file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode);
void fileClose(file_t ctx);
bool fileIsValid(file_t ctx);
bool filePutc(file_t ctx, char c);
bool filePuts(file_t ctx, const char *str);
bool filePutstr(file_t ctx, str_t str);
bool filePutview(file_t ctx, strview_t view);
bool filePuts(file_t ctx, strview_t v);
bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...);
bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args);
usize fileRead(file_t ctx, void *buf, usize len);
usize fileWrite(file_t ctx, const void *buf, usize len);
@ -40,23 +34,16 @@ usize fileWrite(file_t ctx, const void *buf, usize len);
bool fileSeekEnd(file_t ctx);
void fileRewind(file_t ctx);
uint64 fileTell(file_t ctx);
usize fileTell(file_t ctx);
usize fileSize(file_t ctx);
vec(uint8) fileReadWhole(const char *fname);
vec(uint8) fileReadWholeFP(file_t ctx);
buffer_t fileReadWhole(arena_t *arena, arena_t scratch, strview_t name);
buffer_t fileReadWholeFP(arena_t *arena, file_t ctx);
str_t fileReadWholeText(const char *fname);
str_t fileReadWholeTextFP(file_t ctx);
str_t fileReadWholeStr(arena_t *arena, arena_t scratch, strview_t name);
str_t fileReadWholeStrFP(arena_t *arena, file_t ctx);
bool fileWriteWhole(const char *fname, filebuf_t data);
bool fileWriteWholeFP(file_t ctx, filebuf_t data);
bool fileWriteWhole(arena_t scratch, strview_t name, const void *buf, usize len);
bool fileWriteWholeText(const char *fname, strview_t string);
bool fileWriteWholeTextFP(file_t ctx, strview_t string);
uint64 fileGetTime(file_t ctx);
uint64 fileGetTimePath(const char *path);
#ifdef __cplusplus
} // extern "C"
#endif
uint64 fileGetTime(arena_t scratch, strview_t name);
uint64 fileGetTimeFP(file_t ctx);

60
colla/format.c Normal file
View file

@ -0,0 +1,60 @@
#include "format.h"
#define STB_SPRINTF_DECORATE(name) stb_##name
#define STB_SPRINTF_NOUNALIGNED
#define STB_SPRINTF_IMPLEMENTATION
#include "stb/stb_sprintf.h"
#include "arena.h"
static char *fmt__stb_callback(const char *buf, void *ud, int len) {
printf("%s", buf);
return (char *)ud;
}
int fmtPrint(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
int out = fmtPrintv(fmt, args);
va_end(args);
return out;
}
int fmtPrintv(const char *fmt, va_list args) {
char buffer[STB_SPRINTF_MIN];
return stb_vsprintfcb(fmt__stb_callback, buffer, buffer, fmt, args);
}
int fmtBuffer(char *buffer, usize buflen, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
int out = fmtBufferv(buffer, buflen, fmt, args);
va_end(args);
return out;
}
int fmtBufferv(char *buffer, usize buflen, const char *fmt, va_list args) {
return stb_vsnprintf(buffer, (int)buflen, fmt, args);
}
#if 0
str_t fmtStr(Arena *arena, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
str_t out = fmtStrv(arena, fmt, args);
va_end(args);
return out;
}
str_t fmtStrv(Arena *arena, const char *fmt, va_list args) {
va_list vcopy;
va_copy(vcopy, args);
int len = stb_vsnprintf(NULL, 0, fmt, vcopy);
va_end(vcopy);
char *buffer = alloc(arena, char, len + 1);
stb_vsnprintf(buffer, len + 1, fmt, args);
return (str_t){ .buf = buffer, .len = (usize)len };
}
#endif

11
colla/format.h Normal file
View file

@ -0,0 +1,11 @@
#pragma once
#include <stdarg.h>
typedef struct arena_t arena_t;
int fmtPrint(const char *fmt, ...);
int fmtPrintv(const char *fmt, va_list args);
int fmtBuffer(char *buffer, usize buflen, const char *fmt, ...);
int fmtBufferv(char *buffer, usize buflen, const char *fmt, va_list args);

View file

@ -1,474 +1,507 @@
#include "http.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "warnings/colla_warn_beg.h"
// #include "os.h"
#include "arena.h"
#include "strstream.h"
#include "format.h"
#include "socket.h"
#include "tracelog.h"
#include "vec.h"
#if COLLA_WIN
#if COLLA_CMT_LIB
#pragma comment(lib, "Wininet")
#endif
#ifdef _WIN32
#define stricmp _stricmp
#else
#include <strings.h> // strcasecmp
#define stricmp strcasecmp
#include <windows.h>
#include <wininet.h>
#endif
// == INTERNAL ================================================================
static const TCHAR *https__get_method_str(http_method_e method);
static void _setField(vec(http_field_t) *fields_vec, const char *key, const char *value) {
vec(http_field_t) fields = *fields_vec;
for (uint32 i = 0; i < vecLen(fields); ++i) {
if (stricmp(fields[i].key, key) == 0) {
char **curval = &fields[i].value;
usize curlen = strlen(*curval);
usize newlen = strlen(value);
if(newlen > curlen) {
*curval = (char *)realloc(*curval, newlen + 1);
}
memcpy(*curval, value, newlen);
(*curval)[newlen] = '\0';
return;
}
}
// otherwise, add it to the list
http_field_t field;
usize klen = strlen(key);
usize vlen = strlen(value);
field.key = (char *)malloc(klen + 1);
field.value = (char *)malloc(vlen + 1);
memcpy(field.key, key, klen);
memcpy(field.value, value, vlen);
field.key[klen] = field.value[vlen] = '\0';
vecAppend(*fields_vec, field);
}
static void _parseFields(vec(http_field_t) *fields, str_istream_t *in) {
strview_t line;
static http_header_t *http__parse_headers(arena_t *arena, instream_t *in) {
http_header_t *head = NULL;
strview_t line = (strview_t){0};
do {
line = istrGetview(in, '\r');
line = istrGetView(in, '\r');
usize pos = strvFind(line, ':', 0);
if(pos != STRV_NOT_FOUND) {
strview_t key = strvSub(line, 0, pos);
strview_t value = strvSub(line, pos + 2, SIZE_MAX);
if (pos != STR_NONE) {
http_header_t *h = alloc(arena, http_header_t);
char *key_str = NULL;
char *value_str = NULL;
key_str = strvCopy(key).buf;
value_str = strvCopy(value).buf;
_setField(fields, key_str, value_str);
free(key_str);
free(value_str);
h->key = strvSub(line, 0, pos);
h->value = strvSub(line, pos + 2, SIZE_MAX);
h->next = head;
head = h;
}
istrSkip(in, 2); // skip \r\n
} while(line.len > 2);
} while (line.len > 2); // while line != "\r\n"
return head;
}
// == HTTP STATUS =============================================================
const char *httpGetStatusString(resstatus_t status) {
const char *httpGetStatusString(int status) {
switch (status) {
case STATUS_OK: return "OK";
case STATUS_CREATED: return "CREATED";
case STATUS_ACCEPTED: return "ACCEPTED";
case STATUS_NO_CONTENT: return "NO CONTENT";
case STATUS_RESET_CONTENT: return "RESET CONTENT";
case STATUS_PARTIAL_CONTENT: return "PARTIAL CONTENT";
case STATUS_MULTIPLE_CHOICES: return "MULTIPLE CHOICES";
case STATUS_MOVED_PERMANENTLY: return "MOVED PERMANENTLY";
case STATUS_MOVED_TEMPORARILY: return "MOVED TEMPORARILY";
case STATUS_NOT_MODIFIED: return "NOT MODIFIED";
case STATUS_BAD_REQUEST: return "BAD REQUEST";
case STATUS_UNAUTHORIZED: return "UNAUTHORIZED";
case STATUS_FORBIDDEN: return "FORBIDDEN";
case STATUS_NOT_FOUND: return "NOT FOUND";
case STATUS_RANGE_NOT_SATISFIABLE: return "RANGE NOT SATISFIABLE";
case STATUS_INTERNAL_SERVER_ERROR: return "INTERNAL SERVER_ERROR";
case STATUS_NOT_IMPLEMENTED: return "NOT IMPLEMENTED";
case STATUS_BAD_GATEWAY: return "BAD GATEWAY";
case STATUS_SERVICE_NOT_AVAILABLE: return "SERVICE NOT AVAILABLE";
case STATUS_GATEWAY_TIMEOUT: return "GATEWAY TIMEOUT";
case STATUS_VERSION_NOT_SUPPORTED: return "VERSION NOT SUPPORTED";
case 200: return "OK";
case 201: return "CREATED";
case 202: return "ACCEPTED";
case 204: return "NO CONTENT";
case 205: return "RESET CONTENT";
case 206: return "PARTIAL CONTENT";
case 300: return "MULTIPLE CHOICES";
case 301: return "MOVED PERMANENTLY";
case 302: return "MOVED TEMPORARILY";
case 304: return "NOT MODIFIED";
case 400: return "BAD REQUEST";
case 401: return "UNAUTHORIZED";
case 403: return "FORBIDDEN";
case 404: return "NOT FOUND";
case 407: return "RANGE NOT SATISFIABLE";
case 500: return "INTERNAL SERVER_ERROR";
case 501: return "NOT IMPLEMENTED";
case 502: return "BAD GATEWAY";
case 503: return "SERVICE NOT AVAILABLE";
case 504: return "GATEWAY TIMEOUT";
case 505: return "VERSION NOT SUPPORTED";
}
return "UNKNOWN";
}
// == HTTP VERSION ============================================================
int httpVerNumber(http_version_t ver) {
return (ver.major * 10) + ver.minor;
}
// == HTTP REQUEST ============================================================
http_req_t httpParseReq(arena_t *arena, strview_t request) {
http_req_t req = {0};
instream_t in = istrInitLen(request.buf, request.len);
http_request_t reqInit() {
http_request_t req = {0};
reqSetUri(&req, strvInit(""));
req.version = (http_version_t){1, 1};
return req;
}
http_request_t reqParse(const char *request) {
http_request_t req = {0};
str_istream_t in = istrInit(request);
// get data
strview_t method = strvTrim(istrGetview(&in, '/'));
strview_t method = strvTrim(istrGetView(&in, '/'));
istrSkip(&in, 1); // skip /
strview_t page = strvTrim(istrGetview(&in, ' '));
strview_t http = strvTrim(istrGetview(&in, '\n'));
req.url = strvTrim(istrGetView(&in, ' '));
strview_t http = strvTrim(istrGetView(&in, '\n'));
istrSkip(&in, 1); // skip \n
_parseFields(&req.fields, &in);
strview_t body = strvTrim(istrGetviewLen(&in, 0, SIZE_MAX));
req.headers = http__parse_headers(arena, &in);
// parse data
req.body = strvTrim(istrGetViewLen(&in, SIZE_MAX));
// -- method
const char *methods[] = { "GET", "POST", "HEAD", "PUT", "DELETE" };
const int methods_count = sizeof(methods) / sizeof(*methods);
strview_t methods[] = { strv("GET"), strv("POST"), strv("HEAD"), strv("PUT"), strv("DELETE") };
usize methods_count = arrlen(methods);
for (int i = 0; i < methods_count; ++i) {
if (strvCompare(method, strvInit(methods[i])) == 0) {
req.method = (reqtype_t)i;
for (usize i = 0; i < methods_count; ++i) {
if (strvEquals(method, methods[i])) {
req.method = (http_method_e)i;
break;
}
}
// -- page
req.uri = strvCopy(page).buf;
// -- http
in = istrInitLen(http.buf, http.len);
istrIgnoreAndSkip(&in, '/'); // skip HTTP/
istrGetu8(&in, &req.version.major);
istrGetU8(&in, &req.version.major);
istrSkip(&in, 1); // skip .
istrGetu8(&in, &req.version.minor);
// -- body
req.body = strvCopy(body).buf;
istrGetU8(&in, &req.version.minor);
return req;
}
void reqFree(http_request_t ctx) {
for (http_field_t *it = ctx.fields; it != vecEnd(ctx.fields); ++it) {
free(it->key);
free(it->value);
}
vecFree(ctx.fields);
free(ctx.uri);
free(ctx.body);
}
http_res_t httpParseRes(arena_t *arena, strview_t response) {
http_res_t res = {0};
instream_t in = istrInitLen(response.buf, response.len);
bool reqHasField(http_request_t *ctx, const char *key) {
for(uint32 i = 0; i < vecLen(ctx->fields); ++i) {
if(stricmp(ctx->fields[i].key, key) == 0) {
return true;
}
}
return false;
}
void reqSetField(http_request_t *ctx, const char *key, const char *value) {
_setField(&ctx->fields, key, value);
}
void reqSetUri(http_request_t *ctx, strview_t uri) {
if (strvIsEmpty(uri)) return;
free(ctx->uri);
if (uri.buf[0] == '/') {
strvRemovePrefix(uri, 1);
}
ctx->uri = strvCopy(uri).buf;
}
str_ostream_t reqPrepare(http_request_t *ctx) {
str_ostream_t out = ostrInitLen(1024);
const char *method = NULL;
switch(ctx->method) {
case REQ_GET: method = "GET"; break;
case REQ_POST: method = "POST"; break;
case REQ_HEAD: method = "HEAD"; break;
case REQ_PUT: method = "PUT"; break;
case REQ_DELETE: method = "DELETE"; break;
default: err("unrecognized method: %d", method); goto error;
}
ostrPrintf(&out, "%s /%s HTTP/%hhu.%hhu\r\n",
method, ctx->uri, ctx->version.major, ctx->version.minor
);
for(uint32 i = 0; i < vecLen(ctx->fields); ++i) {
ostrPrintf(&out, "%s: %s\r\n", ctx->fields[i].key, ctx->fields[i].value);
}
ostrAppendview(&out, strvInit("\r\n"));
if(ctx->body) {
ostrAppendview(&out, strvInit(ctx->body));
}
error:
return out;
}
str_t reqString(http_request_t *ctx) {
str_ostream_t out = reqPrepare(ctx);
return ostrAsStr(out);
}
// == HTTP RESPONSE ===========================================================
http_response_t resParse(const char *data) {
http_response_t ctx = {0};
str_istream_t in = istrInit(data);
char hp[5];
istrGetstringBuf(&in, hp, 5);
if(stricmp(hp, "http") != 0) {
err("response doesn't start with 'HTTP', instead with %c%c%c%c", hp[0], hp[1], hp[2], hp[3]);
return ctx;
strview_t http = istrGetViewLen(&in, 5);
if (!strvEquals(http, strv("HTTP"))) {
err("response doesn't start with 'HTTP', instead with %v", http);
return (http_res_t){0};
}
istrSkip(&in, 1); // skip /
istrGetu8(&in, &ctx.version.major);
istrGetU8(&in, &res.version.major);
istrSkip(&in, 1); // skip .
istrGetu8(&in, &ctx.version.minor);
istrGeti32(&in, (int32*)&ctx.status_code);
istrGetU8(&in, &res.version.minor);
istrGetI32(&in, (int32*)&res.status_code);
istrIgnore(&in, '\n');
istrSkip(&in, 1); // skip \n
resParseFields(&ctx, &in);
res.headers = http__parse_headers(arena, &in);
const char *tran_encoding = resGetField(&ctx, "transfer-encoding");
if(tran_encoding == NULL || stricmp(tran_encoding, "chunked") != 0) {
strview_t body = istrGetviewLen(&in, 0, SIZE_MAX);
vecClear(ctx.body);
vecReserve(ctx.body, body.len);
memcpy(ctx.body, body.buf, body.len);
strview_t encoding = httpGetHeader(res.headers, strv("transfer-encoding"));
if (!strvEquals(encoding, strv("chunked"))) {
res.body = istrGetViewLen(&in, SIZE_MAX);
}
else {
// fatal("chunked encoding not implemented yet");
err("chunked encoding not implemented yet");
err("chunked encoding not implemented yet! body ignored");
}
return ctx;
}
void resFree(http_response_t ctx) {
for (http_field_t *it = ctx.fields; it != vecEnd(ctx.fields); ++it) {
free(it->key);
free(it->value);
}
vecFree(ctx.fields);
vecFree(ctx.body);
return res;
}
bool resHasField(http_response_t *ctx, const char *key) {
for(uint32 i = 0; i < vecLen(ctx->fields); ++i) {
if(stricmp(ctx->fields[i].key, key) == 0) {
str_t httpReqToStr(arena_t *arena, http_req_t *req) {
outstream_t out = ostrInit(arena);
const char *method = NULL;
switch (req->method) {
case HTTP_GET: method = "GET"; break;
case HTTP_POST: method = "POST"; break;
case HTTP_HEAD: method = "HEAD"; break;
case HTTP_PUT: method = "PUT"; break;
case HTTP_DELETE: method = "DELETE"; break;
default: err("unrecognised method: %d", method); return (str_t){0};
}
ostrPrintf(
&out,
"%s /%v HTTP/%hhu.%hhu\r\n",
method, req->url, req->version.major, req->version.minor
);
http_header_t *h = req->headers;
while (h) {
ostrPrintf(&out, "%v: %v\r\n", h->key, h->value);
h = h->next;
}
ostrPuts(&out, strv("\r\n"));
ostrPuts(&out, req->body);
return ostrAsStr(&out);
}
str_t httpResToStr(arena_t *arena, http_res_t *res) {
outstream_t out = ostrInit(arena);
ostrPrintf(
&out,
"HTTP/%hhu.%hhu %d %s\r\n",
res->version.major,
res->version.minor,
res->status_code,
httpGetStatusString(res->status_code)
);
ostrPuts(&out, strv("\r\n"));
ostrPuts(&out, res->body);
return ostrAsStr(&out);
}
bool httpHasHeader(http_header_t *headers, strview_t key) {
http_header_t *h = headers;
while (h) {
if (strvEquals(h->key, key)) {
return true;
}
h = h->next;
}
return false;
}
void resSetField(http_response_t *ctx, const char *key, const char *value) {
_setField(&ctx->fields, key, value);
void httpSetHeader(http_header_t *headers, strview_t key, strview_t value) {
http_header_t *h = headers;
while (h) {
if (strvEquals(h->key, key)) {
h->value = value;
break;
}
h = h->next;
}
}
const char *resGetField(http_response_t *ctx, const char *field) {
for(uint32 i = 0; i < vecLen(ctx->fields); ++i) {
if(stricmp(ctx->fields[i].key, field) == 0) {
return ctx->fields[i].value;
strview_t httpGetHeader(http_header_t *headers, strview_t key) {
http_header_t *h = headers;
while (h) {
if (strvEquals(h->key, key)) {
return h->value;
}
h = h->next;
}
return (strview_t){0};
}
str_t httpMakeUrlSafe(arena_t *arena, strview_t string) {
strview_t chars = strv(" !\"#$%%&'()*+,/:;=?@[]");
usize final_len = string.len;
// find final string length first
for (usize i = 0; i < string.len; ++i) {
if (strvContains(chars, string.buf[i])) {
final_len += 2;
}
}
return NULL;
}
void resParseFields(http_response_t *ctx, str_istream_t *in) {
_parseFields(&ctx->fields, in);
}
str_ostream_t resPrepare(http_response_t *ctx) {
str_ostream_t out = ostrInitLen(1024);
ostrPrintf(
&out, "HTTP/%hhu.%hhu %d %s\r\n",
ctx->version.major, ctx->version.minor,
ctx->status_code, httpGetStatusString(ctx->status_code)
);
for (http_field_t *field = ctx->fields; field != vecEnd(ctx->fields); ++field) {
ostrPrintf(&out, "%s: %s\r\n", field->key, field->value);
str_t out = {
.buf = alloc(arena, char, final_len + 1),
.len = final_len
};
usize cur = 0;
// substitute characters
for (usize i = 0; i < string.len; ++i) {
if (strvContains(chars, string.buf[i])) {
fmtBuffer(out.buf + cur, 4, "%%%X", string.buf[i]);
cur += 3;
}
else {
out.buf[cur++] = string.buf[i];
}
}
ostrPuts(&out, "\r\n");
ostrAppendview(&out, strvInitLen(ctx->body, vecLen(ctx->body)));
return out;
}
str_t resString(http_response_t *ctx) {
str_ostream_t out = resPrepare(ctx);
return ostrAsStr(out);
http_url_t httpSplitUrl(strview_t url) {
http_url_t out = {0};
if (strvStartsWithView(url, strv("https://"))) {
url = strvRemovePrefix(url, 8);
}
else if (strvStartsWithView(url, strv("http://"))) {
url = strvRemovePrefix(url, 7);
}
out.host = strvSub(url, 0, strvFind(url, '/', 0));
out.uri = strvSub(url, out.host.len, SIZE_MAX);
return out;
}
// == HTTP CLIENT =============================================================
http_res_t httpRequest(http_request_desc_t *request) {
usize arena_begin = arenaTell(request->arena);
http_client_t hcliInit() {
return (http_client_t) {
.port = 80,
http_req_t req = {
.version = (http_version_t){ 1, 1 },
.url = request->url,
.body = request->body,
.method = request->request_type,
};
}
void hcliFree(http_client_t ctx) {
strFree(ctx.host_name);
}
http_header_t *h = NULL;
void hcliSetHost(http_client_t *ctx, strview_t hostname) {
// if the hostname starts with http:// (case insensitive)
if(strvICompare(strvSub(hostname, 0, 7), strvInit("http://")) == 0) {
ctx->host_name = strvCopy(strvSub(hostname, 7, SIZE_MAX));
}
else if(strvICompare(strvSub(hostname, 0, 8), strvInit("https://")) == 0) {
err("HTTPS protocol not yet supported");
return;
}
else {
// undefined protocol, use HTTP
ctx->host_name = strvCopy(hostname);
}
}
http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *req) {
if (strBack(ctx->host_name) == '/') {
ctx->host_name.buf[--ctx->host_name.len] = '\0';
}
if(!reqHasField(req, "Host")) {
reqSetField(req, "Host", ctx->host_name.buf);
}
if(!reqHasField(req, "Content-Length")) {
if(req->body) {
str_ostream_t out = ostrInitLen(20);
ostrAppendu64(&out, strlen(req->body));
reqSetField(req, "Content-Length", out.buf);
ostrFree(out);
}
else {
reqSetField(req, "Content-Length", "0");
}
}
if(req->method == REQ_POST && !reqHasField(req, "Content-Type")) {
reqSetField(req, "Content-Type", "application/x-www-form-urlencoded");
}
if(httpVerNumber(req->version) >= 11 && !reqHasField(req, "Connection")) {
reqSetField(req, "Connection", "close");
for (int i = 0; i < request->header_count; ++i) {
http_header_t *header = request->headers + i;
header->next = h;
h = header;
}
http_response_t res = {0};
str_t req_str = strInit();
str_ostream_t received = ostrInitLen(1024);
if(!skInit()) {
err("couldn't initialize sockets %s", skGetErrorString());
goto skopen_error;
req.headers = h;
http_url_t url = httpSplitUrl(req.url);
if (strvEndsWith(url.host, '/')) {
url.host = strvRemoveSuffix(url.host, 1);
}
ctx->socket = skOpen(SOCK_TCP);
if(ctx->socket == INVALID_SOCKET) {
err("couldn't open socket %s", skGetErrorString());
if (!httpHasHeader(req.headers, strv("Host"))) {
httpSetHeader(req.headers, strv("Host"), url.host);
}
if (!httpHasHeader(req.headers, strv("Content-Length"))) {
char tmp[16] = {0};
fmtBuffer(tmp, arrlen(tmp), "%zu", req.body.len);
httpSetHeader(req.headers, strv("Content-Length"), strv(tmp));
}
if (req.method == HTTP_POST && !httpHasHeader(req.headers, strv("Content-Type"))) {
httpSetHeader(req.headers, strv("Content-Type"), strv("application/x-www-form-urlencoded"));
}
if (!httpHasHeader(req.headers, strv("Connection"))) {
httpSetHeader(req.headers, strv("Connection"), strv("close"));
}
if (!skInit()) {
err("couldn't initialise sockets: %s", skGetErrorString());
goto error;
}
if(skConnect(ctx->socket, ctx->host_name.buf, ctx->port)) {
req_str = reqString(req);
if(req_str.len == 0) {
err("couldn't get string from request");
socket_t sock = skOpen(SOCK_TCP);
if (!skIsValid(sock)) {
err("couldn't open socket: %s", skGetErrorString());
goto error;
}
char hostname[64] = {0};
assert(url.host.len < arrlen(hostname));
memcpy(hostname, url.host.buf, url.host.len);
const int DEFAULT_HTTP_PORT = 80;
if (!skConnect(sock, hostname, DEFAULT_HTTP_PORT)) {
err("Couldn't connect to host %s: %s", hostname, skGetErrorString());
goto error;
}
str_t reqstr = httpReqToStr(request->arena, &req);
if (strIsEmpty(reqstr)) {
err("couldn't get string from request");
goto error;
}
if (skSend(sock, reqstr.buf, (int)reqstr.len) == -1) {
err("couldn't send request to socket: %s", skGetErrorString());
goto error;
}
outstream_t response = ostrInit(request->arena);
char buffer[4096];
int read = 0;
do {
read = skReceive(sock, buffer, arrlen(buffer));
if (read == -1) {
err("couldn't get the data from the server: %s", skGetErrorString());
goto error;
}
ostrPuts(&response, strv(buffer, read));
} while (read != 0);
if(skSend(ctx->socket, req_str.buf, (int)req_str.len) == SOCKET_ERROR) {
err("couldn't send request to socket: %s", skGetErrorString());
goto error;
}
char buffer[1024];
int read = 0;
do {
read = skReceive(ctx->socket, buffer, sizeof(buffer));
if(read == -1) {
err("couldn't get the data from the server: %s", skGetErrorString());
goto error;
}
ostrAppendview(&received, strvInitLen(buffer, read));
} while(read != 0);
// if the data received is not null terminated
if(*(received.buf + received.len) != '\0') {
ostrPutc(&received, '\0');
received.len--;
}
res = resParse(received.buf);
}
else {
err("Couldn't connect to host %s -> %s", ctx->host_name, skGetErrorString());
if (!skClose(sock)) {
err("couldn't close socket: %s", skGetErrorString());
}
if(!skClose(ctx->socket)) {
err("Couldn't close socket");
if (!skCleanup()) {
err("couldn't clean up sockets: %s", skGetErrorString());
}
return httpParseRes(request->arena, ostrAsView(&response));
error:
if(!skCleanup()) {
err("couldn't clean up sockets %s", skGetErrorString());
}
skopen_error:
strFree(req_str);
ostrFree(received);
return res;
arenaRewind(request->arena, arena_begin);
skCleanup();
return (http_res_t){0};
}
http_response_t httpGet(strview_t hostname, strview_t uri) {
http_request_t request = reqInit();
request.method = REQ_GET;
reqSetUri(&request, uri);
#if COLLA_WIN
http_client_t client = hcliInit();
hcliSetHost(&client, hostname);
http_response_t res = hcliSendRequest(&client, &request);
reqFree(request);
hcliFree(client);
return res;
}
url_split_t urlSplit(strview_t uri) {
url_split_t out = {0};
if (strvStartsWithView(uri, strvInit("https://"))) {
uri = strvRemovePrefix(uri, 8);
}
else if (strvStartsWithView(uri, strvInit("http://"))) {
uri = strvRemovePrefix(uri, 7);
buffer_t httpsRequest(http_request_desc_t *req) {
HINTERNET internet = InternetOpen(
TEXT("COLLA"),
INTERNET_OPEN_TYPE_PRECONFIG,
NULL,
NULL,
0
);
if (!internet) {
fatal("call to InternetOpen failed: %u", GetLastError());
}
out.host = strvSub(uri, 0, strvFind(uri, '/', 0));
out.uri = strvSub(uri, out.host.len, SIZE_MAX);
return out;
http_url_t split = httpSplitUrl(req->url);
strview_t server = split.host;
strview_t page = split.uri;
if (strvStartsWithView(server, strv("http://"))) {
server = strvRemovePrefix(server, 7);
}
if (strvStartsWithView(server, strv("https://"))) {
server = strvRemovePrefix(server, 8);
}
arena_t scratch = *req->arena;
const TCHAR *tserver = strvToTChar(&scratch, server);
const TCHAR *tpage = strvToTChar(&scratch, page);
HINTERNET connection = InternetConnect(
internet,
tserver,
INTERNET_DEFAULT_HTTPS_PORT,
NULL,
NULL,
INTERNET_SERVICE_HTTP,
0,
(DWORD_PTR)NULL // userdata
);
if (!connection) {
fatal("call to InternetConnect failed: %u", GetLastError());
}
const TCHAR *accepted_types[] = { TEXT("*/*"), NULL };
HINTERNET request = HttpOpenRequest(
connection,
https__get_method_str(req->request_type),
tpage,
TEXT("HTTP/1.1"),
NULL,
accepted_types,
INTERNET_FLAG_SECURE,
(DWORD_PTR)NULL // userdata
);
if (!request) {
fatal("call to HttpOpenRequest failed: %u", GetLastError());
}
outstream_t header = ostrInit(&scratch);
for (int i = 0; i < req->header_count; ++i) {
http_header_t *h = &req->headers[i];
ostrClear(&header);
ostrPrintf(
&header,
"%.*s: %.*s\r\n",
h->key.len, h->key.buf,
h->value.len, h->value.buf
);
str_t header_str = ostrAsStr(&header);
HttpAddRequestHeadersA(
request,
header_str.buf,
header_str.len,
0
);
}
BOOL request_sent = HttpSendRequest(
request,
NULL,
0,
(void *)req->body.buf,
req->body.len
);
if (!request_sent) {
fatal("call to HttpSendRequest failed: %u", GetLastError());
}
outstream_t out = ostrInit(req->arena);
while (true) {
DWORD bytes_read = 0;
char buffer[4096];
BOOL read = InternetReadFile(
request,
buffer,
sizeof(buffer),
&bytes_read
);
if (!read || bytes_read == 0) {
break;
}
ostrPuts(&out, strv(buffer, bytes_read));
}
InternetCloseHandle(request);
InternetCloseHandle(connection);
InternetCloseHandle(internet);
str_t outstr = ostrAsStr(&out);
return (buffer_t) {
.data = (uint8 *)outstr.buf,
.len = outstr.len
};
}
static const TCHAR *https__get_method_str(http_method_e method) {
switch (method) {
case HTTP_GET: return TEXT("GET");
case HTTP_POST: return TEXT("POST");
case HTTP_HEAD: return TEXT("HEAD");
case HTTP_PUT: return TEXT("PUT");
case HTTP_DELETE: return TEXT("DELETE");
}
// default GET
return NULL;
}
#endif
#include "warnings/colla_warn_end.h"

View file

@ -1,57 +1,20 @@
#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 struct arena_t arena_t;
typedef uintptr_t socket_t;
typedef enum {
REQ_GET,
REQ_POST,
REQ_HEAD,
REQ_PUT,
REQ_DELETE
} reqtype_t;
HTTP_GET,
HTTP_POST,
HTTP_HEAD,
HTTP_PUT,
HTTP_DELETE
} http_method_e;
typedef enum {
// 2xx: success
STATUS_OK = 200,
STATUS_CREATED = 201,
STATUS_ACCEPTED = 202,
STATUS_NO_CONTENT = 204,
STATUS_RESET_CONTENT = 205,
STATUS_PARTIAL_CONTENT = 206,
// 3xx: redirection
STATUS_MULTIPLE_CHOICES = 300,
STATUS_MOVED_PERMANENTLY = 301,
STATUS_MOVED_TEMPORARILY = 302,
STATUS_NOT_MODIFIED = 304,
// 4xx: client error
STATUS_BAD_REQUEST = 400,
STATUS_UNAUTHORIZED = 401,
STATUS_FORBIDDEN = 403,
STATUS_NOT_FOUND = 404,
STATUS_RANGE_NOT_SATISFIABLE = 407,
// 5xx: server error
STATUS_INTERNAL_SERVER_ERROR = 500,
STATUS_NOT_IMPLEMENTED = 501,
STATUS_BAD_GATEWAY = 502,
STATUS_SERVICE_NOT_AVAILABLE = 503,
STATUS_GATEWAY_TIMEOUT = 504,
STATUS_VERSION_NOT_SUPPORTED = 505,
} resstatus_t;
const char *httpGetStatusString(resstatus_t status);
const char *httpGetStatusString(int status);
typedef struct {
uint8 major;
@ -61,83 +24,59 @@ typedef struct {
// translates a http_version_t to a single readable number (e.g. 1.1 -> 11, 1.0 -> 10, etc)
int httpVerNumber(http_version_t ver);
typedef struct {
char *key;
char *value;
} http_field_t;
#include "vec.h"
// == HTTP REQUEST ============================================================
typedef struct http_header_t {
strview_t key;
strview_t value;
struct http_header_t *next;
} http_header_t;
typedef struct {
reqtype_t method;
http_method_e method;
http_version_t version;
vec(http_field_t) fields;
char *uri;
char *body;
} http_request_t;
http_request_t reqInit(void);
http_request_t reqParse(const char *request);
void reqFree(http_request_t ctx);
bool reqHasField(http_request_t *ctx, const char *key);
void reqSetField(http_request_t *ctx, const char *key, const char *value);
void reqSetUri(http_request_t *ctx, strview_t uri);
str_ostream_t reqPrepare(http_request_t *ctx);
str_t reqString(http_request_t *ctx);
// == HTTP RESPONSE ===========================================================
http_header_t *headers;
strview_t url;
strview_t body;
} http_req_t;
typedef struct {
resstatus_t status_code;
vec(http_field_t) fields;
int status_code;
http_version_t version;
vec(uint8) body;
} http_response_t;
http_header_t *headers;
strview_t body;
} http_res_t;
http_response_t resParse(const char *data);
void resFree(http_response_t ctx);
// strview_t request needs to be valid for http_req_t to be valid!
http_req_t httpParseReq(arena_t *arena, strview_t request);
http_res_t httpParseRes(arena_t *arena, strview_t response);
bool resHasField(http_response_t *ctx, const char *key);
void resSetField(http_response_t *ctx, const char *key, const char *value);
const char *resGetField(http_response_t *ctx, const char *field);
str_t httpReqToStr(arena_t *arena, http_req_t *req);
str_t httpResToStr(arena_t *arena, http_res_t *res);
// void resParse(http_response_t *ctx, const char *data);
void resParseFields(http_response_t *ctx, str_istream_t *in);
str_ostream_t resPrepare(http_response_t *ctx);
str_t resString(http_response_t *ctx);
bool httpHasHeader(http_header_t *headers, strview_t key);
void httpSetHeader(http_header_t *headers, strview_t key, strview_t value);
strview_t httpGetHeader(http_header_t *headers, strview_t key);
// == HTTP CLIENT =============================================================
typedef struct {
str_t host_name;
uint16 port;
socket_t socket;
} http_client_t;
http_client_t hcliInit(void);
void hcliFree(http_client_t ctx);
void hcliSetHost(http_client_t *ctx, strview_t hostname);
http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *request);
// == HTTP ====================================================================
http_response_t httpGet(strview_t hostname, strview_t uri);
// == URL =====================================================================
str_t httpMakeUrlSafe(arena_t *arena, strview_t string);
typedef struct {
strview_t host;
strview_t uri;
} url_split_t;
} http_url_t;
url_split_t urlSplit(strview_t uri);
http_url_t httpSplitUrl(strview_t url);
#ifdef __cplusplus
} // extern "C"
#endif
typedef struct {
arena_t *arena;
strview_t url;
http_method_e request_type;
http_header_t *headers;
int header_count;
strview_t body;
} http_request_desc_t;
// arena_t *arena, strview_t url, [ http_header_t *headers, int header_count, strview_t body ]
#define httpGet(arena, url, ...) httpRequest(&(http_request_desc_t){ arena, url, .request_type = HTTP_GET, __VA_ARGS__ })
#define httpsGet(arena, url, ...) httpsRequest(&(http_request_desc_t){ arena, url, .request_type = HTTP_GET, __VA_ARGS__ })
http_res_t httpRequest(http_request_desc_t *request);
buffer_t httpsRequest(http_request_desc_t *request);

View file

@ -1,347 +1,267 @@
#include "ini.h"
#include "warnings/colla_warn_beg.h"
#include <assert.h>
#include "strstream.h"
#include "file.h"
#include "tracelog.h"
// == INI READER ========================================================================
static void ini__parse(arena_t *arena, ini_t *ini, const iniopts_t *options);
static const iniopts_t default_opts = {
.key_value_divider = '='
};
ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options) {
file_t fp = fileOpen(*arena, filename, FILE_READ);
ini_t out = iniParseFile(arena, fp, options);
fileClose(fp);
return out;
}
static iniopts_t setDefaultOptions(const iniopts_t *options);
static initable_t *findTable(ini_t *ctx, strview_t name);
static inivalue_t *findValue(vec(inivalue_t) values, strview_t key);
static void addTable(ini_t *ctx, str_istream_t *in, const iniopts_t *options);
static void addValue(initable_t *table, str_istream_t *in, const iniopts_t *options);
ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options) {
str_t data = fileReadWholeStrFP(arena, file);
return iniParseStr(arena, strv(data), options);
}
void _iniParseInternal(ini_t *ini, const iniopts_t *options) {
// add root table
vecAppend(ini->tables, (initable_t){0});
str_istream_t in = istrInitLen(ini->text.buf, ini->text.len);
istrSkipWhitespace(&in);
while (!istrIsFinished(in)) {
switch(*in.cur) {
case '[':
addTable(ini, &in, options);
break;
case '#': case ';':
istrIgnore(&in, '\n');
break;
default:
addValue(&ini->tables[0], &in, options);
break;
ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options) {
ini_t out = {
.text = str,
.tables = NULL,
};
ini__parse(arena, &out, options);
return out;
}
initable_t *iniGetTable(ini_t *ctx, strview_t name) {
initable_t *t = ctx ? ctx->tables : NULL;
while (t) {
if (strvEquals(t->name, name)) {
return t;
}
istrSkipWhitespace(&in);
t = t->next;
}
return NULL;
}
ini_t iniParse(const char *filename, const iniopts_t *options) {
ini_t ini = { .text = fileReadWholeText(filename) };
if (strIsEmpty(ini.text)) return ini;
iniopts_t opts = setDefaultOptions(options);
_iniParseInternal(&ini, &opts);
return ini;
}
ini_t iniParseString(const char *inistr, const iniopts_t *options) {
ini_t ini = { .text = strFromStr(inistr) };
if (!options) options = &default_opts;
_iniParseInternal(&ini, options);
return ini;
}
void iniFree(ini_t ctx) {
strFree(ctx.text);
for (uint32 i = 0; i < vecLen(ctx.tables); ++i) {
vecFree(ctx.tables[i].values);
inivalue_t *iniGet(initable_t *ctx, strview_t key) {
inivalue_t *v = ctx ? ctx->values : NULL;
while (v) {
if (strvEquals(v->key, key)) {
return v;
}
v = v->next;
}
vecFree(ctx.tables);
return NULL;
}
initable_t *iniGetTable(ini_t *ctx, const char *name) {
if (!name) {
return &ctx->tables[0];
}
else {
return findTable(ctx, strvInit(name));
}
}
inivalue_t *iniGet(initable_t *ctx, const char *key) {
return ctx ? findValue(ctx->values, strvInit(key)) : NULL;
}
vec(strview_t) iniAsArray(const inivalue_t *value, char delim) {
if (!value) return NULL;
iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim) {
strview_t v = value ? value->value : (strview_t){0};
if (!delim) delim = ' ';
vec(strview_t) out = NULL;
strview_t v = value->value;
strview_t *beg = (strview_t *)arena->current;
usize count = 0;
usize start = 0;
for (usize i = 0; i < v.len; ++i) {
if (v.buf[i] == delim) {
strview_t arr_val = strvTrim(strvSub(v, start, i));
if (!strvIsEmpty(arr_val)) vecAppend(out, arr_val);
strview_t arrval = strvTrim(strvSub(v, start, i));
if (!strvIsEmpty(arrval)) {
strview_t *newval = alloc(arena, strview_t);
*newval = arrval;
++count;
}
start = i + 1;
}
}
strview_t last = strvTrim(strvSub(v, start, SIZE_MAX));
if (!strvIsEmpty(last)) vecAppend(out, last);
if (!strvIsEmpty(last)) {
strview_t *newval = alloc(arena, strview_t);
*newval = last;
++count;
}
return (iniarray_t){
.values = beg,
.count = count,
};
}
uint64 iniAsUInt(inivalue_t *value) {
strview_t v = value ? value->value : (strview_t){0};
instream_t in = istrInitLen(v.buf, v.len);
uint64 out = 0;
if (!istrGetU64(&in, &out)) {
out = 0;
}
return out;
}
vec(strview_t) iniAsArrayU8(const inivalue_t *value, const char *delim) {
if (!value || !delim) return NULL;
rune cpdelim = utf8Decode(&delim);
vec(strview_t) out = NULL;
strview_t v = value->value;
const char *start = v.buf;
const char *buf = v.buf;
const char *prevbuf = buf;
for(rune cp = utf8Decode(&buf);
buf != (v.buf + v.len);
cp = utf8Decode(&buf)
) {
if (cp == cpdelim) {
usize start_pos = start - v.buf;
usize end_pos = prevbuf - v.buf;
strview_t arr_val = strvTrim(strvSub(v, start_pos, end_pos));
if (!strvIsEmpty(arr_val)) vecAppend(out, arr_val);
// buf has already gone to the next codepoint, skipping the delimiter
start = buf;
}
prevbuf = buf;
int64 iniAsInt(inivalue_t *value) {
strview_t v = value ? value->value : (strview_t){0};
instream_t in = istrInitLen(v.buf, v.len);
int64 out = 0;
if (!istrGetI64(&in, &out)) {
out = 0;
}
strview_t last = strvTrim(strvSub(v, start - v.buf, SIZE_MAX));
if (!strvIsEmpty(last)) vecAppend(out, last);
return out;
}
uint64 iniAsUInt(const inivalue_t *value) {
if (!value) return 0;
str_istream_t in = istrInitLen(value->value.buf, value->value.len);
uint64 val = 0;
if (!istrGetu64(&in, &val)) val = 0;
return val;
}
int64 iniAsInt(const inivalue_t *value) {
if (!value) return 0;
str_istream_t in = istrInitLen(value->value.buf, value->value.len);
int64 val = 0;
if (!istrGeti64(&in, &val)) val = 0;
return val;
}
double iniAsNum(const inivalue_t *value) {
if (!value) return 0.f;
str_istream_t in = istrInitLen(value->value.buf, value->value.len);
double val = 0;
if (!istrGetdouble(&in, &val)) val = 0;
return val;
}
bool iniAsBool(const inivalue_t *value) {
if (!value) return false;
return strvCompare(value->value, strvInit("true")) == 0;
}
// == INI WRITER ========================================================================
#include "strstream.h"
#include "file.h"
static const winiopts_t default_wopts = {0};
iniwriter_t winiInit() {
iniwriter_t out = {0};
vecAppend(out.tables, (winitable_t){0});
double iniAsNum(inivalue_t *value) {
strview_t v = value ? value->value : (strview_t){0};
instream_t in = istrInitLen(v.buf, v.len);
double out = 0;
if (!istrGetDouble(&in, &out)) {
out = 0.0;
}
return out;
}
void winiFree(iniwriter_t ctx) {
for (winitable_t *tab = ctx.tables; tab != vecEnd(ctx.tables); ++tab) {
strFree(tab->key);
for (winivalue_t *val = tab->values; val != vecEnd(tab->values); ++val) {
strFree(val->key);
strFree(val->value);
}
vecFree(tab->values);
bool iniAsBool(inivalue_t *value) {
strview_t v = value ? value->value : (strview_t){0};
instream_t in = istrInitLen(v.buf, v.len);
bool out = 0;
if (!istrGetBool(&in, &out)) {
out = false;
}
vecFree(ctx.tables);
return out;
}
str_t winiToString(iniwriter_t ctx, const winiopts_t *options) {
if (!options) options = &default_wopts;
// == PRIVATE FUNCTIONS ==============================================================================
str_ostream_t out = ostrInitLen(1024 * 20);
if (!options->no_discalimer) ostrPuts(&out, "# auto-generated by colla's ini.h, do not modify!\n");
// add root values
winitable_t *root = &ctx.tables[0];
for (winivalue_t *val = root->values; val != vecEnd(root->values); ++val) {
ostrPrintf(&out, "%s = %s\n", val->key.buf, val->value.buf);
#define INIPUSH(head, tail, val) \
do { \
if (!head) { \
head = val; \
tail = val; \
} \
else { \
tail->next = val; \
val = tail; \
} \
} while (0)
static iniopts_t ini__get_options(const iniopts_t *options) {
iniopts_t out = {
.key_value_divider = '=',
};
#define SETOPT(v) out.v = options->v ? options->v : out.v
if (options) {
SETOPT(key_value_divider);
SETOPT(merge_duplicate_keys);
SETOPT(merge_duplicate_tables);
}
if (root->values) ostrPuts(&out, "\n");
// add each table
for (usize i = 1; i < vecLen(ctx.tables); ++i) {
winitable_t *tab = &ctx.tables[i];
ostrPrintf(&out, "[%s]\n", tab->key.buf);
for (winivalue_t *val = tab->values; val != vecEnd(tab->values); ++val) {
ostrPrintf(&out, "%s = %s\n", val->key.buf, val->value.buf);
}
if ((i + 1) < vecLen(ctx.tables)) ostrPuts(&out, "\n");
}
return ostrAsStr(out);
#undef SETOPT
return out;
}
void winiToFile(iniwriter_t ctx, const char *filename, const winiopts_t *options) {
if (!options) options = &default_wopts;
static void ini__add_value(arena_t *arena, initable_t *table, instream_t *in, iniopts_t *opts) {
assert(table);
file_t fp = fileOpen(filename, FILE_WRITE);
if (!fileIsValid(fp)) {
err("couldn't write ini to file %s", filename);
return;
}
str_t string = winiToString(ctx, options);
fileWriteWholeTextFP(fp, strvInitStr(string));
strFree(string);
fileClose(fp);
}
winivalue_t *winiAddValEmpty(winitable_t *table) {
if (!table) return NULL;
vecAppend(table->values, (winivalue_t){0});
return &vecBack(table->values);
}
winivalue_t *winiAddVal(winitable_t *table, const char *key, const char *value) {
if (!table) return NULL;
winivalue_t val = { .key = strFromStr(key), .value = strFromStr(value) };
vecAppend(table->values, val);
return &vecBack(table->values);
}
winivalue_t *winiAddValStr(winitable_t *table, str_t key, str_t value) {
if (!table) return NULL;
winivalue_t val = { .key = key, .value = value };
vecAppend(table->values, val);
return &vecBack(table->values);
}
winivalue_t *winiAddValView(winitable_t *table, strview_t key, strview_t value) {
if (!table) return NULL;
winivalue_t val = { .key = strFromView(key), .value = strFromView(value) };
vecAppend(table->values, val);
return &vecBack(table->values);
}
winitable_t *winiAddTablEmpty(iniwriter_t *ctx) {
vecAppend(ctx->tables, (winitable_t){0});
return &vecBack(ctx->tables);
}
winitable_t *winiAddTab(iniwriter_t *ctx, const char *name) {
winitable_t tab = { .key = strFromStr(name) };
vecAppend(ctx->tables, tab);
return &vecBack(ctx->tables);
}
winitable_t *winiAddTabStr(iniwriter_t *ctx, str_t name) {
winitable_t tab = { .key = name };
vecAppend(ctx->tables, tab);
return &vecBack(ctx->tables);
}
winitable_t *winiAddTabView(iniwriter_t *ctx, strview_t name) {
winitable_t tab = { .key = strFromView(name) };
vecAppend(ctx->tables, tab);
return &vecBack(ctx->tables);
}
// == PRIVATE FUNCTIONS ========================================================
static iniopts_t setDefaultOptions(const iniopts_t *options) {
if (!options) return default_opts;
iniopts_t opts = default_opts;
if (options->merge_duplicate_keys)
opts.merge_duplicate_keys = options->merge_duplicate_keys;
if (options->merge_duplicate_tables)
opts.merge_duplicate_tables = options->merge_duplicate_tables;
if (options->key_value_divider)
opts.key_value_divider = options->key_value_divider;
return opts;
}
static initable_t *findTable(ini_t *ctx, strview_t name) {
if (strvIsEmpty(name)) return NULL;
for (uint32 i = 1; i < vecLen(ctx->tables); ++i) {
if (strvCompare(ctx->tables[i].name, name) == 0) {
return &ctx->tables[i];
}
}
return NULL;
}
static inivalue_t *findValue(vec(inivalue_t) values, strview_t key) {
if (strvIsEmpty(key)) return NULL;
for (uint32 i = 0; i < vecLen(values); ++i) {
if (strvCompare(values[i].key, key) == 0) {
return &values[i];
}
}
return NULL;
}
static void addTable(ini_t *ctx, str_istream_t *in, const iniopts_t *options) {
istrSkip(in, 1); // skip [
strview_t name = istrGetview(in, ']');
istrSkip(in, 1); // skip ]
initable_t *table = options->merge_duplicate_tables ? findTable(ctx, name) : NULL;
if (!table) {
vecAppend(ctx->tables, (initable_t){ name });
table = &vecBack(ctx->tables);
}
istrIgnore(in, '\n'); istrSkip(in, 1);
while (!istrIsFinished(*in)) {
switch (*in->cur) {
case '\n': case '\r':
return;
case '#': case ';':
istrIgnore(in, '\n');
break;
default:
addValue(table, in, options);
break;
}
}
}
static void addValue(initable_t *table, str_istream_t *in, const iniopts_t *options) {
if (!table) fatal("table is null");
strview_t key = strvTrim(istrGetview(in, options->key_value_divider));
strview_t key = strvTrim(istrGetView(in, opts->key_value_divider));
istrSkip(in, 1);
strview_t value = strvTrim(istrGetview(in, '\n'));
// value might be until EOF, in that case no use in skipping
if (!istrIsFinished(*in)) istrSkip(in, 1); // skip newline
inivalue_t *new_value = options->merge_duplicate_keys ? findValue(table->values, key) : NULL;
if (!new_value) {
inivalue_t ini_val = (inivalue_t){ key, value };
vecAppend(table->values, ini_val);
strview_t value = strvTrim(istrGetView(in, '\n'));
istrSkip(in, 1);
inivalue_t *newval = NULL;
if (opts->merge_duplicate_keys) {
newval = table->values;
while (newval) {
if (strvEquals(newval->key, key)) {
break;
}
newval = newval->next;
}
}
if (newval) {
newval->value = value;
}
else {
new_value->value = value;
newval = alloc(arena, inivalue_t);
newval->key = key;
newval->value = value;
if (!table->values) {
table->values = newval;
}
else {
table->tail->next = newval;
}
table->tail = newval;
}
}
static void ini__add_table(arena_t *arena, ini_t *ctx, instream_t *in, iniopts_t *options) {
istrSkip(in, 1); // skip [
strview_t name = istrGetView(in, ']');
istrSkip(in, 1); // skip ]
initable_t *table = NULL;
if (options->merge_duplicate_tables) {
table = ctx->tables;
while (table) {
if (strvEquals(table->name, name)) {
break;
}
table = table->next;
}
}
if (!table) {
table = alloc(arena, initable_t);
if (!ctx->tables) {
ctx->tables = table;
}
else {
ctx->tail->next = table;
}
ctx->tail = table;
}
istrIgnoreAndSkip(in, '\n');
while (!istrIsFinished(*in)) {
switch (istrPeek(in)) {
case '\n': // fallthrough
case '\r':
return;
case '#': // fallthrough
case ';':
istrIgnoreAndSkip(in, '\n');
break;
default:
ini__add_value(arena, table, in, options);
break;
}
}
}
static void ini__parse(arena_t *arena, ini_t *ini, const iniopts_t *options) {
iniopts_t opts = ini__get_options(options);
instream_t in = istrInitLen(ini->text.buf, ini->text.len);
while (!istrIsFinished(in)) {
istrSkipWhitespace(&in);
switch (istrPeek(&in)) {
case '[':
ini__add_table(arena, ini, &in, &opts);
break;
case '#': // fallthrough
case ';':
istrIgnoreAndSkip(&in, '\n');
break;
default:
ini__add_value(arena, ini->tables, &in, &opts);
break;
}
}
}
#undef INIPUSH
#include "warnings/colla_warn_end.h"

View file

@ -1,29 +1,28 @@
#pragma once
#include <stdbool.h>
#include "collatypes.h"
#include "str.h"
#include "vec.h"
#include "utf8.h"
#include "file.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct arena_t arena_t;
// == INI READER ========================================================================
typedef struct {
typedef struct inivalue_t {
strview_t key;
strview_t value;
struct inivalue_t *next;
} inivalue_t;
typedef struct {
typedef struct initable_t {
strview_t name;
vec(inivalue_t) values;
inivalue_t *values;
inivalue_t *tail;
struct initable_t *next;
} initable_t;
typedef struct {
str_t text;
vec(initable_t) tables;
strview_t text;
initable_t *tables;
initable_t *tail;
} ini_t;
typedef struct {
@ -32,58 +31,22 @@ typedef struct {
char key_value_divider; // default =
} iniopts_t;
ini_t iniParse(const char *filename, const iniopts_t *options);
ini_t iniParseString(const char *inistr, const iniopts_t *options);
void iniFree(ini_t ctx);
ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options);
ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options);
ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options);
initable_t *iniGetTable(ini_t *ctx, const char *name);
inivalue_t *iniGet(initable_t *ctx, const char *key);
#define INI_ROOT strv("__ROOT__")
vec(strview_t) iniAsArray(const inivalue_t *value, char delim);
// delim is expected to be a single utf8 character
vec(strview_t) iniAsArrayU8(const inivalue_t *value, const char *delim);
uint64 iniAsUInt(const inivalue_t *value);
int64 iniAsInt(const inivalue_t *value);
double iniAsNum(const inivalue_t *value);
bool iniAsBool(const inivalue_t *value);
// == INI WRITER ========================================================================
initable_t *iniGetTable(ini_t *ctx, strview_t name);
inivalue_t *iniGet(initable_t *ctx, strview_t key);
typedef struct {
str_t key;
str_t value;
} winivalue_t;
strview_t *values;
usize count;
} iniarray_t;
typedef struct {
str_t key;
vec(winivalue_t) values;
} winitable_t;
typedef struct {
vec(winitable_t) tables;
} iniwriter_t;
typedef struct {
bool no_discalimer; // default false
char key_value_divider; // default =
} winiopts_t;
iniwriter_t winiInit();
void winiFree(iniwriter_t ctx);
str_t winiToString(iniwriter_t ctx, const winiopts_t *options);
void winiToFile(iniwriter_t ctx, const char *filename, const winiopts_t *options);
winivalue_t *winiAddValEmpty(winitable_t *table);
winivalue_t *winiAddVal(winitable_t *table, const char *key, const char *value);
winivalue_t *winiAddValStr(winitable_t *table, str_t key, str_t value);
winivalue_t *winiAddValView(winitable_t *table, strview_t key, strview_t value);
winitable_t *winiAddTablEmpty(iniwriter_t *ctx);
winitable_t *winiAddTab(iniwriter_t *ctx, const char *name);
winitable_t *winiAddTabStr(iniwriter_t *ctx, str_t name);
winitable_t *winiAddTabView(iniwriter_t *ctx, strview_t name);
#ifdef __cplusplus
} // extern "C"
#endif
iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim);
uint64 iniAsUInt(inivalue_t *value);
int64 iniAsInt(inivalue_t *value);
double iniAsNum(inivalue_t *value);
bool iniAsBool(inivalue_t *value);

288
colla/json.c Normal file
View file

@ -0,0 +1,288 @@
#include "json.h"
#include "warnings/colla_warn_beg.h"
#include <stdio.h>
#include "strstream.h"
#include "tracelog.h"
#define json__ensure(c) \
if (istrGet(in) != (c)) { \
istrRewindN(in, 1); \
fatal("wrong character at %zu, should be " #c " but is %c", istrTell(*in), istrPeek(in));\
}
str_t json__read_whole(arena_t *arena, const char *filename) {
str_t out = {0};
FILE *fp = fopen(filename, "rb");
if (!fp) {
err("could not open %s", filename);
return (str_t){0};
}
fseek(fp, 0, SEEK_END);
long len = ftell(fp);
fseek(fp, 0, SEEK_SET);
out.buf = alloc(arena, char, len + 1);
out.len = len;
fread(out.buf, 1, len, fp);
fclose(fp);
return out;
}
jsonval_t *json__parse_pair(arena_t *arena, instream_t *in, jsonflags_e flags);
jsonval_t *json__parse_value(arena_t *arena, instream_t *in, jsonflags_e flags);
bool json__is_value_finished(instream_t *in) {
usize old_pos = istrTell(*in);
istrSkipWhitespace(in);
switch(istrPeek(in)) {
case '}': // fallthrough
case ']': // fallthrough
case ',':
return true;
}
in->cur = in->start + old_pos;
return false;
}
void json__parse_null(instream_t *in) {
strview_t null_view = istrGetViewLen(in, 4);
if (!strvEquals(null_view, strv("null"))) {
fatal("should be null but is: (%.*s) at %zu", null_view.len, null_view.buf, istrTell(*in));
}
if (!json__is_value_finished(in)) {
fatal("null, should be finished, but isn't at %zu", istrTell(*in));
}
}
jsonval_t *json__parse_array(arena_t *arena, instream_t *in, jsonflags_e flags) {
json__ensure('[');
istrSkipWhitespace(in);
// if it is an empty array
if (istrPeek(in) == ']') {
istrSkip(in, 1);
return NULL;
}
jsonval_t *head = json__parse_value(arena, in, flags);
jsonval_t *cur = head;
while (true) {
istrSkipWhitespace(in);
switch (istrGet(in)) {
case ']':
return head;
case ',':
{
istrSkipWhitespace(in);
// trailing comma
if (!(flags & JSON_NO_TRAILING_COMMAS) && istrPeek(in) == ']') {
return head;
}
jsonval_t *next = json__parse_value(arena, in, flags);
cur->next = next;
next->prev = cur;
cur = next;
break;
}
default:
istrRewindN(in, 1);
fatal("unknown char after array at %zu: (%c)(%d)", istrTell(*in), *in->cur, *in->cur);
}
}
return NULL;
}
str_t json__parse_string(arena_t *arena, instream_t *in, jsonflags_e flags) {
istrSkipWhitespace(in);
json__ensure('"');
const char *from = in->cur;
for (; !istrIsFinished(*in) && *in->cur != '"'; ++in->cur) {
if (istrPeek(in) == '\\') {
++in->cur;
}
}
usize len = in->cur - from;
str_t out = str(arena, from, len);
json__ensure('"');
return out;
}
double json__parse_number(instream_t *in, jsonflags_e flags) {
double value = 0.0;
istrGetDouble(in, &value);
return value;
}
bool json__parse_bool(instream_t *in, jsonflags_e flags) {
size_t remaining = istrRemaining(*in);
if (remaining >= 4 && memcmp(in->cur, "true", 4) == 0) {
istrSkip(in, 4);
return true;
}
if (remaining >= 5 && memcmp(in->cur, "false", 5) == 0) {
istrSkip(in, 5);
return false;
}
fatal("unknown boolean at %zu: %.10s", istrTell(*in), in->cur);
return false;
}
jsonval_t *json__parse_obj(arena_t *arena, instream_t *in, jsonflags_e flags) {
json__ensure('{');
istrSkipWhitespace(in);
// if it is an empty object
if (istrPeek(in) == '}') {
istrSkip(in, 1);
return NULL;
}
jsonval_t *head = json__parse_pair(arena, in, flags);
jsonval_t *cur = head;
while (true) {
istrSkipWhitespace(in);
switch (istrGet(in)) {
case '}':
return head;
case ',':
{
istrSkipWhitespace(in);
// trailing commas
if (!(flags & JSON_NO_TRAILING_COMMAS) && istrPeek(in) == '}') {
return head;
}
jsonval_t *next = json__parse_pair(arena, in, flags);
cur->next = next;
next->prev = cur;
cur = next;
break;
}
default:
istrRewindN(in, 1);
fatal("unknown char after object at %zu: (%c)(%d)", istrTell(*in), *in->cur, *in->cur);
}
}
return head;
}
jsonval_t *json__parse_pair(arena_t *arena, instream_t *in, jsonflags_e flags) {
str_t key = json__parse_string(arena, in, flags);
// skip preamble
istrSkipWhitespace(in);
json__ensure(':');
jsonval_t *out = json__parse_value(arena, in, flags);
out->key = key;
return out;
}
jsonval_t *json__parse_value(arena_t *arena, instream_t *in, jsonflags_e flags) {
jsonval_t *out = alloc(arena, jsonval_t);
istrSkipWhitespace(in);
switch (istrPeek(in)) {
// object
case '{':
out->object = json__parse_obj(arena, in, flags);
out->type = JSON_OBJECT;
break;
// array
case '[':
out->array = json__parse_array(arena, in, flags);
out->type = JSON_ARRAY;
break;
// string
case '"':
out->string = json__parse_string(arena, in, flags);
out->type = JSON_STRING;
break;
// boolean
case 't': // fallthrough
case 'f':
out->boolean = json__parse_bool(in, flags);
out->type = JSON_BOOL;
break;
// null
case 'n':
json__parse_null(in);
out->type = JSON_NULL;
break;
// comment
case '/':
fatal("TODO comments");
break;
// number
default:
out->number = json__parse_number(in, flags);
out->type = JSON_NUMBER;
break;
}
return out;
}
json_t jsonParse(arena_t *arena, arena_t scratch, const char *filename, jsonflags_e flags) {
str_t data = json__read_whole(&scratch, filename);
json_t json = jsonParseStr(arena, strv(data), flags);
return json;
}
json_t jsonParseStr(arena_t *arena, strview_t jsonstr, jsonflags_e flags) {
jsonval_t *root = alloc(arena, jsonval_t);
root->type = JSON_OBJECT;
instream_t in = istrInitLen(jsonstr.buf, jsonstr.len);
root->object = json__parse_obj(arena, &in, flags);
return root;
}
jsonval_t *jsonGet(jsonval_t *node, strview_t key) {
if (!node) return NULL;
if (node->type != JSON_OBJECT) {
err("passed type is not an object");
return NULL;
}
node = node->object;
while (node) {
if (strvEquals(strv(node->key), key)) {
return node;
}
node = node->next;
}
return NULL;
}
#include "warnings/colla_warn_end.h"

47
colla/json.h Normal file
View file

@ -0,0 +1,47 @@
#pragma once
#include "str.h"
#include "arena.h"
typedef enum {
JSON_NULL,
JSON_ARRAY,
JSON_STRING,
JSON_NUMBER,
JSON_BOOL,
JSON_OBJECT,
} jsontype_e;
typedef enum {
JSON_DEFAULT = 0,
JSON_NO_TRAILING_COMMAS = 1 << 0,
JSON_NO_COMMENTS = 1 << 1,
} jsonflags_e;
typedef struct jsonval_t jsonval_t;
typedef struct jsonval_t {
jsonval_t *next;
jsonval_t *prev;
str_t key;
union {
jsonval_t *array;
str_t string;
double number;
bool boolean;
jsonval_t *object;
};
jsontype_e type;
} jsonval_t;
typedef jsonval_t *json_t;
json_t jsonParse(arena_t *arena, arena_t scratch, const char *filename, jsonflags_e flags);
json_t jsonParseStr(arena_t *arena, strview_t jsonstr, jsonflags_e flags);
jsonval_t *jsonGet(jsonval_t *node, strview_t key);
#define json_for(name, arr) for (jsonval_t *name = arr->array; name; name = name->next)
#define json_check(val, js_type) ((val) && (val)->type == js_type)

382
colla/server.c Normal file
View file

@ -0,0 +1,382 @@
#include "server.h"
#include <stdio.h>
#include "socket.h"
#include "tracelog.h"
#include "strstream.h"
#include "arena.h"
#define SERVER_BUFSZ 4096
typedef enum {
PARSE_REQ_BEGIN,
PARSE_REQ_PAGE,
PARSE_REQ_VERSION,
PARSE_REQ_FIELDS,
PARSE_REQ_FINISHED,
} server__req_status_e;
typedef struct {
server_req_t req;
server__req_status_e status;
char fullbuf[SERVER_BUFSZ * 2];
usize prevbuf_len;
} server__req_ctx_t;
typedef struct server__route_t {
str_t page;
server_route_f fn;
void *userdata;
struct server__route_t *next;
} server__route_t;
typedef struct server_t {
socket_t socket;
server__route_t *routes;
server__route_t *routes_default;
bool should_stop;
} server_t;
bool server__parse_chunk(arena_t *arena, server__req_ctx_t *ctx, char buffer[SERVER_BUFSZ], usize buflen) {
memcpy(ctx->fullbuf + ctx->prevbuf_len, buffer, buflen);
usize fulllen = ctx->prevbuf_len + buflen;
instream_t in = istrInitLen(ctx->fullbuf, fulllen);
#define RESET_STREAM() in.cur = in.start + begin
#define BEGIN_STREAM() begin = istrTell(in)
usize begin = istrTell(in);
switch (ctx->status) {
case PARSE_REQ_BEGIN:
{
BEGIN_STREAM();
strview_t method = istrGetView(&in, ' ');
if (istrGet(&in) != ' ') {
RESET_STREAM();
break;
}
if (strvEquals(method, strv("GET"))) {
ctx->req.method = HTTP_GET;
}
else if(strvEquals(method, strv("POST"))) {
ctx->req.method = HTTP_POST;
}
else {
fatal("unknown method: (%.*s)", method.len, method.buf);
}
info("parsed method: %.*s", method.len, method.buf);
ctx->status = PARSE_REQ_PAGE;
}
// fallthrough
case PARSE_REQ_PAGE:
{
BEGIN_STREAM();
strview_t page = istrGetView(&in, ' ');
if (istrGet(&in) != ' ') {
RESET_STREAM();
break;
}
ctx->req.page = str(arena, page);
info("parsed page: %.*s", page.len, page.buf);
ctx->status = PARSE_REQ_VERSION;
}
// fallthrough
case PARSE_REQ_VERSION:
{
BEGIN_STREAM();
strview_t version = istrGetView(&in, '\n');
if (istrGet(&in) != '\n') {
RESET_STREAM();
break;
}
if (version.len < 8) {
fatal("version too short: (%.*s)", version.len, version.buf);
}
if (strvEquals(strvSub(version, 0, 4), strv("HTTP"))) {
fatal("version does not start with HTTP: (%.4s)", version.buf);
}
// skip HTTP
version = strvRemovePrefix(version, 4);
uint8 major, minor;
int scanned = sscanf(version.buf, "/%hhu.%hhu", &major, &minor);
if (scanned != 2) {
fatal("could not scan both major and minor from version: (%.*s)", version.len, version.buf);
}
ctx->req.version_major = major;
ctx->req.version_minor = minor;
ctx->status = PARSE_REQ_FIELDS;
}
// fallthrough
case PARSE_REQ_FIELDS:
{
bool finished_parsing = false;
while (true) {
BEGIN_STREAM();
strview_t field = istrGetView(&in, '\n');
if (istrGet(&in) != '\n') {
RESET_STREAM();
break;
}
instream_t field_in = istrInitLen(field.buf, field.len);
strview_t key = istrGetView(&field_in, ':');
if (istrGet(&field_in) != ':') {
fatal("field does not have ':' (%.*s)", field.len, field.buf);
}
istrSkipWhitespace(&field_in);
strview_t value = istrGetView(&field_in, '\r');
if (istrGet(&field_in) != '\r') {
warn("field does not finish with \\r: (%.*s)", field.len, field.buf);
RESET_STREAM();
break;
}
server_field_t *new_field = alloc(arena, server_field_t);
new_field->key = str(arena, key);
new_field->value = str(arena, value);
if (!ctx->req.fields) {
ctx->req.fields = new_field;
}
if (ctx->req.fields_tail) {
ctx->req.fields_tail->next = new_field;
}
ctx->req.fields_tail = new_field;
// check if we finished parsing the fields
if (istrGet(&in) == '\r') {
if (istrGet(&in) == '\n') {
finished_parsing = true;
break;
}
else {
istrRewindN(&in, 2);
warn("should have finished parsing field, but apparently not?: (%.*s)", in.cur, istrRemaining(in));
}
}
else {
istrRewindN(&in, 1);
}
}
if (!finished_parsing) {
break;
}
ctx->status = PARSE_REQ_FINISHED;
break;
}
default: break;
}
#undef RESET_STREAM
ctx->prevbuf_len = istrRemaining(in);
memmove(ctx->fullbuf, ctx->fullbuf + istrTell(in), ctx->prevbuf_len);
return ctx->status == PARSE_REQ_FINISHED;
}
void server__parse_req_url(arena_t *arena, server_req_t *req) {
instream_t in = istrInitLen(req->page.buf, req->page.len);
istrIgnore(&in, '?');
// no fields in url
if (istrGet(&in) != '?') {
return;
}
req->page.len = istrTell(in) - 1;
req->page.buf[req->page.len] = '\0';
while (!istrIsFinished(in)) {
strview_t field = istrGetView(&in, '&');
istrSkip(&in, 1); // skip &
usize pos = strvFind(field, '=', 0);
if (pos == SIZE_MAX) {
fatal("url parameter does not include =: %.*s", field.buf, field.len);
}
strview_t key = strvSub(field, 0, pos);
strview_t val = strvSub(field, pos + 1, SIZE_MAX);
server_field_t *f = alloc(arena, server_field_t);
f->key = str(arena, key);
f->value = str(arena, val);
f->next = req->page_fields;
req->page_fields = f;
}
}
server_t *serverSetup(arena_t *arena, uint16 port) {
if (!skInit()) {
fatal("couldn't initialise sockets: %s", skGetErrorString());
}
socket_t sk = skOpen(SOCK_TCP);
if (!skIsValid(sk)) {
fatal("couldn't open socket: %s", skGetErrorString());
}
skaddrin_t addr = {
.sin_family = AF_INET,
.sin_addr.s_addr = INADDR_ANY,
.sin_port = htons(port),
};
if (!skBindPro(sk, (skaddr_t *)&addr, sizeof(addr))) {
fatal("could not bind socket: %s", skGetErrorString());
}
if (!skListenPro(sk, 10)) {
fatal("could not listen on socket: %s", skGetErrorString());
}
server_t *server = alloc(arena, server_t);
server->socket = sk;
return server;
}
void serverRoute(arena_t *arena, server_t *server, strview_t page, server_route_f cb, void *userdata) {
// check if a route for that page already exists
server__route_t *r = server->routes;
while (r) {
if (strvEquals(strv(r->page), page)) {
r->fn = cb;
r->userdata = userdata;
break;
}
r = r->next;
}
// no route found, make a new one
if (!r) {
r = alloc(arena, server__route_t);
r->next = server->routes;
server->routes = r;
r->page = str(arena, page);
}
r->fn = cb;
r->userdata = userdata;
}
void serverRouteDefault(arena_t *arena, server_t *server, server_route_f cb, void *userdata) {
server__route_t *r = server->routes_default;
if (!r) {
r = alloc(arena, server__route_t);
server->routes_default = r;
}
r->fn = cb;
r->userdata = userdata;
}
void serverStart(arena_t scratch, server_t *server) {
usize start = arenaTell(&scratch);
while (!server->should_stop) {
socket_t client = skAccept(server->socket);
if (!skIsValid(client)) {
err("accept failed");
continue;
}
info("received connection: %zu", client);
arenaRewind(&scratch, start);
server__req_ctx_t req_ctx = {0};
char buffer[SERVER_BUFSZ];
int read = 0;
do {
read = skReceive(client, buffer, sizeof(buffer));
if(read == -1) {
fatal("couldn't get the data from the server: %s", skGetErrorString());
}
if (server__parse_chunk(&scratch, &req_ctx, buffer, read)) {
break;
}
} while(read != 0);
info("parsed request");
server_req_t req = req_ctx.req;
server__parse_req_url(&scratch, &req);
server__route_t *route = server->routes;
while (route) {
if (strEquals(route->page, req.page)) {
break;
}
route = route->next;
}
if (!route) {
route = server->routes_default;
}
str_t response = route->fn(scratch, server, &req, route->userdata);
skSend(client, response.buf, response.len);
skClose(client);
}
if (!skClose(server->socket)) {
fatal("couldn't close socket: %s", skGetErrorString());
}
}
void serverStop(server_t *server) {
server->should_stop = true;
}
str_t serverMakeResponse(arena_t *arena, int status_code, strview_t content_type, strview_t body) {
return strFmt(
arena,
"HTTP/1.1 %d %s\r\n"
"Content-Type: %.*s\r\n"
"\r\n"
"%.*s",
status_code, httpGetStatusString(status_code),
content_type.len, content_type.buf,
body.len, body.buf
);
}
#undef SERVER_BUFSZ

36
colla/server.h Normal file
View file

@ -0,0 +1,36 @@
#pragma once
#include "collatypes.h"
#include "str.h"
#include "http.h"
typedef struct arena_t arena_t;
typedef struct server_t server_t;
typedef struct server_field_t {
str_t key;
str_t value;
struct server_field_t *next;
} server_field_t;
typedef struct {
http_method_e method;
str_t page;
server_field_t *page_fields;
uint8 version_minor;
uint8 version_major;
server_field_t *fields;
server_field_t *fields_tail;
// buffer_t body;
} server_req_t;
typedef str_t (*server_route_f)(arena_t scratch, server_t *server, server_req_t *req, void *userdata);
server_t *serverSetup(arena_t *arena, uint16 port);
void serverRoute(arena_t *arena, server_t *server, strview_t page, server_route_f cb, void *userdata);
void serverRouteDefault(arena_t *arena, server_t *server, server_route_f cb, void *userdata);
void serverStart(arena_t scratch, server_t *server);
void serverStop(server_t *server);
str_t serverMakeResponse(arena_t *arena, int status_code, strview_t content_type, strview_t body);

View file

@ -1,119 +1,114 @@
#include "socket.h"
#include <stdio.h>
#include "tracelog.h"
#ifndef NDEBUG
// VERY MUCH NOT THREAD SAFE
static int initialize_count = 0;
#if COLLA_WIN && COLLA_CMT_LIB
#pragma comment(lib, "Ws2_32")
#endif
#if SOCK_WINDOWS
static bool _win_skInit();
static bool _win_skCleanup();
static int _win_skGetError();
static const char *_win_skGetErrorString();
#if COLLA_WIN
#include <winsock2.h>
#define SOCK_CALL(fun) _win_##fun
bool skInit(void) {
WSADATA w;
int error = WSAStartup(0x0202, &w);
return error == 0;
}
#elif SOCK_POSIX
bool skCleanup(void) {
return WSACleanup() == 0;
}
bool skClose(socket_t sock) {
return closesocket(sock) != -1;
}
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) {
return WSAPoll(to_poll, num_to_poll, timeout);
}
int skGetError(void) {
return WSAGetLastError();
}
#else
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>
#include <string.h> // strerror
#include <poll.h>
#define INVALID_SOCKET (-1)
#define SOCKET_ERROR (-1)
static bool _posix_skInit();
static bool _posix_skCleanup();
static int _posix_skGetError();
static const char *_posix_skGetErrorString();
#define SOCK_CALL(fun) _posix_##fun
#endif
bool skInit() {
#ifndef NDEBUG
++initialize_count;
#endif
return SOCK_CALL(skInit());
bool skInit(void) {
return true;
}
bool skCleanup() {
#ifndef NDEBUG
--initialize_count;
#endif
return SOCK_CALL(skCleanup());
bool skCleanup(void) {
return true;
}
socket_t skOpen(sktype_t type) {
bool skClose(socket_t sock) {
return close(sock) != -1;
}
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) {
return poll(to_poll, num_to_poll, timeout);
}
int skGetError(void) {
return errno;
}
const char *skGetErrorString(void) {
return strerror(errno);
}
#endif
socket_t skOpen(sktype_e type) {
int sock_type = 0;
switch(type) {
case SOCK_TCP: sock_type = SOCK_STREAM; break;
case SOCK_UDP: sock_type = SOCK_DGRAM; break;
default: fatal("skType not recognized: %d", type); break;
case SOCK_TCP: sock_type = SOCK_STREAM; break;
case SOCK_UDP: sock_type = SOCK_DGRAM; break;
default: fatal("skType not recognized: %d", type); break;
}
return skOpenPro(AF_INET, sock_type, 0);
}
socket_t skOpenEx(const char *protocol) {
#ifndef NDEBUG
if(initialize_count <= 0) {
fatal("skInit has not been called");
}
#endif
struct protoent *proto = getprotobyname(protocol);
if(!proto) {
return INVALID_SOCKET;
return (socket_t){0};
}
return skOpenPro(AF_INET, SOCK_STREAM, proto->p_proto);
}
socket_t skOpenPro(int af, int type, int protocol) {
#ifndef NDEBUG
if(initialize_count <= 0) {
fatal("skInit has not been called");
}
#endif
return socket(af, type, protocol);
}
sk_addrin_t skAddrinInit(const char *ip, uint16_t port) {
sk_addrin_t addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip);
return addr;
bool skIsValid(socket_t sock) {
return sock != (socket_t)-1;
}
bool skClose(socket_t sock) {
#if SOCK_WINDOWS
int error = closesocket(sock);
#elif SOCK_POSIX
int error = close(sock);
#endif
sock = INVALID_SOCKET;
return error != SOCKET_ERROR;
skaddrin_t skAddrinInit(const char *ip, uint16_t port) {
return (skaddrin_t){
.sin_family = AF_INET,
.sin_port = htons(port),
// TODO use inet_pton instead
.sin_addr.s_addr = inet_addr(ip),
};
}
bool skBind(socket_t sock, const char *ip, uint16_t port) {
sk_addrin_t addr;
addr.sin_family = AF_INET;
// TODO use inet_pton instead
addr.sin_addr.s_addr = inet_addr(ip);
addr.sin_port = htons(port);
return skBindPro(sock, (sk_addr_t *) &addr, sizeof(addr));
skaddrin_t addr = skAddrinInit(ip, port);
return skBindPro(sock, (skaddr_t *)&addr, sizeof(addr));
}
bool skBindPro(socket_t sock, const sk_addr_t *name, sk_len_t namelen) {
return bind(sock, name, namelen) != SOCKET_ERROR;
bool skBindPro(socket_t sock, const skaddr_t *name, int namelen) {
return bind(sock, name, namelen) != -1;
}
bool skListen(socket_t sock) {
@ -121,16 +116,16 @@ bool skListen(socket_t sock) {
}
bool skListenPro(socket_t sock, int backlog) {
return listen(sock, backlog) != SOCKET_ERROR;
return listen(sock, backlog) != -1;
}
socket_t skAccept(socket_t sock) {
sk_addrin_t addr;
sk_len_t addr_size = (sk_len_t)sizeof(addr);
return skAcceptPro(sock, (sk_addr_t *) &addr, &addr_size);
skaddrin_t addr = {0};
int addr_size = sizeof(addr);
return skAcceptPro(sock, (skaddr_t *)&addr, &addr_size);
}
socket_t skAcceptPro(socket_t sock, sk_addr_t *addr, sk_len_t *addrlen) {
socket_t skAcceptPro(socket_t sock, skaddr_t *addr, int *addrlen) {
return accept(sock, addr, addrlen);
}
@ -143,16 +138,13 @@ bool skConnect(socket_t sock, const char *server, unsigned short server_port) {
address = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]);
}
sk_addrin_t addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(address);
addr.sin_port = htons(server_port);
skaddrin_t addr = skAddrinInit(address, server_port);
return skConnectPro(sock, (sk_addr_t *) &addr, sizeof(addr));
return skConnectPro(sock, (skaddr_t *)&addr, sizeof(addr));
}
bool skConnectPro(socket_t sock, const sk_addr_t *name, sk_len_t namelen) {
return connect(sock, name, namelen) != SOCKET_ERROR;
bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen) {
return connect(sock, name, namelen) != -1;
}
int skSend(socket_t sock, const void *buf, int len) {
@ -163,11 +155,11 @@ int skSendPro(socket_t sock, const void *buf, int len, int flags) {
return send(sock, buf, len, flags);
}
int skSendTo(socket_t sock, const void *buf, int len, const sk_addrin_t *to) {
return skSendToPro(sock, buf, len, 0, (sk_addr_t*) to, sizeof(sk_addrin_t));
int skSendTo(socket_t sock, const void *buf, int len, const skaddrin_t *to) {
return skSendToPro(sock, buf, len, 0, (skaddr_t*)to, sizeof(skaddrin_t));
}
int skSendToPro(socket_t sock, const void *buf, int len, int flags, const sk_addr_t *to, int tolen) {
int skSendToPro(socket_t sock, const void *buf, int len, int flags, const skaddr_t *to, int tolen) {
return sendto(sock, buf, len, flags, to, tolen);
}
@ -179,52 +171,19 @@ int skReceivePro(socket_t sock, void *buf, int len, int flags) {
return recv(sock, buf, len, flags);
}
int skReceiveFrom(socket_t sock, void *buf, int len, sk_addrin_t *from) {
sk_len_t fromlen = sizeof(sk_addr_t);
return skReceiveFromPro(sock, buf, len, 0, (sk_addr_t*)from, &fromlen);
int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from) {
int fromlen = sizeof(skaddr_t);
return skReceiveFromPro(sock, buf, len, 0, (skaddr_t*)from, &fromlen);
}
int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, sk_addr_t *from, sk_len_t *fromlen) {
int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, skaddr_t *from, int *fromlen) {
return recvfrom(sock, buf, len, flags, from, fromlen);
}
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) {
#if SOCK_WINDOWS
return WSAPoll(to_poll, num_to_poll, timeout);
#elif SOCK_POSIX
return poll(to_poll, num_to_poll, timeout);
#endif
}
bool skIsValid(socket_t sock) {
return sock != INVALID_SOCKET;
}
int skGetError() {
return SOCK_CALL(skGetError());
}
const char *skGetErrorString() {
return SOCK_CALL(skGetErrorString());
}
#ifdef SOCK_WINDOWS
static bool _win_skInit() {
WSADATA w;
int error = WSAStartup(0x0202, &w);
return error == 0;
}
static bool _win_skCleanup() {
return WSACleanup() == 0;
}
static int _win_skGetError() {
return WSAGetLastError();
}
static const char *_win_skGetErrorString() {
switch(_win_skGetError()) {
// put this at the end of file to not make everything else unreadable
#if COLLA_WIN
const char *skGetErrorString(void) {
switch(skGetError()) {
case WSA_INVALID_HANDLE: return "Specified event object handle is invalid.";
case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available.";
case WSA_INVALID_PARAMETER: return "One or more parameters are invalid.";
@ -324,22 +283,4 @@ static const char *_win_skGetErrorString() {
return "(nothing)";
}
#else
static bool _posix_skInit() {
return true;
}
static bool _posix_skCleanup() {
return true;
}
static int _posix_skGetError() {
return errno;
}
static const char *_posix_skGetErrorString() {
return strerror(errno);
}
#endif
#endif

View file

@ -1,45 +1,22 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "collatypes.h"
#include <stdbool.h>
#include <stdint.h>
#ifdef _WIN32
#define SOCK_WINDOWS 1
#if COLLA_WIN
#include <winsock2.h>
#else
#define SOCK_POSIX 1
#include <sys/socket.h>
#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 uintptr_t socket_t;
typedef struct sockaddr skaddr_t;
typedef struct sockaddr_in skaddrin_t;
typedef struct pollfd skpoll_t;
typedef enum {
SOCK_TCP,
SOCK_UDP,
} sktype_t;
// == RAW SOCKETS ==========================================
} sktype_e;
// Initialize sockets, returns true on success
bool skInit(void);
@ -47,7 +24,7 @@ bool skInit(void);
bool skCleanup(void);
// Opens a socket, check socket_t with skValid
socket_t skOpen(sktype_t type);
socket_t skOpen(sktype_e type);
// Opens a socket using 'protocol', options are
// ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp
// check socket_t with skValid
@ -62,12 +39,12 @@ bool skIsValid(socket_t sock);
bool skClose(socket_t sock);
// Fill out a sk_addrin_t structure with "ip" and "port"
sk_addrin_t skAddrinInit(const char *ip, uint16_t port);
skaddrin_t skAddrinInit(const char *ip, uint16_t port);
// Associate a local address with a socket
bool skBind(socket_t sock, const char *ip, uint16_t port);
// Associate a local address with a socket
bool skBindPro(socket_t sock, const sk_addr_t *name, sk_len_t namelen);
bool skBindPro(socket_t sock, const skaddr_t *name, int namelen);
// Place a socket in a state in which it is listening for an incoming connection
bool skListen(socket_t sock);
@ -77,29 +54,29 @@ bool skListenPro(socket_t sock, int backlog);
// Permits an incoming connection attempt on a socket
socket_t skAccept(socket_t sock);
// Permits an incoming connection attempt on a socket
socket_t skAcceptPro(socket_t sock, sk_addr_t *addr, sk_len_t *addrlen);
socket_t skAcceptPro(socket_t sock, skaddr_t *addr, int *addrlen);
// Connects to a server (e.g. "127.0.0.1" or "google.com") with a port(e.g. 1234), returns true on success
bool skConnect(socket_t sock, const char *server, unsigned short server_port);
// Connects to a server, returns true on success
bool skConnectPro(socket_t sock, const sk_addr_t *name, sk_len_t namelen);
bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen);
// Sends data on a socket, returns true on success
int skSend(socket_t sock, const void *buf, int len);
// Sends data on a socket, returns true on success
int skSendPro(socket_t sock, const void *buf, int len, int flags);
// Sends data to a specific destination
int skSendTo(socket_t sock, const void *buf, int len, const sk_addrin_t *to);
int skSendTo(socket_t sock, const void *buf, int len, const skaddrin_t *to);
// Sends data to a specific destination
int skSendToPro(socket_t sock, const void *buf, int len, int flags, const sk_addr_t *to, int tolen);
int skSendToPro(socket_t sock, const void *buf, int len, int flags, const skaddr_t *to, int tolen);
// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error
int skReceive(socket_t sock, void *buf, int len);
// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error
int skReceivePro(socket_t sock, void *buf, int len, int flags);
// Receives a datagram and stores the source address.
int skReceiveFrom(socket_t sock, void *buf, int len, sk_addrin_t *from);
int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from);
// Receives a datagram and stores the source address.
int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, sk_addr_t *from, sk_len_t *fromlen);
int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, skaddr_t *from, int *fromlen);
// Wait for an event on some sockets
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout);
@ -108,14 +85,3 @@ int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout);
int skGetError(void);
// Returns a human-readable string from a skGetError
const char *skGetErrorString(void);
// == UDP SOCKETS ==========================================
typedef socket_t udpsock_t;
#ifdef __cplusplus
} // extern "C"
#endif

1930
colla/stb/stb_sprintf.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,291 +1,259 @@
#include "str.h"
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <ctype.h>
#include <assert.h>
#include <stdio.h>
#include "warnings/colla_warn_beg.h"
#include "arena.h"
#include "format.h"
#include "tracelog.h"
#include "strstream.h"
#ifdef _WIN32
#include "win32_slim.h"
#else
#include <iconv.h>
#endif
#ifndef min
#define min(a, b) ((a) < (b) ? (a) : (b))
#if COLLA_WIN
#include <stringapiset.h>
#endif
// == STR_T ========================================================
str_t strInit(void) {
return (str_t) {
.buf = NULL,
.len = 0
str_t strInit(arena_t *arena, const char *buf) {
return buf ? strInitLen(arena, buf, strlen(buf)) : (str_t){0};
}
str_t strInitLen(arena_t *arena, const char *buf, usize len) {
if (!buf || !len) return (str_t){0};
str_t out = {
.buf = alloc(arena, char, len + 1),
.len = len
};
}
str_t strFromStr(const char *cstr) {
return cstr ? strFromBuf(cstr, strlen(cstr)) : strInit();
}
memcpy(out.buf, buf, len);
str_t strFromView(strview_t view) {
return strFromBuf(view.buf, view.len);
}
str_t strFromBuf(const char *buf, usize len) {
if (!buf) return strInit();
str_t str;
str.len = len;
str.buf = (char *)malloc(len + 1);
memcpy(str.buf, buf, len);
str.buf[len] = '\0';
return str;
}
str_t strFromFmt(const char *fmt, ...) {
str_ostream_t out = ostrInit();
va_list va;
va_start(va, fmt);
ostrPrintfV(&out, fmt, va);
va_end(va);
return ostrAsStr(out);
}
void strFree(str_t ctx) {
free(ctx.buf);
}
str_t strFromWCHAR(const wchar_t *src, usize len) {
if(len == 0) len = wcslen(src);
#ifdef _WIN32
// TODO CP_ACP should be CP_UTF8 but if i put CP_UTF8 it doesn't work??
int result_len = WideCharToMultiByte(
CP_ACP, 0,
src, (int)len,
NULL, 0,
NULL, NULL
);
char *buf = (char *)malloc(result_len + 1);
if(buf) {
WideCharToMultiByte(
CP_ACP, 0,
src, (int)len,
buf, result_len,
NULL, NULL
);
buf[result_len] = '\0';
}
return (str_t) {
.buf = buf,
.len = result_len
};
#else
usize actual_len = len * sizeof(wchar_t);
usize dest_len = len * 6;
char *dest = (char *)malloc(dest_len);
iconv_t cd = iconv_open("UTF-8", "WCHAR_T");
assert(cd);
usize dest_left = dest_len;
char *dest_temp = dest;
char *src_temp = (char*)src;
usize lost = iconv(cd, &src_temp, &actual_len, &dest_temp, &dest_left);
assert(lost != ((usize)-1));
(void)lost;
dest_len -= dest_left;
dest = (char *)realloc(dest, dest_len + 1);
dest[dest_len] = '\0';
iconv_close(cd);
return (str_t){
.buf = dest,
.len = dest_len
};
#endif
}
wchar_t *strToWCHAR(str_t ctx) {
#ifdef _WIN32
UINT codepage = CP_ACP;
int len = MultiByteToWideChar(
codepage, 0,
ctx.buf, (int)ctx.len,
NULL, 0
);
wchar_t *str = (wchar_t *)malloc(sizeof(wchar_t) * (len + 1));
if(!str) return NULL;
len = MultiByteToWideChar(
codepage, 0,
ctx.buf, (int)ctx.len,
str, len
);
str[len] = '\0';
return str;
#else
usize dest_len = ctx.len * sizeof(wchar_t);
char *dest = (char *)malloc(dest_len);
iconv_t cd = iconv_open("WCHAR_T", "UTF-8");
assert(cd);
usize dest_left = dest_len;
char *dest_temp = dest;
char *src_temp = ctx.buf;
usize lost = iconv(cd, &src_temp, &ctx.len, &dest_temp, &dest_left);
assert(lost != ((usize)-1));
(void)lost;
dest_len -= dest_left;
dest = (char *)realloc(dest, dest_len + 1);
dest[dest_len] = '\0';
iconv_close(cd);
return (wchar_t *)dest;
#endif
}
str_t strDup(str_t ctx) {
return strFromBuf(ctx.buf, ctx.len);
}
str_t strMove(str_t *ctx) {
str_t out = *ctx;
ctx->buf = NULL;
ctx->len = 0;
return out;
}
strview_t strGetView(str_t ctx) {
return (strview_t) {
.buf = ctx.buf,
.len = ctx.len
};
str_t strInitView(arena_t *arena, strview_t view) {
return strInitLen(arena, view.buf, view.len);
}
char *strBegin(str_t ctx) {
return ctx.buf;
str_t strFmt(arena_t *arena, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
str_t out = strFmtv(arena, fmt, args);
va_end(args);
return out;
}
char *strEnd(str_t ctx) {
return ctx.buf ? ctx.buf + ctx.len : NULL;
str_t strFmtv(arena_t *arena, const char *fmt, va_list args) {
va_list vcopy;
va_copy(vcopy, args);
int len = fmtBufferv(NULL, 0, fmt, vcopy);
va_end(vcopy);
char *buffer = alloc(arena, char, len + 1);
fmtBufferv(buffer, len + 1, fmt, args);
return (str_t){ .buf = buffer, .len = (usize)len };
}
char strBack(str_t ctx) {
return ctx.buf ? ctx.buf[ctx.len - 1] : '\0';
str_t strFromWChar(arena_t *arena, const wchar_t *src, usize srclen) {
if (!src) return (str_t){0};
if (!srclen) srclen = wcslen(src);
str_t out = {0};
#if COLLA_WIN
int outlen = WideCharToMultiByte(
CP_UTF8, 0,
src, (int)srclen,
NULL, 0,
NULL, NULL
);
if (outlen == 0) {
unsigned long error = GetLastError();
if (error == ERROR_NO_UNICODE_TRANSLATION) {
err("couldn't translate wide string (%S) to utf8, no unicode translation", src);
}
else {
err("couldn't translate wide string (%S) to utf8, %u", error);
}
return (str_t){0};
}
out.buf = alloc(arena, char, outlen + 1);
WideCharToMultiByte(
CP_UTF8, 0,
src, (int)srclen,
out.buf, outlen,
NULL, NULL
);
#elif COLLA_LIN
fatal("strFromWChar not implemented yet!");
#endif
return out;
}
bool strEquals(str_t a, str_t b) {
return strCompare(a, b) == 0;
}
int strCompare(str_t a, str_t b) {
return a.len == b.len ?
memcmp(a.buf, b.buf, a.len) :
a.len - (int)b.len;
}
str_t strDup(arena_t *arena, str_t src) {
return strInitLen(arena, src.buf, src.len);
}
bool strIsEmpty(str_t ctx) {
return ctx.len == 0;
}
void strSwap(str_t *ctx, str_t *other) {
char *buf = other->buf;
usize len = other->len;
other->buf = ctx->buf;
other->len = ctx->len;
ctx->buf = buf;
ctx->len = len;
return ctx.len == 0 || ctx.buf == NULL;
}
void strReplace(str_t *ctx, char from, char to) {
for(usize i = 0; i < ctx->len; ++i) {
if(ctx->buf[i] == from) {
ctx->buf[i] = to;
}
if (!ctx) return;
char *buf = ctx->buf;
for (usize i = 0; i < ctx->len; ++i) {
buf[i] = buf[i] == from ? to : buf[i];
}
}
str_t strSubstr(str_t ctx, usize from, usize to) {
if(strIsEmpty(ctx)) return strInit();
strview_t strSub(str_t ctx, usize from, usize to) {
if (to > ctx.len) to = ctx.len;
if (from > to) from = to;
return strFromBuf(ctx.buf + from, to - from);
}
strview_t strSubview(str_t ctx, usize from, usize to) {
if(strIsEmpty(ctx)) return strvInit(NULL);
if (to > ctx.len) to = ctx.len;
if (from > to) from = to;
return strvInitLen(ctx.buf + from, to - from);
if (from > to) from = to;
return (strview_t){ ctx.buf + from, to - from };
}
void strLower(str_t *ctx) {
for(usize i = 0; i < ctx->len; ++i) {
ctx->buf[i] = (char)tolower(ctx->buf[i]);
char *buf = ctx->buf;
for (usize i = 0; i < ctx->len; ++i) {
buf[i] = (buf[i] >= 'A' && buf[i] <= 'Z') ?
buf[i] += 'a' - 'A' :
buf[i];
}
}
str_t strToLower(str_t ctx) {
str_t str = strDup(ctx);
strLower(&str);
return str;
}
void strUpper(str_t *ctx) {
for(usize i = 0; i < ctx->len; ++i) {
ctx->buf[i] = (char)toupper(ctx->buf[i]);
char *buf = ctx->buf;
for (usize i = 0; i < ctx->len; ++i) {
buf[i] = (buf[i] >= 'a' && buf[i] <= 'z') ?
buf[i] -= 'a' - 'A' :
buf[i];
}
}
str_t strToUpper(str_t ctx) {
str_t str = strDup(ctx);
strUpper(&str);
return str;
str_t strToLower(arena_t *arena, str_t ctx) {
strLower(&ctx);
return strDup(arena, ctx);
}
str_t strToUpper(arena_t *arena, str_t ctx) {
strUpper(&ctx);
return strDup(arena, ctx);
}
// == STRVIEW_T ====================================================
strview_t strvInit(const char *cstr) {
return strvInitLen(cstr, cstr ? strlen(cstr) : 0);
}
strview_t strvInitStr(str_t str) {
return strvInitLen(str.buf, str.len);
}
strview_t strvInitLen(const char *buf, usize size) {
return (strview_t) {
.buf = buf,
.len = size
return (strview_t){
.buf = cstr,
.len = cstr ? strlen(cstr) : 0,
};
}
char strvFront(strview_t ctx) {
return ctx.buf[0];
strview_t strvInitLen(const char *buf, usize size) {
return (strview_t){
.buf = buf,
.len = size,
};
}
char strvBack(strview_t ctx) {
return ctx.buf[ctx.len - 1];
strview_t strvInitStr(str_t str) {
return (strview_t){
.buf = str.buf,
.len = str.len
};
}
const char *strvBegin(strview_t ctx) {
return ctx.buf;
}
const char *strvEnd(strview_t ctx) {
return ctx.buf + ctx.len;
}
bool strvIsEmpty(strview_t ctx) {
return ctx.len == 0;
return ctx.len == 0 || !ctx.buf;
}
bool strvEquals(strview_t a, strview_t b) {
return strvCompare(a, b) == 0;
}
int strvCompare(strview_t a, strview_t b) {
return a.len == b.len ?
memcmp(a.buf, b.buf, a.len) :
a.len - (int)b.len;
}
wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen) {
wchar_t *out = NULL;
int len = 0;
if (strvIsEmpty(ctx)) {
goto error;
}
#if COLLA_WIN
len = MultiByteToWideChar(
CP_UTF8, 0,
ctx.buf, (int)ctx.len,
NULL, 0
);
if (len == 0) {
unsigned long error = GetLastError();
if (error == ERROR_NO_UNICODE_TRANSLATION) {
err("couldn't translate string (%v) to a wide string, no unicode translation", ctx);
}
else {
err("couldn't translate string (%v) to a wide string, %u", ctx, error);
}
goto error;
}
out = alloc(arena, wchar_t, len + 1);
MultiByteToWideChar(
CP_UTF8, 0,
ctx.buf, (int)ctx.len,
out, len
);
#elif COLLA_LIN
fatal("strFromWChar not implemented yet!");
#endif
error:
if (outlen) {
*outlen = (usize)len;
}
return out;
}
TCHAR *strvToTChar(arena_t *arena, strview_t str) {
#if UNICODE
return strvToWChar(arena, str, NULL);
#else
char *cstr = alloc(arena, char, str.len + 1);
memcpy(cstr, str.buf, str.len);
return cstr;
#endif
}
strview_t strvRemovePrefix(strview_t ctx, usize n) {
if (n > ctx.len) n = ctx.len;
return (strview_t){
.buf = ctx.buf + n,
.len = ctx.len - n
.len = ctx.len - n,
};
}
@ -293,9 +261,9 @@ strview_t strvRemoveSuffix(strview_t ctx, usize n) {
if (n > ctx.len) n = ctx.len;
return (strview_t){
.buf = ctx.buf,
.len = ctx.len - n
.len = ctx.len - n,
};
}
}
strview_t strvTrim(strview_t ctx) {
return strvTrimLeft(strvTrimRight(ctx));
@ -303,334 +271,115 @@ strview_t strvTrim(strview_t ctx) {
strview_t strvTrimLeft(strview_t ctx) {
strview_t out = ctx;
for (usize i = 0; i < ctx.len && isspace(ctx.buf[i]); ++i) {
++out.buf;
--out.len;
for (usize i = 0; i < ctx.len; ++i) {
char c = ctx.buf[i];
if (c != ' ' || c < '\t' || c > '\r') {
break;
}
out.buf++;
out.len--;
}
return out;
}
strview_t strvTrimRight(strview_t ctx) {
strview_t out = ctx;
for (isize i = ctx.len - 1; i >= 0 && isspace(ctx.buf[i]); --i) {
--out.len;
for (isize i = ctx.len - 1; i >= 0; --i) {
char c = ctx.buf[i];
if (c != ' ' || c < '\t' || c > '\r') {
break;
}
out.len--;
}
return out;
}
str_t strvCopy(strview_t ctx) {
return strFromView(ctx);
}
str_t strvCopyN(strview_t ctx, usize count, usize from) {
usize sz = ctx.len + 1 - from;
count = min(count, sz);
return strFromBuf(ctx.buf + from, count);
}
usize strvCopyBuf(strview_t ctx, char *buf, usize len, usize from) {
usize sz = ctx.len + 1 - from;
len = min(len, sz);
memcpy(buf, ctx.buf + from, len);
buf[len - 1] = '\0';
return len - 1;
}
strview_t strvSub(strview_t ctx, usize from, usize to) {
if (to > ctx.len) to = ctx.len;
if (from > to) from = to;
return strvInitLen(ctx.buf + from, to - from);
}
int strvCompare(strview_t ctx, strview_t other) {
if(ctx.len < other.len) return -1;
if(ctx.len > other.len) return 1;
return memcmp(ctx.buf, other.buf, ctx.len);
}
int strvICompare(strview_t ctx, strview_t other) {
if(ctx.len < other.len) return -1;
if(ctx.len > other.len) return 1;
for(usize i = 0; i < ctx.len; ++i) {
int a = tolower(ctx.buf[i]);
int b = tolower(other.buf[i]);
if(a != b) return a - b;
}
return 0;
return (strview_t){ ctx.buf + from, to - from };
}
bool strvStartsWith(strview_t ctx, char c) {
return strvFront(ctx) == c;
return ctx.len > 0 && ctx.buf[0] == c;
}
bool strvStartsWithView(strview_t ctx, strview_t view) {
if(ctx.len < view.len) return false;
return memcmp(ctx.buf, view.buf, view.len) == 0;
return ctx.len >= view.len && memcmp(ctx.buf, view.buf, view.len) == 0;
}
bool strvEndsWith(strview_t ctx, char c) {
return strvBack(ctx) == c;
return ctx.len > 0 && ctx.buf[ctx.len - 1] == c;
}
bool strvEndsWithView(strview_t ctx, strview_t view) {
if(ctx.len < view.len) return false;
return memcmp(ctx.buf + ctx.len - view.len, view.buf, view.len) == 0;
return ctx.len >= view.len && memcmp(ctx.buf + ctx.len, view.buf, view.len) == 0;
}
bool strvContains(strview_t ctx, char c) {
for(usize i = 0; i < ctx.len; ++i) {
if(ctx.buf[i] == c) return true;
if(ctx.buf[i] == c) {
return true;
}
}
return false;
}
bool strvContainsView(strview_t ctx, strview_t view) {
if(ctx.len < view.len) return false;
if (ctx.len < view.len) return false;
usize end = ctx.len - view.len;
for(usize i = 0; i < end; ++i) {
if(memcmp(ctx.buf + i, view.buf, view.len) == 0) return true;
for (usize i = 0; i < end; ++i) {
if (memcmp(ctx.buf + i, view.buf, view.len) == 0) {
return true;
}
}
return false;
}
usize strvFind(strview_t ctx, char c, usize from) {
for(usize i = from; i < ctx.len; ++i) {
if(ctx.buf[i] == c) return i;
}
return SIZE_MAX;
}
usize strvFindView(strview_t ctx, strview_t view, usize from) {
if(ctx.len < view.len) return SIZE_MAX;
usize end = ctx.len - view.len;
for(usize i = from; i < end; ++i) {
if(memcmp(ctx.buf + i, view.buf, view.len) == 0) return i;
}
return SIZE_MAX;
}
usize strvRFind(strview_t ctx, char c, usize from) {
if(from >= ctx.len) {
from = ctx.len;
}
from = ctx.len - from;
const char *buf = ctx.buf + from;
for(; buf >= ctx.buf; --buf) {
if(*buf == c) return (buf - ctx.buf);
}
return SIZE_MAX;
}
usize strvRFindView(strview_t ctx, strview_t view, usize from) {
if(view.len > ctx.len) {
return SIZE_MAX;
}
if(from > ctx.len) {
from = ctx.len;
}
from = ctx.len - from;
from -= view.len;
const char *buf = ctx.buf + from;
for(; buf >= ctx.buf; --buf) {
if(memcmp(buf, view.buf, view.len) == 0) return (buf - ctx.buf);
}
return SIZE_MAX;
}
usize strvFindFirstOf(strview_t ctx, strview_t view, usize from) {
if(ctx.len < view.len) return SIZE_MAX;
for(usize i = from; i < ctx.len; ++i) {
for(usize j = 0; j < view.len; ++j) {
if(ctx.buf[i] == view.buf[j]) return i;
}
}
return SIZE_MAX;
}
usize strvFindLastOf(strview_t ctx, strview_t view, usize from) {
if(from >= ctx.len) {
from = ctx.len - 1;
}
const char *buf = ctx.buf + from;
for(; buf >= ctx.buf; --buf) {
for(usize j = 0; j < view.len; ++j) {
if(*buf == view.buf[j]) return (buf - ctx.buf);
}
}
return SIZE_MAX;
}
usize strvFindFirstNot(strview_t ctx, char c, usize from) {
usize end = ctx.len - 1;
for(usize i = from; i < end; ++i) {
if(ctx.buf[i] != c) return i;
}
return SIZE_MAX;
}
usize strvFindFirstNotOf(strview_t ctx, strview_t view, usize from) {
for(usize i = from; i < ctx.len; ++i) {
if(!strvContains(view, ctx.buf[i])) {
for (usize i = from; i < ctx.len; ++i) {
if (ctx.buf[i] == c) {
return i;
}
}
return SIZE_MAX;
return STR_NONE;
}
usize strvFindLastNot(strview_t ctx, char c, usize from) {
if(from >= ctx.len) {
from = ctx.len - 1;
}
const char *buf = ctx.buf + from;
for(; buf >= ctx.buf; --buf) {
if(*buf != c) {
return buf - ctx.buf;
usize strvFindView(strview_t ctx, strview_t view, usize from) {
if (ctx.len < view.len) return STR_NONE;
usize end = ctx.len - view.len;
for (usize i = 0; i < end; ++i) {
if (memcmp(ctx.buf + i, view.buf, view.len) == 0) {
return i;
}
}
return SIZE_MAX;
return STR_NONE;
}
usize strvFindLastNotOf(strview_t ctx, strview_t view, usize from) {
if(from >= ctx.len) {
from = ctx.len - 1;
}
const char *buf = ctx.buf + from;
for(; buf >= ctx.buf; --buf) {
if(!strvContains(view, *buf)) {
return buf - ctx.buf;
usize strvRFind(strview_t ctx, char c, usize from_right) {
if (from_right > ctx.len) from_right = ctx.len;
isize end = (isize)(ctx.len - from_right);
for (isize i = end; i >= 0; --i) {
if (ctx.buf[i] == c) {
return (usize)i;
}
}
return SIZE_MAX;
return STR_NONE;
}
#ifdef STR_TESTING
#include <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);
usize strvRFindView(strview_t ctx, strview_t view, usize from_right) {
if (from_right > ctx.len) from_right = ctx.len;
isize end = (isize)(ctx.len - from_right);
if (end < view.len) return STR_NONE;
for (isize i = end - view.len; i >= 0; --i) {
if (memcmp(ctx.buf + i, view.buf, view.len) == 0) {
return (usize)i;
}
printf("]\n");
strFree(&s);
}
debug("== testing back/isempty =========");
{
s = strInitStr("hello world");
printf("[ ");
while(!strIsEmpty(&s)) {
printf("%c ", strBack(&s));
strPop(&s);
}
printf("]\n");
strFree(&s);
}
debug("== testing append ===============");
{
s = strInitStr("hello ");
printf("\"%s\" %zu\n", s.buf, s.len);
strAppend(&s, "world");
printf("\"%s\" %zu\n", s.buf, s.len);
strAppendView(&s, strvInit(", how is it "));
printf("\"%s\" %zu\n", s.buf, s.len);
uint8 buf[] = { 'g', 'o', 'i', 'n', 'g' };
strAppendBuf(&s, (char*)buf, sizeof(buf));
printf("\"%s\" %zu\n", s.buf, s.len);
strAppendChars(&s, '?', 2);
printf("\"%s\" %zu\n", s.buf, s.len);
strFree(&s);
}
debug("== testing push/pop =============");
{
s = strInit();
str_t s2 = strInitStr("hello world");
printf("%-14s %-14s\n", "s", "s2");
printf("----------------------------\n");
while(!strIsEmpty(&s2)) {
printf("%-14s %-14s\n", s.buf, s2.buf);
strPush(&s, strPop(&s2));
}
printf("%-14s %-14s\n", s.buf, s2.buf);
strFree(&s);
strFree(&s2);
}
debug("== testing swap =================");
{
s = strInitStr("hello");
str_t s2 = strInitStr("world");
printf("%-8s %-8s\n", "s", "s2");
printf("----------------\n");
printf("%-8s %-8s\n", s.buf, s2.buf);
strSwap(&s, &s2);
printf("%-8s %-8s\n", s.buf, s2.buf);
strFree(&s);
strFree(&s2);
}
debug("== testing substr ===============");
{
s = strInitStr("hello world");
printf("s: %s\n", s.buf);
printf("-- string\n");
str_t s2 = strSubstr(&s, 0, 5);
printf("0..5: \"%s\"\n", s2.buf);
strFree(&s2);
s2 = strSubstr(&s, 5, SIZE_MAX);
printf("6..SIZE_MAX: \"%s\"\n", s2.buf);
strFree(&s2);
printf("-- view\n");
strview_t v = strSubview(&s, 0, 5);
printf("0..5: \"%.*s\"\n", (int)v.len, v.buf);
v = strSubview(&s, 5, SIZE_MAX);
printf("6..SIZE_MAX: \"%.*s\"\n", (int)v.len, v.buf);
strFree(&s);
}
strFree(&s);
return STR_NONE;
}
#endif
#include "warnings/colla_warn_beg.h"
#undef CP_UTF8
#undef ERROR_NO_UNICODE_TRANSLATION

View file

@ -1,19 +1,15 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stddef.h>
#include <limits.h>
#include <wchar.h>
#include <stdarg.h> // va_list
#include <string.h> // strlen
#include "collatypes.h"
#define STRV_NOT_FOUND SIZE_MAX
typedef struct arena_t arena_t;
typedef struct str_t {
#define STR_NONE SIZE_MAX
typedef struct {
char *buf;
usize len;
} str_t;
@ -25,73 +21,77 @@ typedef struct {
// == STR_T ========================================================
str_t strInit(void);
str_t strFromStr(const char *cstr);
str_t strFromView(strview_t view);
str_t strFromBuf(const char *buf, usize len);
str_t strFromFmt(const char *fmt, ...);
#define str__1(arena, x) \
_Generic((x), \
const char *: strInit, \
char *: strInit, \
strview_t: strInitView \
)(arena, x)
str_t strFromWCHAR(const wchar_t *src, usize len);
wchar_t *strToWCHAR(str_t ctx);
#define str__2(arena, cstr, clen) strInitLen(arena, cstr, clen)
#define str__impl(_1, _2, n, ...) str__##n
void strFree(str_t ctx);
str_t strDup(str_t ctx);
str_t strMove(str_t *ctx);
// either:
// arena_t arena, [const] char *cstr, [usize len]
// arena_t arena, strview_t view
#define str(arena, ...) str__impl(__VA_ARGS__, 2, 1, 0)(arena, __VA_ARGS__)
strview_t strGetView(str_t ctx);
str_t strInit(arena_t *arena, const char *buf);
str_t strInitLen(arena_t *arena, const char *buf, usize len);
str_t strInitView(arena_t *arena, strview_t view);
str_t strFmt(arena_t *arena, const char *fmt, ...);
str_t strFmtv(arena_t *arena, const char *fmt, va_list args);
char *strBegin(str_t ctx);
char *strEnd(str_t ctx);
char strBack(str_t ctx);
str_t strFromWChar(arena_t *arena, const wchar_t *src, usize srclen);
bool strEquals(str_t a, str_t b);
int strCompare(str_t a, str_t b);
str_t strDup(arena_t *arena, str_t src);
bool strIsEmpty(str_t ctx);
void strReplace(str_t *ctx, char from, char to);
void strReplace(str_t *ctx, char from, char to);
// if len == SIZE_MAX, copies until end
str_t strSubstr(str_t ctx, usize from, usize to);
// if len == SIZE_MAX, returns until end
strview_t strSubview(str_t ctx, usize from, usize to);
strview_t strSub(str_t ctx, usize from, usize to);
void strLower(str_t *ctx);
str_t strToLower(str_t ctx);
void strUpper(str_t *ctx);
str_t strToUpper(str_t ctx);
#ifdef STR_TESTING
void strTest(void);
#endif
str_t strToLower(arena_t *arena, str_t ctx);
str_t strToUpper(arena_t *arena, str_t ctx);
// == STRVIEW_T ====================================================
strview_t strvInit(const char *cstr);
strview_t strvInitStr(str_t str);
strview_t strvInitLen(const char *buf, usize size);
#define strv__1(x) \
_Generic((x), \
char *: strvInit, \
const char *: strvInit, \
str_t: strvInitStr \
)(x)
char strvFront(strview_t ctx);
char strvBack(strview_t ctx);
const char *strvBegin(strview_t ctx);
const char *strvEnd(strview_t ctx);
// move view forward by n characters
strview_t strvRemovePrefix(strview_t ctx, usize n);
// move view backwards by n characters
strview_t strvRemoveSuffix(strview_t ctx, usize n);
// removes whitespace from the beginning and the end
strview_t strvTrim(strview_t ctx);
// removes whitespace from the beginning
strview_t strvTrimLeft(strview_t ctx);
// removes whitespace from the end
strview_t strvTrimRight(strview_t ctx);
#define strv__2(cstr, clen) strvInitLen(cstr, clen)
#define strv__impl(_1, _2, n, ...) strv__##n
#define strv(...) strv__impl(__VA_ARGS__, 2, 1, 0)(__VA_ARGS__)
strview_t strvInit(const char *cstr);
strview_t strvInitLen(const char *buf, usize size);
strview_t strvInitStr(str_t str);
bool strvIsEmpty(strview_t ctx);
bool strvEquals(strview_t a, strview_t b);
int strvCompare(strview_t a, strview_t b);
str_t strvCopy(strview_t ctx);
str_t strvCopyN(strview_t ctx, usize count, usize from);
usize strvCopyBuf(strview_t ctx, char *buf, usize len, usize from);
wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen);
TCHAR *strvToTChar(arena_t *arena, strview_t str);
strview_t strvRemovePrefix(strview_t ctx, usize n);
strview_t strvRemoveSuffix(strview_t ctx, usize n);
strview_t strvTrim(strview_t ctx);
strview_t strvTrimLeft(strview_t ctx);
strview_t strvTrimRight(strview_t ctx);
strview_t strvSub(strview_t ctx, usize from, usize to);
int strvCompare(strview_t ctx, strview_t other);
int strvICompare(strview_t ctx, strview_t other);
bool strvStartsWith(strview_t ctx, char c);
bool strvStartsWithView(strview_t ctx, strview_t view);
@ -105,18 +105,5 @@ bool strvContainsView(strview_t ctx, strview_t view);
usize strvFind(strview_t ctx, char c, usize from);
usize strvFindView(strview_t ctx, strview_t view, usize from);
usize strvRFind(strview_t ctx, char c, usize from);
usize strvRFindView(strview_t ctx, strview_t view, usize from);
// Finds the first occurrence of any of the characters of 'view' in this view
usize strvFindFirstOf(strview_t ctx, strview_t view, usize from);
usize strvFindLastOf(strview_t ctx, strview_t view, usize from);
usize strvFindFirstNot(strview_t ctx, char c, usize from);
usize strvFindFirstNotOf(strview_t ctx, strview_t view, usize from);
usize strvFindLastNot(strview_t ctx, char c, usize from);
usize strvFindLastNotOf(strview_t ctx, strview_t view, usize from);
#ifdef __cplusplus
} // extern "C"
#endif
usize strvRFind(strview_t ctx, char c, usize from_right);
usize strvRFindView(strview_t ctx, strview_t view, usize from_right);

View file

@ -1,5 +1,7 @@
#include "strstream.h"
#include "warnings/colla_warn_beg.h"
#include <string.h>
#include <stdlib.h>
#include <limits.h>
@ -7,9 +9,11 @@
#include <stdio.h>
#include <ctype.h>
#include <math.h> // HUGE_VALF
#include "tracelog.h"
#if defined(_WIN32) && defined(__TINYC__)
#include "tracelog.h"
#include "arena.h"
#if COLLA_WIN && COLLA_TCC
#define strtoull _strtoui64
#define strtoll _strtoi64
#define strtof strtod
@ -17,39 +21,48 @@
/* == INPUT STREAM ============================================ */
str_istream_t istrInit(const char *str) {
instream_t istrInit(const char *str) {
return istrInitLen(str, strlen(str));
}
str_istream_t istrInitLen(const char *str, usize len) {
str_istream_t res;
instream_t istrInitLen(const char *str, usize len) {
instream_t res;
res.start = res.cur = str;
res.size = len;
return res;
}
char istrGet(str_istream_t *ctx) {
char istrGet(instream_t *ctx) {
return *ctx->cur++;
}
void istrIgnore(str_istream_t *ctx, char delim) {
usize position = ctx->cur - ctx->start;
usize i;
for(i = position;
i < ctx->size && *ctx->cur != delim;
++i, ++ctx->cur);
void istrIgnore(instream_t *ctx, char delim) {
for (; !istrIsFinished(*ctx) && *ctx->cur != delim; ++ctx->cur) {
}
//usize position = ctx->cur - ctx->start;
//usize i;
//for(i = position;
// i < ctx->size && *ctx->cur != delim;
// ++i, ++ctx->cur);
}
void istrIgnoreAndSkip(str_istream_t *ctx, char delim) {
void istrIgnoreAndSkip(instream_t *ctx, char delim) {
istrIgnore(ctx, delim);
istrSkip(ctx, 1);
}
char istrPeek(str_istream_t *ctx) {
char istrPeek(instream_t *ctx) {
return *ctx->cur;
}
void istrSkip(str_istream_t *ctx, usize n) {
char istrPeekNext(instream_t *ctx) {
usize offset = (ctx->cur - ctx->start) + 1;
return offset > ctx->size ? '\0' : *(ctx->cur + 1);
}
void istrSkip(instream_t *ctx, usize n) {
usize remaining = ctx->size - (ctx->cur - ctx->start);
if(n > remaining) {
warn("skipping more then remaining: %zu -> %zu", n, remaining);
@ -58,13 +71,13 @@ void istrSkip(str_istream_t *ctx, usize n) {
ctx->cur += n;
}
void istrSkipWhitespace(str_istream_t *ctx) {
void istrSkipWhitespace(instream_t *ctx) {
while (*ctx->cur && isspace(*ctx->cur)) {
++ctx->cur;
}
}
void istrRead(str_istream_t *ctx, char *buf, usize len) {
void istrRead(instream_t *ctx, char *buf, usize len) {
usize remaining = ctx->size - (ctx->cur - ctx->start);
if(len > remaining) {
warn("istrRead: trying to read len %zu from remaining %zu", len, remaining);
@ -74,7 +87,7 @@ void istrRead(str_istream_t *ctx, char *buf, usize len) {
ctx->cur += len;
}
usize istrReadMax(str_istream_t *ctx, char *buf, usize len) {
usize istrReadMax(instream_t *ctx, char *buf, usize len) {
usize remaining = ctx->size - (ctx->cur - ctx->start);
len = remaining < len ? remaining : len;
memcpy(buf, ctx->cur, len);
@ -82,29 +95,29 @@ usize istrReadMax(str_istream_t *ctx, char *buf, usize len) {
return len;
}
void istrRewind(str_istream_t *ctx) {
void istrRewind(instream_t *ctx) {
ctx->cur = ctx->start;
}
void istrRewindN(str_istream_t *ctx, usize amount) {
void istrRewindN(instream_t *ctx, usize amount) {
usize remaining = ctx->size - (ctx->cur - ctx->start);
if (amount > remaining) amount = remaining;
ctx->cur -= amount;
}
usize istrTell(str_istream_t ctx) {
usize istrTell(instream_t ctx) {
return ctx.cur - ctx.start;
}
usize istrRemaining(str_istream_t ctx) {
usize istrRemaining(instream_t ctx) {
return ctx.size - (ctx.cur - ctx.start);
}
bool istrIsFinished(str_istream_t ctx) {
bool istrIsFinished(instream_t ctx) {
return (usize)(ctx.cur - ctx.start) >= ctx.size;
}
bool istrGetbool(str_istream_t *ctx, bool *val) {
bool istrGetBool(instream_t *ctx, bool *val) {
usize remaining = ctx->size - (ctx->cur - ctx->start);
if(strncmp(ctx->cur, "true", remaining) == 0) {
*val = true;
@ -117,16 +130,16 @@ bool istrGetbool(str_istream_t *ctx, bool *val) {
return false;
}
bool istrGetu8(str_istream_t *ctx, uint8 *val) {
bool istrGetU8(instream_t *ctx, uint8 *val) {
char *end = NULL;
*val = (uint8) strtoul(ctx->cur, &end, 0);
if(ctx->cur == end) {
warn("istrGetu8: no valid conversion could be performed");
warn("istrGetU8: no valid conversion could be performed");
return false;
}
else if(*val == UINT8_MAX) {
warn("istrGetu8: value read is out of the range of representable values");
warn("istrGetU8: value read is out of the range of representable values");
return false;
}
@ -134,16 +147,16 @@ bool istrGetu8(str_istream_t *ctx, uint8 *val) {
return true;
}
bool istrGetu16(str_istream_t *ctx, uint16 *val) {
bool istrGetU16(instream_t *ctx, uint16 *val) {
char *end = NULL;
*val = (uint16) strtoul(ctx->cur, &end, 0);
if(ctx->cur == end) {
warn("istrGetu16: no valid conversion could be performed");
warn("istrGetU16: no valid conversion could be performed");
return false;
}
else if(*val == UINT16_MAX) {
warn("istrGetu16: value read is out of the range of representable values");
warn("istrGetU16: value read is out of the range of representable values");
return false;
}
@ -151,16 +164,16 @@ bool istrGetu16(str_istream_t *ctx, uint16 *val) {
return true;
}
bool istrGetu32(str_istream_t *ctx, uint32 *val) {
bool istrGetU32(instream_t *ctx, uint32 *val) {
char *end = NULL;
*val = (uint32) strtoul(ctx->cur, &end, 0);
if(ctx->cur == end) {
warn("istrGetu32: no valid conversion could be performed");
warn("istrGetU32: no valid conversion could be performed");
return false;
}
else if(*val == UINT32_MAX) {
warn("istrGetu32: value read is out of the range of representable values");
warn("istrGetU32: value read is out of the range of representable values");
return false;
}
@ -168,16 +181,16 @@ bool istrGetu32(str_istream_t *ctx, uint32 *val) {
return true;
}
bool istrGetu64(str_istream_t *ctx, uint64 *val) {
bool istrGetU64(instream_t *ctx, uint64 *val) {
char *end = NULL;
*val = strtoull(ctx->cur, &end, 0);
if(ctx->cur == end) {
warn("istrGetu64: no valid conversion could be performed");
warn("istrGetU64: no valid conversion could be performed");
return false;
}
else if(*val == ULLONG_MAX) {
warn("istrGetu64: value read is out of the range of representable values");
warn("istrGetU64: value read is out of the range of representable values");
return false;
}
@ -185,16 +198,16 @@ bool istrGetu64(str_istream_t *ctx, uint64 *val) {
return true;
}
bool istrGeti8(str_istream_t *ctx, int8 *val) {
bool istrGetI8(instream_t *ctx, int8 *val) {
char *end = NULL;
*val = (int8) strtol(ctx->cur, &end, 0);
if(ctx->cur == end) {
warn("istrGeti8: no valid conversion could be performed");
warn("istrGetI8: no valid conversion could be performed");
return false;
}
else if(*val == INT8_MAX || *val == INT8_MIN) {
warn("istrGeti8: value read is out of the range of representable values");
warn("istrGetI8: value read is out of the range of representable values");
return false;
}
@ -202,16 +215,16 @@ bool istrGeti8(str_istream_t *ctx, int8 *val) {
return true;
}
bool istrGeti16(str_istream_t *ctx, int16 *val) {
bool istrGetI16(instream_t *ctx, int16 *val) {
char *end = NULL;
*val = (int16) strtol(ctx->cur, &end, 0);
if(ctx->cur == end) {
warn("istrGeti16: no valid conversion could be performed");
warn("istrGetI16: no valid conversion could be performed");
return false;
}
else if(*val == INT16_MAX || *val == INT16_MIN) {
warn("istrGeti16: value read is out of the range of representable values");
warn("istrGetI16: value read is out of the range of representable values");
return false;
}
@ -219,16 +232,16 @@ bool istrGeti16(str_istream_t *ctx, int16 *val) {
return true;
}
bool istrGeti32(str_istream_t *ctx, int32 *val) {
bool istrGetI32(instream_t *ctx, int32 *val) {
char *end = NULL;
*val = (int32) strtol(ctx->cur, &end, 0);
if(ctx->cur == end) {
warn("istrGeti32: no valid conversion could be performed");
warn("istrGetI32: no valid conversion could be performed");
return false;
}
else if(*val == INT32_MAX || *val == INT32_MIN) {
warn("istrGeti32: value read is out of the range of representable values");
warn("istrGetI32: value read is out of the range of representable values");
return false;
}
@ -236,16 +249,16 @@ bool istrGeti32(str_istream_t *ctx, int32 *val) {
return true;
}
bool istrGeti64(str_istream_t *ctx, int64 *val) {
bool istrGetI64(instream_t *ctx, int64 *val) {
char *end = NULL;
*val = strtoll(ctx->cur, &end, 0);
if(ctx->cur == end) {
warn("istrGeti64: no valid conversion could be performed");
warn("istrGetI64: no valid conversion could be performed");
return false;
}
else if(*val == INT64_MAX || *val == INT64_MIN) {
warn("istrGeti64: value read is out of the range of representable values");
warn("istrGetI64: value read is out of the range of representable values");
return false;
}
@ -253,16 +266,16 @@ bool istrGeti64(str_istream_t *ctx, int64 *val) {
return true;
}
bool istrGetfloat(str_istream_t *ctx, float *val) {
bool istrGetFloat(instream_t *ctx, float *val) {
char *end = NULL;
*val = strtof(ctx->cur, &end);
if(ctx->cur == end) {
warn("istrGetfloat: no valid conversion could be performed");
warn("istrGetFloat: no valid conversion could be performed");
return false;
}
else if(*val == HUGE_VALF || *val == -HUGE_VALF) {
warn("istrGetfloat: value read is out of the range of representable values");
warn("istrGetFloat: value read is out of the range of representable values");
return false;
}
@ -270,16 +283,16 @@ bool istrGetfloat(str_istream_t *ctx, float *val) {
return true;
}
bool istrGetdouble(str_istream_t *ctx, double *val) {
bool istrGetDouble(instream_t *ctx, double *val) {
char *end = NULL;
*val = strtod(ctx->cur, &end);
if(ctx->cur == end) {
warn("istrGetdouble: no valid conversion could be performed");
warn("istrGetDouble: no valid conversion could be performed (%.5s)", ctx->cur);
return false;
}
else if(*val == HUGE_VAL || *val == -HUGE_VAL) {
warn("istrGetdouble: value read is out of the range of representable values");
warn("istrGetDouble: value read is out of the range of representable values");
return false;
}
@ -287,286 +300,127 @@ bool istrGetdouble(str_istream_t *ctx, double *val) {
return true;
}
usize istrGetstring(str_istream_t *ctx, char **val, char delim) {
str_t istrGetStr(arena_t *arena, instream_t *ctx, char delim) {
const char *from = ctx->cur;
istrIgnore(ctx, delim);
// if it didn't actually find it, it just reached the end of the string
if(*ctx->cur != delim) {
*val = NULL;
return 0;
return (str_t){0};
}
usize len = ctx->cur - from;
*val = (char *)malloc(len + 1);
memcpy(*val, from, len);
(*val)[len] = '\0';
return len;
str_t out = {
.buf = alloc(arena, char, len + 1),
.len = len
};
memcpy(out.buf, from, len);
return out;
}
usize istrGetstringBuf(str_istream_t *ctx, char *val, usize len) {
usize istrGetBuf(instream_t *ctx, char *buf, usize buflen) {
usize remaining = ctx->size - (ctx->cur - ctx->start);
len -= 1;
len = remaining < len ? remaining : len;
memcpy(val, ctx->cur, len);
val[len] = '\0';
ctx->cur += len;
return len;
buflen -= 1;
buflen = remaining < buflen ? remaining : buflen;
memcpy(buf, ctx->cur, buflen);
buf[buflen] = '\0';
ctx->cur += buflen;
return buflen;
}
strview_t istrGetview(str_istream_t *ctx, char delim) {
strview_t istrGetView(instream_t *ctx, char delim) {
const char *from = ctx->cur;
istrIgnore(ctx, delim);
usize len = ctx->cur - from;
return strvInitLen(from, len);
}
strview_t istrGetviewLen(str_istream_t *ctx, usize from, usize to) {
usize len = ctx->size - (ctx->cur - ctx->start) - from;
if (to > len) to = len;
if (from > to) from = to;
return strvInitLen(ctx->cur + from, to - from);
strview_t istrGetViewLen(instream_t *ctx, usize len) {
const char *from = ctx->cur;
istrSkip(ctx, len);
usize buflen = ctx->cur - from;
return (strview_t){ from, buflen };
}
/* == OUTPUT STREAM =========================================== */
static void _ostrRealloc(str_ostream_t *ctx, usize needed) {
ctx->cap = (ctx->cap * 2) + needed;
ctx->buf = (char *)realloc(ctx->buf, ctx->cap);
void ostr__remove_null(outstream_t *o) {
if (ostrTell(o)) {
arenaPop(o->arena, 1);
}
}
str_ostream_t ostrInit() {
return ostrInitLen(1);
}
str_ostream_t ostrInitLen(usize initial_alloc) {
str_ostream_t stream;
stream.buf = (char *)calloc(initial_alloc, 1);
stream.len = 0;
stream.cap = initial_alloc;
return stream;
}
str_ostream_t ostrInitStr(const char *cstr, usize len) {
str_ostream_t stream;
stream.buf = (char *)malloc(len + 1);
memcpy(stream.buf, cstr, len);
stream.len = len;
stream.cap = len + 1;
return stream;
}
void ostrFree(str_ostream_t ctx) {
free(ctx.buf);
}
void ostrClear(str_ostream_t *ctx) {
ctx->len = 0;
}
char ostrBack(str_ostream_t ctx) {
if(ctx.len == 0) return '\0';
return ctx.buf[ctx.len - 1];
}
str_t ostrAsStr(str_ostream_t ctx) {
return (str_t) {
.buf = ctx.buf,
.len = ctx.len
outstream_t ostrInit(arena_t *arena) {
return (outstream_t){
.beg = (char *)(arena ? arena->current : NULL),
.arena = arena,
};
}
strview_t ostrAsView(str_ostream_t ctx) {
return (strview_t) {
.buf = ctx.buf,
.len = ctx.len
void ostrClear(outstream_t *ctx) {
arenaPop(ctx->arena, ostrTell(ctx));
}
usize ostrTell(outstream_t *ctx) {
return ctx->arena ? (char *)ctx->arena->current - ctx->beg : 0;
}
char ostrBack(outstream_t *ctx) {
return arenaTell(ctx->arena) ? *ctx->arena->current : '\0';
}
str_t ostrAsStr(outstream_t *ctx) {
return (str_t){
.buf = ctx->beg,
.len = ostrTell(ctx)
};
}
void ostrReplace(str_ostream_t *ctx, char from, char to) {
for(usize i = 0; i < ctx->len; ++i) {
if(ctx->buf[i] == from) {
ctx->buf[i] = to;
}
}
strview_t ostrAsView(outstream_t *ctx) {
return (strview_t){
.buf = ctx->beg,
.len = ostrTell(ctx)
};
}
void ostrPrintf(str_ostream_t *ctx, const char *fmt, ...) {
va_list va;
va_start(va, fmt);
ostrPrintfV(ctx, fmt, va);
va_end(va);
void ostrPrintf(outstream_t *ctx, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
ostrPrintfV(ctx, fmt, args);
va_end(args);
}
void ostrPrintfV(str_ostream_t *ctx, const char *fmt, va_list args) {
va_list vtemp;
int len;
usize remaining;
// vsnprintf returns the length of the formatted string, even if truncated
// we use this to get the actual length of the formatted string
va_copy(vtemp, args);
len = vsnprintf(NULL, 0, fmt, vtemp);
va_end(vtemp);
if(len < 0) {
err("couldn't format string \"%s\"", fmt);
goto error;
}
remaining = ctx->cap - ctx->len;
if(remaining < (usize)len) {
_ostrRealloc(ctx, len + 1);
remaining = ctx->cap - ctx->len;
}
// actual formatting here
va_copy(vtemp, args);
len = vsnprintf(ctx->buf + ctx->len, remaining, fmt, vtemp);
va_end(vtemp);
if(len < 0) {
err("couldn't format stringh \"%s\"", fmt);
goto error;
}
ctx->len += len;
error:
return;
void ostrPrintfV(outstream_t *ctx, const char *fmt, va_list args) {
if (!ctx->arena) return;
ostr__remove_null(ctx);
strFmtv(ctx->arena, fmt, args);
}
#define APPEND_BUF_LEN 20
void ostrPutc(str_ostream_t *ctx, char c) {
ostrAppendchar(ctx, c);
void ostrPutc(outstream_t *ctx, char c) {
if (!ctx->arena) return;
ostr__remove_null(ctx);
char *newc = alloc(ctx->arena, char);
*newc = c;
}
void ostrPuts(str_ostream_t *ctx, const char *str) {
ostrAppendview(ctx, strvInit(str));
void ostrPuts(outstream_t *ctx, strview_t v) {
if (strvIsEmpty(v)) return;
ostr__remove_null(ctx);
str(ctx->arena, v);
}
void ostrAppendbool(str_ostream_t *ctx, bool val) {
ostrAppendview(ctx, strvInit(val ? "true" : "false"));
void ostrAppendBool(outstream_t *ctx, bool val) {
ostrPuts(ctx, val ? strv("true") : strv("false"));
}
void ostrAppendchar(str_ostream_t *ctx, char val) {
if(ctx->len >= ctx->cap) {
_ostrRealloc(ctx, 1);
}
ctx->buf[ctx->len++] = val;
ctx->buf[ctx->len] = '\0';
void ostrAppendUInt(outstream_t *ctx, uint64 val) {
ostrPrintf(ctx, "%I64u", val);
}
void ostrAppendu8(str_ostream_t *ctx, uint8 val) {
char buf[APPEND_BUF_LEN];
int len = snprintf(buf, sizeof(buf), "%hhu", val);
if(len <= 0) {
err("ostrAppendu8: couldn't write %hhu", val);
return;
}
ostrAppendview(ctx, strvInitLen(buf, len));
void ostrAppendInt(outstream_t *ctx, int64 val) {
ostrPrintf(ctx, "%I64d", val);
}
void ostrAppendu16(str_ostream_t *ctx, uint16 val) {
char buf[APPEND_BUF_LEN];
int len = snprintf(buf, sizeof(buf), "%hu", val);
if(len <= 0) {
err("ostrAppendu16: couldn't write %hu", val);
return;
}
ostrAppendview(ctx, strvInitLen(buf, len));
void ostrAppendNum(outstream_t *ctx, double val) {
ostrPrintf(ctx, "%g", val);
}
void ostrAppendu32(str_ostream_t *ctx, uint32 val) {
char buf[APPEND_BUF_LEN];
int len = snprintf(buf, sizeof(buf), "%u", val);
if(len <= 0) {
err("ostrAppendu32: couldn't write %u", val);
return;
}
ostrAppendview(ctx, strvInitLen(buf, len));
}
void ostrAppendu64(str_ostream_t *ctx, uint64 val) {
char buf[APPEND_BUF_LEN];
#if _WIN32
int len = snprintf(buf, sizeof(buf), "%llu", val);
#else
int len = snprintf(buf, sizeof(buf), "%lu", val);
#endif
if(len <= 0) {
err("ostrAppendu64: couldn't write %lu", val);
return;
}
ostrAppendview(ctx, strvInitLen(buf, len));
}
void ostrAppendi8(str_ostream_t *ctx, int8 val) {
char buf[APPEND_BUF_LEN];
int len = snprintf(buf, sizeof(buf), "%hhi", val);
if(len <= 0) {
err("ostrAppendi8: couldn't write %hhi", val);
return;
}
ostrAppendview(ctx, strvInitLen(buf, len));
}
void ostrAppendi16(str_ostream_t *ctx, int16 val) {
char buf[APPEND_BUF_LEN];
int len = snprintf(buf, sizeof(buf), "%hi", val);
if(len <= 0) {
err("ostrAppendi16: couldn't write %hi", val);
return;
}
ostrAppendview(ctx, strvInitLen(buf, len));
}
void ostrAppendi32(str_ostream_t *ctx, int32 val) {
char buf[APPEND_BUF_LEN];
int len = snprintf(buf, sizeof(buf), "%i", val);
if(len <= 0) {
err("ostrAppendi32: couldn't write %i", val);
return;
}
ostrAppendview(ctx, strvInitLen(buf, len));
}
void ostrAppendi64(str_ostream_t *ctx, int64 val) {
char buf[APPEND_BUF_LEN];
#if _WIN32
int len = snprintf(buf, sizeof(buf), "%lli", val);
#else
int len = snprintf(buf, sizeof(buf), "%li", val);
#endif
if(len <= 0) {
err("ostrAppendi64: couldn't write %li", val);
return;
}
ostrAppendview(ctx, strvInitLen(buf, len));
}
void ostrAppendfloat(str_ostream_t *ctx, float val) {
char buf[APPEND_BUF_LEN * 3];
int len = snprintf(buf, sizeof(buf), "%g", (double)val);
if(len <= 0) {
err("ostrAppendfloat: couldn't write %g", (double)val);
return;
}
ostrAppendview(ctx, strvInitLen(buf, len));
}
void ostrAppenddouble(str_ostream_t *ctx, double val) {
char buf[APPEND_BUF_LEN * 3];
int len = snprintf(buf, sizeof(buf), "%g", val);
if(len <= 0) {
err("ostrAppenddouble: couldn't write %g", val);
return;
}
ostrAppendview(ctx, strvInitLen(buf, len));
}
void ostrAppendview(str_ostream_t *ctx, strview_t view) {
if((ctx->cap - ctx->len) <= view.len) {
_ostrRealloc(ctx, view.len + 1);
}
memcpy(ctx->buf + ctx->len, view.buf, view.len);
ctx->len += view.len;
ctx->buf[ctx->len] = '\0';
}
#include "warnings/colla_warn_end.h"

View file

@ -4,110 +4,97 @@
extern "C" {
#endif
#include <stdbool.h>
#include <stddef.h>
#include <stdarg.h>
#include "collatypes.h"
#include "str.h"
typedef struct arena_t arena_t;
/* == INPUT STREAM ============================================ */
typedef struct {
const char *start;
const char *cur;
usize size;
} str_istream_t;
} instream_t;
// initialize with null-terminated string
str_istream_t istrInit(const char *str);
str_istream_t istrInitLen(const char *str, usize len);
instream_t istrInit(const char *str);
instream_t istrInitLen(const char *str, usize len);
// get the current character and advance
char istrGet(str_istream_t *ctx);
char istrGet(instream_t *ctx);
// get the current character but don't advance
char istrPeek(str_istream_t *ctx);
char istrPeek(instream_t *ctx);
// get the next character but don't advance
char istrPeekNext(instream_t *ctx);
// ignore characters until the delimiter
void istrIgnore(str_istream_t *ctx, char delim);
void istrIgnore(instream_t *ctx, char delim);
// ignore characters until the delimiter and skip it
void istrIgnoreAndSkip(str_istream_t *ctx, char delim);
void istrIgnoreAndSkip(instream_t *ctx, char delim);
// skip n characters
void istrSkip(str_istream_t *ctx, usize n);
void istrSkip(instream_t *ctx, usize n);
// skips whitespace (' ', '\n', '\t', '\r')
void istrSkipWhitespace(str_istream_t *ctx);
void istrSkipWhitespace(instream_t *ctx);
// read len bytes into buffer, the buffer will not be null terminated
void istrRead(str_istream_t *ctx, char *buf, usize len);
void istrRead(instream_t *ctx, char *buf, usize len);
// read a maximum of len bytes into buffer, the buffer will not be null terminated
// returns the number of bytes read
usize istrReadMax(str_istream_t *ctx, char *buf, usize len);
usize istrReadMax(instream_t *ctx, char *buf, usize len);
// returns to the beginning of the stream
void istrRewind(str_istream_t *ctx);
void istrRewind(instream_t *ctx);
// returns back <amount> characters
void istrRewindN(str_istream_t *ctx, usize amount);
void istrRewindN(instream_t *ctx, usize amount);
// returns the number of bytes read from beginning of stream
usize istrTell(str_istream_t ctx);
usize istrTell(instream_t ctx);
// returns the number of bytes left to read in the stream
usize istrRemaining(str_istream_t ctx);
usize istrRemaining(instream_t ctx);
// return true if the stream doesn't have any new bytes to read
bool istrIsFinished(str_istream_t ctx);
bool istrIsFinished(instream_t ctx);
bool istrGetbool(str_istream_t *ctx, bool *val);
bool istrGetu8(str_istream_t *ctx, uint8 *val);
bool istrGetu16(str_istream_t *ctx, uint16 *val);
bool istrGetu32(str_istream_t *ctx, uint32 *val);
bool istrGetu64(str_istream_t *ctx, uint64 *val);
bool istrGeti8(str_istream_t *ctx, int8 *val);
bool istrGeti16(str_istream_t *ctx, int16 *val);
bool istrGeti32(str_istream_t *ctx, int32 *val);
bool istrGeti64(str_istream_t *ctx, int64 *val);
bool istrGetfloat(str_istream_t *ctx, float *val);
bool istrGetdouble(str_istream_t *ctx, double *val);
// get a string until a delimiter, the string is allocated by the function and should be freed
usize istrGetstring(str_istream_t *ctx, char **val, char delim);
bool istrGetBool(instream_t *ctx, bool *val);
bool istrGetU8(instream_t *ctx, uint8 *val);
bool istrGetU16(instream_t *ctx, uint16 *val);
bool istrGetU32(instream_t *ctx, uint32 *val);
bool istrGetU64(instream_t *ctx, uint64 *val);
bool istrGetI8(instream_t *ctx, int8 *val);
bool istrGetI16(instream_t *ctx, int16 *val);
bool istrGetI32(instream_t *ctx, int32 *val);
bool istrGetI64(instream_t *ctx, int64 *val);
bool istrGetFloat(instream_t *ctx, float *val);
bool istrGetDouble(instream_t *ctx, double *val);
str_t istrGetStr(arena_t *arena, instream_t *ctx, char delim);
// get a string of maximum size len, the string is not allocated by the function and will be null terminated
usize istrGetstringBuf(str_istream_t *ctx, char *val, usize len);
strview_t istrGetview(str_istream_t *ctx, char delim);
strview_t istrGetviewLen(str_istream_t *ctx, usize from, usize to);
usize istrGetBuf(instream_t *ctx, char *buf, usize buflen);
strview_t istrGetView(instream_t *ctx, char delim);
strview_t istrGetViewLen(instream_t *ctx, usize len);
/* == OUTPUT STREAM =========================================== */
typedef struct {
char *buf;
usize len;
usize cap;
} str_ostream_t;
char *beg;
arena_t *arena;
} outstream_t;
str_ostream_t ostrInit(void);
str_ostream_t ostrInitLen(usize initial_alloc);
str_ostream_t ostrInitStr(const char *buf, usize len);
outstream_t ostrInit(arena_t *exclusive_arena);
void ostrClear(outstream_t *ctx);
void ostrFree(str_ostream_t ctx);
void ostrClear(str_ostream_t *ctx);
usize ostrTell(outstream_t *ctx);
char ostrBack(str_ostream_t ctx);
str_t ostrAsStr(str_ostream_t ctx);
strview_t ostrAsView(str_ostream_t ctx);
char ostrBack(outstream_t *ctx);
str_t ostrAsStr(outstream_t *ctx);
strview_t ostrAsView(outstream_t *ctx);
void ostrReplace(str_ostream_t *ctx, char from, char to);
void ostrPrintf(outstream_t *ctx, const char *fmt, ...);
void ostrPrintfV(outstream_t *ctx, const char *fmt, va_list args);
void ostrPutc(outstream_t *ctx, char c);
void ostrPuts(outstream_t *ctx, strview_t v);
void ostrPrintf(str_ostream_t *ctx, const char *fmt, ...);
void ostrPrintfV(str_ostream_t *ctx, const char *fmt, va_list args);
void ostrPutc(str_ostream_t *ctx, char c);
void ostrPuts(str_ostream_t *ctx, const char *str);
void ostrAppendbool(str_ostream_t *ctx, bool val);
void ostrAppendchar(str_ostream_t *ctx, char val);
void ostrAppendu8(str_ostream_t *ctx, uint8 val);
void ostrAppendu16(str_ostream_t *ctx, uint16 val);
void ostrAppendu32(str_ostream_t *ctx, uint32 val);
void ostrAppendu64(str_ostream_t *ctx, uint64 val);
void ostrAppendi8(str_ostream_t *ctx, int8 val);
void ostrAppendi16(str_ostream_t *ctx, int16 val);
void ostrAppendi32(str_ostream_t *ctx, int32 val);
void ostrAppendi64(str_ostream_t *ctx, int64 val);
void ostrAppendfloat(str_ostream_t *ctx, float val);
void ostrAppenddouble(str_ostream_t *ctx, double val);
void ostrAppendview(str_ostream_t *ctx, strview_t view);
void ostrAppendBool(outstream_t *ctx, bool val);
void ostrAppendUInt(outstream_t *ctx, uint64 val);
void ostrAppendInt(outstream_t *ctx, int64 val);
void ostrAppendNum(outstream_t *ctx, double val);
#ifdef __cplusplus
} // extern "C"

View file

@ -4,9 +4,32 @@
#include <string.h>
#include <stdlib.h>
#ifdef _WIN32
#pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS.
#include "win32_slim.h"
#include "format.h"
#if COLLA_WIN
#if COLLA_MSVC
#pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS.
#endif
#include <handleapi.h>
// avoid including windows.h
#ifndef STD_OUTPUT_HANDLE
#define STD_OUTPUT_HANDLE ((DWORD)-11)
#endif
#ifndef CP_UTF8
#define CP_UTF8 65001
#endif
typedef unsigned short WORD;
typedef unsigned long DWORD;
typedef unsigned int UINT;
typedef int BOOL;
WINBASEAPI HANDLE WINAPI GetStdHandle(DWORD nStdHandle);
WINBASEAPI BOOL WINAPI SetConsoleTextAttribute(HANDLE hConsoleOutput, WORD wAttributes);
WINBASEAPI BOOL WINAPI SetConsoleOutputCP(UINT wCodePageID);
#ifndef TLOG_VS
#define TLOG_WIN32_NO_VS
#ifndef TLOG_NO_COLOURS
@ -16,33 +39,33 @@
#endif
#ifdef TLOG_VS
#ifndef _WIN32
#if COLLA_WIN
#error "can't use TLOG_VS if not on windows"
#endif
#endif
#ifdef TLOG_NO_COLOURS
#define BLACK ""
#define RED ""
#define GREEN ""
#define YELLOW ""
#define BLUE ""
#define MAGENTA ""
#define CYAN ""
#define WHITE ""
#define RESET ""
#define BOLD ""
#define COLOUR_BLACK ""
#define COLOUR_RED ""
#define COLOUR_GREEN ""
#define COLOUR_YELLOW ""
#define COLOUR_BLUE ""
#define COLOUR_MAGENTA ""
#define COLOUR_CYAN ""
#define COLOUR_WHITE ""
#define COLOUR_RESET ""
#define COLOUR_BOLD ""
#else
#define BLACK "\033[30m"
#define RED "\033[31m"
#define GREEN "\033[32m"
#define YELLOW "\033[33m"
#define BLUE "\033[22;34m"
#define MAGENTA "\033[35m"
#define CYAN "\033[36m"
#define WHITE "\033[37m"
#define RESET "\033[0m"
#define BOLD "\033[1m"
#define COLOUR_BLACK "\033[30m"
#define COLOUR_RED "\033[31m"
#define COLOUR_GREEN "\033[32m"
#define COLOUR_YELLOW "\033[33m"
#define COLOUR_BLUE "\033[22;34m"
#define COLOUR_MAGENTA "\033[35m"
#define COLOUR_CYAN "\033[36m"
#define COLOUR_WHITE "\033[37m"
#define COLOUR_RESET "\033[0m"
#define COLOUR_BOLD "\033[1m"
#endif
#define MAX_TRACELOG_MSG_LENGTH 1024
@ -67,8 +90,7 @@ static void setLevelColour(int level) {
void traceLog(int level, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
traceLogVaList(level, fmt, args);
va_start(args, fmt); traceLogVaList(level, fmt, args);
va_end(args);
}
@ -88,12 +110,12 @@ void traceLogVaList(int level, const char *fmt, va_list args) {
const char *beg;
switch (level) {
case LogTrace: beg = BOLD WHITE "[TRACE]: " RESET; break;
case LogDebug: beg = BOLD BLUE "[DEBUG]: " RESET; break;
case LogInfo: beg = BOLD GREEN "[INFO]: " RESET; break;
case LogWarning: beg = BOLD YELLOW "[WARNING]: " RESET; break;
case LogError: beg = BOLD RED "[ERROR]: " RESET; break;
case LogFatal: beg = BOLD RED "[FATAL]: " RESET; break;
case LogTrace: beg = COLOUR_BOLD COLOUR_WHITE "[TRACE]: " COLOUR_RESET; break;
case LogDebug: beg = COLOUR_BOLD COLOUR_BLUE "[DEBUG]: " COLOUR_RESET; break;
case LogInfo: beg = COLOUR_BOLD COLOUR_GREEN "[INFO]: " COLOUR_RESET; break;
case LogWarning: beg = COLOUR_BOLD COLOUR_YELLOW "[WARNING]: " COLOUR_RESET; break;
case LogError: beg = COLOUR_BOLD COLOUR_RED "[ERROR]: " COLOUR_RESET; break;
case LogFatal: beg = COLOUR_BOLD COLOUR_RED "[FATAL]: " COLOUR_RESET; break;
default: beg = ""; break;
}
@ -104,7 +126,7 @@ void traceLogVaList(int level, const char *fmt, va_list args) {
strncpy(buffer, beg, sizeof(buffer));
#endif
vsnprintf(buffer + offset, sizeof(buffer) - offset, fmt, args);
fmtBufferv(buffer + offset, sizeof(buffer) - offset, fmt, args);
#if defined(TLOG_VS)
OutputDebugStringA(buffer);
@ -123,7 +145,10 @@ void traceLogVaList(int level, const char *fmt, va_list args) {
#endif
#ifndef TLOG_DONT_EXIT_ON_FATAL
if (level == LogFatal) exit(1);
if (level == LogFatal) {
abort();
exit(1);
}
#endif
#ifdef TLOG_MUTEX

View file

@ -12,7 +12,7 @@ extern "C" {
* -> TLOG_MUTEX: use a mutex on every traceLog call
*/
#include <stdbool.h>
#include "collatypes.h"
#include <stdarg.h>
enum {

133
colla/vmem.c Normal file
View file

@ -0,0 +1,133 @@
#include "vmem.h"
#include <assert.h>
#include "tracelog.h"
static usize vmem__page_size = 0;
static void vmem__update_page_size(void);
// platform generic functions
usize vmemGetPageSize(void) {
if (!vmem__page_size) {
vmem__update_page_size();
}
return vmem__page_size;
}
usize vmemPadToPage(usize byte_count) {
if (!vmem__page_size) {
vmem__update_page_size();
}
if (byte_count == 0) {
return vmem__page_size;
}
// bit twiddiling, vmem__page_size MUST be a power of 2
usize padding = vmem__page_size - (byte_count & (vmem__page_size - 1));
if (padding == vmem__page_size) {
padding = 0;
}
return byte_count + padding;
}
#if COLLA_WIN
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
void *vmemInit(usize size, usize *out_padded_size) {
usize alloc_size = vmemPadToPage(size);
void *ptr = VirtualAlloc(NULL, alloc_size, MEM_RESERVE, PAGE_NOACCESS);
if (out_padded_size) {
*out_padded_size = alloc_size;
}
return ptr;
}
bool vmemRelease(void *base_ptr) {
return VirtualFree(base_ptr, 0, MEM_RELEASE);
}
bool vmemCommit(void *ptr, usize num_of_pages) {
usize page_size = vmemGetPageSize();
void *new_ptr = VirtualAlloc(ptr, num_of_pages * page_size, MEM_COMMIT, PAGE_READWRITE);
if (!new_ptr) {
debug("ERROR: failed to commit memory: %lu\n", GetLastError());
}
return new_ptr != NULL;
}
static void vmem__update_page_size(void) {
SYSTEM_INFO info = {0};
GetSystemInfo(&info);
vmem__page_size = info.dwPageSize;
}
#elif COLLA_LIN
#include <sys/mman.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
struct vmem__header {
usize len;
};
void *vmemInit(usize size, usize *out_padded_size) {
size += sizeof(vmem__header);
usize alloc_size = padToPage(size);
vmem__header *header = mmap(NULL, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (header == MAP_FAILED) {
fatal("could not map %zu memory: %s", size, strerror(errno));
}
if (padded_size) {
*padded_size = alloc_size;
}
header->len = alloc_size;
return header + 1;
}
bool vmemRelease(void *base_ptr) {
if (!base_ptr) return false;
vmem__header *header = (vmem__header *)base_ptr - 1;
int res = munmap(header, header->len);
if (res == -1) {
err("munmap failed: %s", strerror(errno));
}
return res != -1;
}
bool vmemCommit(void *ptr, usize num_of_pages) {
// mmap doesn't need a commit step
(VOID)ptr;
(VOID)num_of_pages;
return true;
}
static void vmem__update_page_size(void) {
long lin_page_size = sysconf(_SC_PAGE_SIZE);
if (lin_page_size < 0) {
fatal("could not get page size: %s", strerror(errno));
}
page_size = (usize)lin_page_size;
}
#endif

12
colla/vmem.h Normal file
View file

@ -0,0 +1,12 @@
#ifndef VIRTUAL_MEMORY_HEADER
#define VIRTUAL_MEMORY_HEADER
#include "collatypes.h"
void *vmemInit(usize size, usize *out_padded_size);
bool vmemRelease(void *base_ptr);
bool vmemCommit(void *ptr, usize num_of_pages);
usize vmemGetPageSize(void);
usize vmemPadToPage(usize byte_count);
#endif // VIRTUAL_MEMORY_HEADER

View file

@ -0,0 +1,7 @@
#if COLLA_CLANG
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Winitializer-overrides"
#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
#endif

View file

@ -0,0 +1,5 @@
#if COLLA_CLANG
#pragma clang diagnostic pop
#endif

View file

@ -1,15 +0,0 @@
#pragma once
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef WIN32_EXTRA_LEAN
#define WIN32_EXTRA_LEAN
#endif
#include <windows.h>
#endif // _WIN32

View file

@ -1,8 +0,0 @@
include = colla/
files = colla/*.c
[windows]
link = ws2_32.lib
[linux]
link = pthread

View file

@ -4,8 +4,8 @@
extern "C" {
#endif
#include <stdbool.h> // bool
#include <string.h> // memset
#include "collatypes.h"
#include "tracelog.h" // fatal
// heavily inspired by https://gist.github.com/Enichan/5f01140530ff0133fde19c9549a2a973

View file

@ -1,204 +1,204 @@
#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
#include <stdio.h>
bool dirRemove(const char *path) {
dir_t dir = dirOpen(path);
if (!dirValid(dir)) return false;
dir_entry_t *it = NULL;
while((it = dirNext(dir))) {
if (it->type == FS_TYPE_FILE) {
str_t file_path = strFromFmt("%s/%s", path, it->name.buf);
if (remove(file_path.buf)) {
err("couldn't remove %s > %s", file_path.buf, strerror(errno));
}
strFree(file_path);
}
else if (it->type == FS_TYPE_DIR) {
if (strcmp(it->name.buf, ".") == 0 || strcmp(it->name.buf, "..") == 0) {
continue;
}
str_t new_path = strFromFmt("%s/%s", path, it->name.buf);
info("new path: %s--%s -> %s", path, it->name.buf, new_path.buf);
if (!dirRemove(new_path.buf)) {
err("couldn't delete folder %s", new_path.buf);
break;
}
strFree(new_path);
}
else {
err("%d -> %s", it->type, it->name.buf);
}
}
dirClose(dir);
#ifdef _WIN32
return RemoveDirectoryA(path);
#else
return rmdir(path) == 0;
#endif
}
#include "dir.h"
#include "tracelog.h"
#if COLLA_WIN
#include "win32_slim.h"
#include <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);
outstream_t out = ostrInitLen(n + 3);
n = GetFullPathName(path, n, out.buf, NULL);
assert(n > 0);
out.len += n;
switch(ostrBack(out)) {
case '\\':
case '/':
case ':':
// directory ends in path separator
// NOP
break;
default:
ostrPutc(&out, '\\');
}
ostrPutc(&out, '*');
_dir_internal_t *dir = malloc(sizeof(_dir_internal_t));
if(dir) {
wchar_t *wpath = strToWCHAR(ostrAsStr(out));
assert(wpath);
*dir = _getFirst(wpath);
free(wpath);
}
ostrFree(out);
return dir;
}
void dirClose(dir_t ctx) {
free(ctx);
}
bool dirValid(dir_t ctx) {
_dir_internal_t *dir = (_dir_internal_t*)ctx;
return dir->handle != INVALID_HANDLE_VALUE;
}
dir_entry_t *dirNext(dir_t ctx) {
_dir_internal_t *dir = (_dir_internal_t*)ctx;
strFree(dir->cur.name);
if(!dir->handle) return NULL;
dir->cur = dir->next;
_getNext(dir);
return &dir->cur;
}
void dirCreate(const char *path) {
CreateDirectoryA(path, NULL);
}
#else
#include <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
#include <stdio.h>
bool dirRemove(const char *path) {
dir_t dir = dirOpen(path);
if (!dirValid(dir)) return false;
dir_entry_t *it = NULL;
while((it = dirNext(dir))) {
if (it->type == FS_TYPE_FILE) {
str_t file_path = strFromFmt("%s/%s", path, it->name.buf);
if (remove(file_path.buf)) {
err("couldn't remove %s > %s", file_path.buf, strerror(errno));
}
strFree(file_path);
}
else if (it->type == FS_TYPE_DIR) {
if (strcmp(it->name.buf, ".") == 0 || strcmp(it->name.buf, "..") == 0) {
continue;
}
str_t new_path = strFromFmt("%s/%s", path, it->name.buf);
info("new path: %s--%s -> %s", path, it->name.buf, new_path.buf);
if (!dirRemove(new_path.buf)) {
err("couldn't delete folder %s", new_path.buf);
break;
}
strFree(new_path);
}
else {
err("%d -> %s", it->type, it->name.buf);
}
}
dirClose(dir);
#if COLLA_WIN
return RemoveDirectoryA(path);
#else
return rmdir(path) == 0;
#endif
}

View file

@ -1,34 +1,34 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "str.h"
typedef void *dir_t;
typedef enum {
FS_TYPE_UNKNOWN,
FS_TYPE_FILE,
FS_TYPE_DIR,
} fs_type_t;
typedef struct {
fs_type_t type;
str_t name;
} dir_entry_t;
dir_t dirOpen(const char *path);
void dirClose(dir_t ctx);
bool dirValid(dir_t ctx);
dir_entry_t *dirNext(dir_t ctx);
void dirCreate(const char *path);
bool dirRemove(const char *path);
#ifdef __cplusplus
} // extern "C"
#endif
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "str.h"
typedef void *dir_t;
typedef enum {
FS_TYPE_UNKNOWN,
FS_TYPE_FILE,
FS_TYPE_DIR,
} fs_type_t;
typedef struct {
fs_type_t type;
str_t name;
} dir_entry_t;
dir_t dirOpen(const char *path);
void dirClose(dir_t ctx);
bool dirValid(dir_t ctx);
dir_entry_t *dirNext(dir_t ctx);
void dirCreate(const char *path);
bool dirRemove(const char *path);
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -5,7 +5,7 @@
#include <assert.h>
#include "tracelog.h"
#ifdef _WIN32
#if COLLA_WIN
#include "win32_slim.h"
#include "str.h"

View file

@ -27,7 +27,7 @@ extern "C" {
* }
*/
#include <stdbool.h>
#include "collatypes.h"
#include "cthreads.h"
enum {

View file

@ -7,7 +7,7 @@
#include "tracelog.h"
#ifdef _WIN32
#if COLLA_WIN
#include "win32_slim.h"
#include <sys/stat.h>

View file

@ -4,8 +4,7 @@
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
#include "collatypes.h"
#include "file.h"

View file

@ -1,144 +1,144 @@
#include "jobpool.h"
#include <vec.h>
typedef struct {
cthread_func_t func;
void *arg;
} job_t;
typedef struct {
vec(job_t) jobs;
uint32 head;
cmutex_t work_mutex;
condvar_t work_cond;
condvar_t working_cond;
int32 working_count;
int32 thread_count;
bool stop;
} _pool_internal_t;
static job_t _getJob(_pool_internal_t *pool);
static int _poolWorker(void *arg);
jobpool_t poolInit(uint32 num) {
if (!num) num = 2;
_pool_internal_t *pool = malloc(sizeof(_pool_internal_t));
*pool = (_pool_internal_t){
.work_mutex = mtxInit(),
.work_cond = condInit(),
.working_cond = condInit(),
.thread_count = (int32)num
};
for (usize i = 0; i < num; ++i) {
thrDetach(thrCreate(_poolWorker, pool));
}
return pool;
}
void poolFree(jobpool_t pool_in) {
_pool_internal_t *pool = pool_in;
if (!pool) return;
mtxLock(pool->work_mutex);
pool->stop = true;
condWakeAll(pool->work_cond);
mtxUnlock(pool->work_mutex);
poolWait(pool);
vecFree(pool->jobs);
mtxFree(pool->work_mutex);
condFree(pool->work_cond);
condFree(pool->working_cond);
free(pool);
}
bool poolAdd(jobpool_t pool_in, cthread_func_t func, void *arg) {
_pool_internal_t *pool = pool_in;
if (!pool) return false;
mtxLock(pool->work_mutex);
if (pool->head > vecLen(pool->jobs)) {
vecClear(pool->jobs);
pool->head = 0;
}
job_t job = { func, arg };
vecAppend(pool->jobs, job);
condWake(pool->work_cond);
mtxUnlock(pool->work_mutex);
return true;
}
void poolWait(jobpool_t pool_in) {
_pool_internal_t *pool = pool_in;
if (!pool) return;
mtxLock(pool->work_mutex);
// while its either
// - working and there's still some threads doing some work
// - not working and there's still some threads exiting
while ((!pool->stop && pool->working_count > 0) ||
(pool->stop && pool->thread_count > 0)
) {
condWait(pool->working_cond, pool->work_mutex);
}
mtxUnlock(pool->work_mutex);
}
// == PRIVATE FUNCTIONS ===================================
static job_t _getJob(_pool_internal_t *pool) {
if (pool->head >= vecLen(pool->jobs)) {
pool->head = 0;
}
job_t job = pool->jobs[pool->head++];
return job;
}
static int _poolWorker(void *arg) {
_pool_internal_t *pool = arg;
while (true) {
mtxLock(pool->work_mutex);
// wait for a new job
while (pool->head >= vecLen(pool->jobs) && !pool->stop) {
condWait(pool->work_cond, pool->work_mutex);
}
if (pool->stop) {
break;
}
job_t job = _getJob(pool);
pool->working_count++;
mtxUnlock(pool->work_mutex);
if (job.func) {
job.func(job.arg);
}
mtxLock(pool->work_mutex);
pool->working_count--;
if (!pool->stop &&
pool->working_count == 0 &&
pool->head == vecLen(pool->jobs)
) {
condWake(pool->working_cond);
}
mtxUnlock(pool->work_mutex);
}
pool->thread_count--;
condWake(pool->working_cond);
mtxUnlock(pool->work_mutex);
return 0;
}
#include "jobpool.h"
#include "vec.h"
typedef struct {
cthread_func_t func;
void *arg;
} job_t;
typedef struct {
vec(job_t) jobs;
uint32 head;
cmutex_t work_mutex;
condvar_t work_cond;
condvar_t working_cond;
int32 working_count;
int32 thread_count;
bool stop;
} _pool_internal_t;
static job_t _getJob(_pool_internal_t *pool);
static int _poolWorker(void *arg);
jobpool_t poolInit(uint32 num) {
if (!num) num = 2;
_pool_internal_t *pool = malloc(sizeof(_pool_internal_t));
*pool = (_pool_internal_t){
.work_mutex = mtxInit(),
.work_cond = condInit(),
.working_cond = condInit(),
.thread_count = (int32)num
};
for (usize i = 0; i < num; ++i) {
thrDetach(thrCreate(_poolWorker, pool));
}
return pool;
}
void poolFree(jobpool_t pool_in) {
_pool_internal_t *pool = pool_in;
if (!pool) return;
mtxLock(pool->work_mutex);
pool->stop = true;
condWakeAll(pool->work_cond);
mtxUnlock(pool->work_mutex);
poolWait(pool);
vecFree(pool->jobs);
mtxFree(pool->work_mutex);
condFree(pool->work_cond);
condFree(pool->working_cond);
free(pool);
}
bool poolAdd(jobpool_t pool_in, cthread_func_t func, void *arg) {
_pool_internal_t *pool = pool_in;
if (!pool) return false;
mtxLock(pool->work_mutex);
if (pool->head > vecLen(pool->jobs)) {
vecClear(pool->jobs);
pool->head = 0;
}
job_t job = { func, arg };
vecAppend(pool->jobs, job);
condWake(pool->work_cond);
mtxUnlock(pool->work_mutex);
return true;
}
void poolWait(jobpool_t pool_in) {
_pool_internal_t *pool = pool_in;
if (!pool) return;
mtxLock(pool->work_mutex);
// while its either
// - working and there's still some threads doing some work
// - not working and there's still some threads exiting
while ((!pool->stop && pool->working_count > 0) ||
(pool->stop && pool->thread_count > 0)
) {
condWait(pool->working_cond, pool->work_mutex);
}
mtxUnlock(pool->work_mutex);
}
// == PRIVATE FUNCTIONS ===================================
static job_t _getJob(_pool_internal_t *pool) {
if (pool->head >= vecLen(pool->jobs)) {
pool->head = 0;
}
job_t job = pool->jobs[pool->head++];
return job;
}
static int _poolWorker(void *arg) {
_pool_internal_t *pool = arg;
while (true) {
mtxLock(pool->work_mutex);
// wait for a new job
while (pool->head >= vecLen(pool->jobs) && !pool->stop) {
condWait(pool->work_cond, pool->work_mutex);
}
if (pool->stop) {
break;
}
job_t job = _getJob(pool);
pool->working_count++;
mtxUnlock(pool->work_mutex);
if (job.func) {
job.func(job.arg);
}
mtxLock(pool->work_mutex);
pool->working_count--;
if (!pool->stop &&
pool->working_count == 0 &&
pool->head == vecLen(pool->jobs)
) {
condWake(pool->working_cond);
}
mtxUnlock(pool->work_mutex);
}
pool->thread_count--;
condWake(pool->working_cond);
mtxUnlock(pool->work_mutex);
return 0;
}

View file

@ -1,12 +1,12 @@
#pragma once
#include <collatypes.h>
#include <cthreads.h>
typedef void *jobpool_t;
jobpool_t poolInit(uint32 num);
void poolFree(jobpool_t pool);
bool poolAdd(jobpool_t pool, cthread_func_t func, void *arg);
void poolWait(jobpool_t pool);
#pragma once
#include "collatypes.h"
#include "cthreads.h"
typedef void *jobpool_t;
jobpool_t poolInit(uint32 num);
void poolFree(jobpool_t pool);
bool poolAdd(jobpool_t pool, cthread_func_t func, void *arg);
void poolWait(jobpool_t pool);

View file

@ -4,7 +4,7 @@
#include <stdlib.h>
#include <ctype.h>
#ifdef _WIN32
#if COLLA_WIN
#define _BUFSZ 128
#include <lmcons.h>

View file

@ -9,7 +9,7 @@ extern "C" {
#include "str.h"
#include "collatypes.h"
#ifdef _WIN32
#if COLLA_WIN
#include <stdio.h>
#include "win32_slim.h"
isize getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp);

104
gen.lua
View file

@ -1,104 +0,0 @@
local outfile = "colla.h"
local colladir = "src/"
local function hasArg(value)
for k,v in pairs(arg) do
if v == value then
return true
end
end
return false
end
local use_namespace = hasArg("-namespace")
os.remove(outfile)
local function cat(f)
local fp = io.open(f)
local text = fp:read("a")
fp:close()
return text
end
str = [[
/*
colla.h -- All colla libraries in a single header
Do the following in *one* C file to create the implementation
#define COLLA_IMPL
Use the following in the same C file for options
#define COLLA_NO_THREADS // don't include the threads module
#define COLLA_NO_NET // don't include networking stuff
*/
]]
str = str .. cat(colladir .. "collatypes.h") .. "\n"
str = str .. cat(colladir .. "tracelog.h") .. "\n"
str = str .. cat(colladir .. "str.h") .. "\n"
str = str .. cat(colladir .. "vec.h") .. "\n"
str = str .. cat(colladir .. "hashmap.h") .. "\n"
str = str .. cat(colladir .. "utf8.h") .. "\n"
str = str .. cat(colladir .. "ini.h") .. "\n"
str = str .. cat(colladir .. "strstream.h") .. "\n"
str = str .. cat(colladir .. "win32_slim.h") .. "\n"
str = str .. cat(colladir .. "file.h") .. "\n"
str = str .. cat(colladir .. "dir.h") .. "\n"
str = str .. "#ifndef COLLA_NO_NET\n"
str = str .. cat(colladir .. "socket.h") .. "\n"
str = str .. cat(colladir .. "http.h") .. "\n"
str = str .. "#endif // COLLA_NO_NET\n"
str = str .. "#if !defined(__TINYC__) && !defined(COLLA_NO_THREADS)\n"
str = str .. cat(colladir .. "cthreads.h") .. "\n"
str = str .. "#endif // !defined(__TINYC__) && !defined(COLLA_NO_THREADS)\n"
str = str .. "#ifdef COLLA_IMPL\n"
str = str .. cat(colladir .. "tracelog.c") .. "\n"
str = str .. cat(colladir .. "strstream.c") .. "\n"
str = str .. cat(colladir .. "str.c") .. "\n"
str = str .. cat(colladir .. "hashmap.c") .. "\n"
str = str .. cat(colladir .. "utf8.c") .. "\n"
str = str .. cat(colladir .. "ini.c") .. "\n"
str = str .. cat(colladir .. "file.c") .. "\n"
str = str .. cat(colladir .. "dir.c") .. "\n"
str = str .. "#ifndef COLLA_NO_NET\n"
str = str .. cat(colladir .. "socket.c") .. "\n"
str = str .. cat(colladir .. "http.c") .. "\n"
str = str .. "#endif // COLLA_NO_NET\n"
str = str .. "#if !defined(__TINYC__) && !defined(COLLA_NO_THREADS)\n"
str = str .. cat(colladir .. "cthreads.c") .. "\n"
str = str .. "#endif // !defined(__TINYC__) && !defined(COLLA_NO_THREADS)\n"
str = str .. "#endif /* COLLA_IMPL */\n"
-- remove includes
str = str:gsub('#include "([^"]+)"', '/* #include "%1" */')
str = str .. string.format([[
/*
MIT License
Copyright (c) 1994-2019 Lua.org, PUC-Rio.
Copyright (c) 2020-%s snarmph.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
]], os.date("%Y"))
local f = io.open(outfile, "w")
f:write(str)
f:close()