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 <stdint.h>
#include <stddef.h> #include <stddef.h>
#include <stdbool.h>
#include "colladefines.h"
typedef unsigned char uchar; typedef unsigned char uchar;
typedef unsigned short ushort; typedef unsigned short ushort;
@ -19,3 +22,16 @@ typedef int64_t int64;
typedef size_t usize; typedef size_t usize;
typedef ptrdiff_t isize; 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 "cthreads.h"
#include <stdlib.h>
typedef struct { typedef struct {
cthread_func_t func; cthread_func_t func;
void *arg; void *arg;
} _thr_internal_t; } _thr_internal_t;
#ifdef _WIN32 #if COLLA_WIN
#include "win32_slim.h" #define WIN32_LEAN_AND_MEAN
#include <stdlib.h> #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 =========================================== // == THREAD ===========================================
@ -21,7 +31,7 @@ static DWORD _thrFuncInternal(void *arg) {
cthread_t thrCreate(cthread_func_t func, void *arg) { cthread_t thrCreate(cthread_func_t func, void *arg) {
HANDLE thread = INVALID_HANDLE_VALUE; 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) { if(params) {
params->func = func; params->func = func;
@ -68,7 +78,7 @@ bool thrJoin(cthread_t ctx, int *code) {
// == MUTEX ============================================ // == MUTEX ============================================
cmutex_t mtxInit(void) { cmutex_t mtxInit(void) {
CRITICAL_SECTION *crit_sec = malloc(sizeof(CRITICAL_SECTION)); CRITICAL_SECTION *crit_sec = calloc(1, sizeof(CRITICAL_SECTION));
if(crit_sec) { if(crit_sec) {
InitializeCriticalSection(crit_sec); InitializeCriticalSection(crit_sec);
} }
@ -100,10 +110,10 @@ bool mtxUnlock(cmutex_t ctx) {
// == CONDITION VARIABLE =============================== // == CONDITION VARIABLE ===============================
#include <tracelog.h> #include "tracelog.h"
condvar_t condInit(void) { condvar_t condInit(void) {
CONDITION_VARIABLE *cond = malloc(sizeof(CONDITION_VARIABLE)); CONDITION_VARIABLE *cond = calloc(1, sizeof(CONDITION_VARIABLE));
InitializeConditionVariable(cond); InitializeConditionVariable(cond);
return (condvar_t)cond; return (condvar_t)cond;
} }
@ -121,7 +131,7 @@ void condWakeAll(condvar_t cond) {
} }
void condWait(condvar_t cond, cmutex_t mtx) { 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) { 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 #else
#include <pthread.h> #include <pthread.h>
#include <stdlib.h>
#include <unistd.h> #include <unistd.h>
#include <sys/syscall.h> #include <sys/syscall.h>
#include <sys/types.h> #include <sys/types.h>
@ -150,7 +159,7 @@ static void *_thrFuncInternal(void *arg) {
cthread_t thrCreate(cthread_func_t func, void *arg) { cthread_t thrCreate(cthread_func_t func, void *arg) {
pthread_t handle = (pthread_t)NULL; 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) { if(params) {
params->func = func; params->func = func;
@ -195,7 +204,7 @@ bool thrJoin(cthread_t ctx, int *code) {
// == MUTEX ============================================ // == MUTEX ============================================
cmutex_t mtxInit(void) { 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(mutex) {
if(pthread_mutex_init(mutex, NULL)) { if(pthread_mutex_init(mutex, NULL)) {
@ -230,7 +239,7 @@ bool mtxUnlock(cmutex_t ctx) {
// == CONDITION VARIABLE =============================== // == CONDITION VARIABLE ===============================
condvar_t condInit(void) { 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(cond) {
if(pthread_cond_init(cond, NULL)) { if(pthread_cond_init(cond, NULL)) {

View file

@ -1,11 +1,6 @@
#pragma once #pragma once
#ifdef __cplusplus #include "collatypes.h"
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
// == THREAD =========================================== // == THREAD ===========================================
@ -37,31 +32,6 @@ bool mtxLock(cmutex_t ctx);
bool mtxTryLock(cmutex_t ctx); bool mtxTryLock(cmutex_t ctx);
bool mtxUnlock(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 =============================== // == CONDITION VARIABLE ===============================
typedef uintptr_t condvar_t; typedef uintptr_t condvar_t;
@ -76,7 +46,3 @@ void condWakeAll(condvar_t cond);
void condWait(condvar_t cond, cmutex_t mtx); void condWait(condvar_t cond, cmutex_t mtx);
void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds); 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 "file.h"
#include "warnings/colla_warn_beg.h"
#include "tracelog.h" #include "tracelog.h"
#include "format.h"
#ifdef _WIN32 #if COLLA_WIN
#include "win32_slim.h"
#include <stdlib.h>
static DWORD _toWin32Access(int mode) { #define WIN32_LEAN_AND_MEAN
if(mode & FILE_READ) return GENERIC_READ; #include <fileapi.h>
if(mode & FILE_WRITE) return GENERIC_WRITE; #include <handleapi.h>
if(mode & FILE_BOTH) return GENERIC_READ | GENERIC_WRITE;
fatal("unrecognized access mode: %d", mode); #undef FILE_BEGIN
return 0; #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) { static DWORD file__mode_to_creation(filemode_e mode) {
if(mode & FILE_READ) return OPEN_EXISTING; if (mode == FILE_READ) return OPEN_EXISTING;
if(mode == (FILE_WRITE | FILE_CLEAR)) return CREATE_ALWAYS; if (mode == FILE_WRITE) return CREATE_ALWAYS;
if(mode & FILE_WRITE) return OPEN_ALWAYS; return OPEN_ALWAYS;
if(mode & FILE_BOTH) return OPEN_ALWAYS;
fatal("unrecognized creation mode: %d", mode);
return 0;
} }
bool fileExists(const char *fname) { bool fileExists(const char *name) {
return GetFileAttributesA(fname) != INVALID_FILE_ATTRIBUTES; return GetFileAttributesA(name) != INVALID_FILE_ATTRIBUTES;
} }
file_t fileOpen(const char *fname, filemode_t mode) { file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode) {
return (file_t)CreateFileA( TCHAR long_path_prefix[] = TEXT("\\\\?\\");
fname, const usize prefix_len = arrlen(long_path_prefix) - 1;
_toWin32Access(mode),
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, 0,
NULL, NULL,
_toWin32Creation(mode), file__mode_to_creation(mode),
FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_NORMAL,
NULL NULL
); );
return (file_t){
.handle = (uintptr_t)handle,
};
} }
void fileClose(file_t ctx) { void fileClose(file_t ctx) {
if (ctx) { if (!fileIsValid(ctx)) return;
CloseHandle((HANDLE)ctx); 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) { 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) { bool filePutc(file_t ctx, char c) {
return fileWrite(ctx, &c, 1) == 1; return fileWrite(ctx, &c, 1) == 1;
} }
bool filePuts(file_t ctx, const char *str) { bool filePuts(file_t ctx, strview_t v) {
usize len = strlen(str); return fileWrite(ctx, v.buf, v.len) == v.len;
return fileWrite(ctx, str, len) == len;
} }
bool filePutstr(file_t ctx, str_t str) { bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...) {
return fileWrite(ctx, str.buf, str.len) == str.len; 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) { bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args) {
return fileWrite(ctx, view.buf, view.len) == view.len; 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) { buffer_t fileReadWhole(arena_t *arena, arena_t scratch, strview_t name) {
DWORD bytes_read = 0; return fileReadWholeFP(arena, fileOpen(scratch, name, FILE_READ));
BOOL result = ReadFile((HANDLE)ctx, buf, (DWORD)len, &bytes_read, NULL);
return result == TRUE ? (usize)bytes_read : 0;
} }
usize fileWrite(file_t ctx, const void *buf, usize len) { buffer_t fileReadWholeFP(arena_t *arena, file_t ctx) {
DWORD bytes_read = 0; if (!fileIsValid(ctx)) return (buffer_t){0};
BOOL result = WriteFile((HANDLE)ctx, buf, (DWORD)len, &bytes_read, NULL); buffer_t out = {0};
return result == TRUE ? (usize)bytes_read : 0;
}
bool fileSeekEnd(file_t ctx) { out.len = fileSize(ctx);
return SetFilePointerEx((HANDLE)ctx, (LARGE_INTEGER){0}, NULL, FILE_END) == TRUE; out.data = alloc(arena, uint8, out.len);
} usize read = fileRead(ctx, out.data, out.len);
void fileRewind(file_t ctx) { if (read != out.len) {
SetFilePointerEx((HANDLE)ctx, (LARGE_INTEGER){0}, NULL, FILE_BEGIN); err("fileReadWholeFP: fileRead failed, should be %zu but is %zu", out.len, read);
} arenaPop(arena, out.len);
return (buffer_t){0};
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;
} }
fsize = fileTell(ctx); return out;
fileRewind(ctx);
contents.buf = (char *)malloc(fsize + 1);
contents.len = fsize;
if(!contents.buf) {
err("file: couldn't allocate buffer");
goto failed;
}
read = fileRead(ctx, contents.buf, fsize);
if(read != fsize) {
err("file: read wrong amount of bytes: %zu instead of %zu", read, fsize);
goto failed_free;
}
contents.buf[contents.len] = '\0';
failed:
return contents;
failed_free:
strFree(contents);
return strInit();
} }
static vec(uint8) _readWholeInternalVec(file_t ctx) { str_t fileReadWholeStr(arena_t *arena, arena_t scratch, strview_t name) {
vec(uint8) contents = NULL; return fileReadWholeStrFP(arena, fileOpen(scratch, name, FILE_READ));
uint64 fsize = 0; }
usize read = 0;
if(!fileSeekEnd(ctx)) { str_t fileReadWholeStrFP(arena_t *arena, file_t ctx) {
err("file: couldn't read until end"); if (!fileIsValid(ctx)) return (str_t){0};
goto failed;
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); return out;
fileRewind(ctx);
vecReserve(contents, fsize);
if(!contents) {
err("file: couldn't allocate buffer");
goto failed;
}
read = fileRead(ctx, contents, fsize);
if(read != fsize) {
err("file: read wrong amount of bytes: %zu instead of %zu", read, fsize);
goto failed_free;
}
_veclen(contents) = read;
failed:
return contents;
failed_free:
vecFree(contents);
return contents;
} }
vec(uint8) fileReadWhole(const char *fname) { bool fileWriteWhole(arena_t scratch, strview_t name, const void *buf, usize len) {
file_t fp = fileOpen(fname, FILE_READ); file_t fp = fileOpen(scratch, name, FILE_WRITE);
if (!fileIsValid(fp)) return NULL;
vec(uint8) contents = fileReadWholeFP(fp);
fileClose(fp);
return contents;
}
vec(uint8) fileReadWholeFP(file_t ctx) {
return _readWholeInternalVec(ctx);
}
str_t fileReadWholeText(const char *fname) {
file_t fp = fileOpen(fname, FILE_READ);
if(!fileIsValid(fp)) {
err("couldn't open file %s", fname);
return strInit();
}
str_t contents = fileReadWholeTextFP(fp);
fileClose(fp);
return contents;
}
str_t fileReadWholeTextFP(file_t ctx) {
return _readWholeInternalStr(ctx);
}
bool fileWriteWhole(const char *fname, filebuf_t data) {
file_t fp = fileOpen(fname, FILE_WRITE);
if (!fileIsValid(fp)) { if (!fileIsValid(fp)) {
err("couldn't open file %s", fname);
return false; return false;
} }
bool res = fileWriteWholeFP(fp, data); usize written = fileWrite(fp, buf, len);
fileClose(fp); fileClose(fp);
return res; return written == len;
} }
bool fileWriteWholeFP(file_t ctx, filebuf_t data) { uint64 fileGetTime(arena_t scratch, strview_t name) {
usize written = fileWrite(ctx, data.buf, data.len); return fileGetTimeFP(fileOpen(scratch, name, FILE_READ));
return written == data.len;
} }
bool fileWriteWholeText(const char *fname, strview_t string) { #include "warnings/colla_warn_end.h"
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;
}

View file

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

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 "http.h"
#include <string.h> #include "warnings/colla_warn_beg.h"
#include <stdio.h>
#include <stdlib.h>
// #include "os.h" #include "arena.h"
#include "strstream.h"
#include "format.h"
#include "socket.h"
#include "tracelog.h" #include "tracelog.h"
#include "vec.h" #if COLLA_WIN
#if COLLA_CMT_LIB
#pragma comment(lib, "Wininet")
#endif
#ifdef _WIN32 #include <windows.h>
#define stricmp _stricmp #include <wininet.h>
#else
#include <strings.h> // strcasecmp
#define stricmp strcasecmp
#endif #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) { static http_header_t *http__parse_headers(arena_t *arena, instream_t *in) {
vec(http_field_t) fields = *fields_vec; http_header_t *head = NULL;
strview_t line = (strview_t){0};
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;
do { do {
line = istrGetview(in, '\r'); line = istrGetView(in, '\r');
usize pos = strvFind(line, ':', 0); usize pos = strvFind(line, ':', 0);
if(pos != STRV_NOT_FOUND) { if (pos != STR_NONE) {
strview_t key = strvSub(line, 0, pos); http_header_t *h = alloc(arena, http_header_t);
strview_t value = strvSub(line, pos + 2, SIZE_MAX);
char *key_str = NULL; h->key = strvSub(line, 0, pos);
char *value_str = NULL; h->value = strvSub(line, pos + 2, SIZE_MAX);
key_str = strvCopy(key).buf; h->next = head;
value_str = strvCopy(value).buf; head = h;
_setField(fields, key_str, value_str);
free(key_str);
free(value_str);
} }
istrSkip(in, 2); // skip \r\n 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(int status) {
const char *httpGetStatusString(resstatus_t status) {
switch (status) { switch (status) {
case STATUS_OK: return "OK"; case 200: return "OK";
case STATUS_CREATED: return "CREATED"; case 201: return "CREATED";
case STATUS_ACCEPTED: return "ACCEPTED"; case 202: return "ACCEPTED";
case STATUS_NO_CONTENT: return "NO CONTENT"; case 204: return "NO CONTENT";
case STATUS_RESET_CONTENT: return "RESET CONTENT"; case 205: return "RESET CONTENT";
case STATUS_PARTIAL_CONTENT: return "PARTIAL CONTENT"; case 206: return "PARTIAL CONTENT";
case STATUS_MULTIPLE_CHOICES: return "MULTIPLE CHOICES";
case STATUS_MOVED_PERMANENTLY: return "MOVED PERMANENTLY"; case 300: return "MULTIPLE CHOICES";
case STATUS_MOVED_TEMPORARILY: return "MOVED TEMPORARILY"; case 301: return "MOVED PERMANENTLY";
case STATUS_NOT_MODIFIED: return "NOT MODIFIED"; case 302: return "MOVED TEMPORARILY";
case STATUS_BAD_REQUEST: return "BAD REQUEST"; case 304: return "NOT MODIFIED";
case STATUS_UNAUTHORIZED: return "UNAUTHORIZED";
case STATUS_FORBIDDEN: return "FORBIDDEN"; case 400: return "BAD REQUEST";
case STATUS_NOT_FOUND: return "NOT FOUND"; case 401: return "UNAUTHORIZED";
case STATUS_RANGE_NOT_SATISFIABLE: return "RANGE NOT SATISFIABLE"; case 403: return "FORBIDDEN";
case STATUS_INTERNAL_SERVER_ERROR: return "INTERNAL SERVER_ERROR"; case 404: return "NOT FOUND";
case STATUS_NOT_IMPLEMENTED: return "NOT IMPLEMENTED"; case 407: return "RANGE NOT SATISFIABLE";
case STATUS_BAD_GATEWAY: return "BAD GATEWAY";
case STATUS_SERVICE_NOT_AVAILABLE: return "SERVICE NOT AVAILABLE"; case 500: return "INTERNAL SERVER_ERROR";
case STATUS_GATEWAY_TIMEOUT: return "GATEWAY TIMEOUT"; case 501: return "NOT IMPLEMENTED";
case STATUS_VERSION_NOT_SUPPORTED: return "VERSION NOT SUPPORTED"; case 502: return "BAD GATEWAY";
case 503: return "SERVICE NOT AVAILABLE";
case 504: return "GATEWAY TIMEOUT";
case 505: return "VERSION NOT SUPPORTED";
} }
return "UNKNOWN"; return "UNKNOWN";
} }
// == HTTP VERSION ============================================================
int httpVerNumber(http_version_t ver) { int httpVerNumber(http_version_t ver) {
return (ver.major * 10) + ver.minor; 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() { strview_t method = strvTrim(istrGetView(&in, '/'));
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, '/'));
istrSkip(&in, 1); // skip / istrSkip(&in, 1); // skip /
strview_t page = strvTrim(istrGetview(&in, ' ')); req.url = strvTrim(istrGetView(&in, ' '));
strview_t http = strvTrim(istrGetview(&in, '\n')); strview_t http = strvTrim(istrGetView(&in, '\n'));
istrSkip(&in, 1); // skip \n istrSkip(&in, 1); // skip \n
_parseFields(&req.fields, &in); req.headers = http__parse_headers(arena, &in);
strview_t body = strvTrim(istrGetviewLen(&in, 0, SIZE_MAX)); req.body = strvTrim(istrGetViewLen(&in, SIZE_MAX));
// parse data strview_t methods[] = { strv("GET"), strv("POST"), strv("HEAD"), strv("PUT"), strv("DELETE") };
usize methods_count = arrlen(methods);
// -- method for (usize i = 0; i < methods_count; ++i) {
const char *methods[] = { "GET", "POST", "HEAD", "PUT", "DELETE" }; if (strvEquals(method, methods[i])) {
const int methods_count = sizeof(methods) / sizeof(*methods); req.method = (http_method_e)i;
break;
for (int i = 0; i < methods_count; ++i) {
if (strvCompare(method, strvInit(methods[i])) == 0) {
req.method = (reqtype_t)i;
} }
} }
// -- page
req.uri = strvCopy(page).buf;
// -- http
in = istrInitLen(http.buf, http.len); in = istrInitLen(http.buf, http.len);
istrIgnoreAndSkip(&in, '/'); // skip HTTP/ istrIgnoreAndSkip(&in, '/'); // skip HTTP/
istrGetu8(&in, &req.version.major); istrGetU8(&in, &req.version.major);
istrSkip(&in, 1); // skip . istrSkip(&in, 1); // skip .
istrGetu8(&in, &req.version.minor); istrGetU8(&in, &req.version.minor);
// -- body
req.body = strvCopy(body).buf;
return req; return req;
} }
void reqFree(http_request_t ctx) { http_res_t httpParseRes(arena_t *arena, strview_t response) {
for (http_field_t *it = ctx.fields; it != vecEnd(ctx.fields); ++it) { http_res_t res = {0};
free(it->key); instream_t in = istrInitLen(response.buf, response.len);
free(it->value);
}
vecFree(ctx.fields);
free(ctx.uri);
free(ctx.body);
}
bool reqHasField(http_request_t *ctx, const char *key) { strview_t http = istrGetViewLen(&in, 5);
for(uint32 i = 0; i < vecLen(ctx->fields); ++i) { if (!strvEquals(http, strv("HTTP"))) {
if(stricmp(ctx->fields[i].key, key) == 0) { err("response doesn't start with 'HTTP', instead with %v", http);
return true; return (http_res_t){0};
}
}
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;
} }
istrSkip(&in, 1); // skip / istrSkip(&in, 1); // skip /
istrGetu8(&in, &ctx.version.major); istrGetU8(&in, &res.version.major);
istrSkip(&in, 1); // skip . istrSkip(&in, 1); // skip .
istrGetu8(&in, &ctx.version.minor); istrGetU8(&in, &res.version.minor);
istrGeti32(&in, (int32*)&ctx.status_code); istrGetI32(&in, (int32*)&res.status_code);
istrIgnore(&in, '\n'); istrIgnore(&in, '\n');
istrSkip(&in, 1); // skip \n istrSkip(&in, 1); // skip \n
resParseFields(&ctx, &in); res.headers = http__parse_headers(arena, &in);
const char *tran_encoding = resGetField(&ctx, "transfer-encoding"); strview_t encoding = httpGetHeader(res.headers, strv("transfer-encoding"));
if(tran_encoding == NULL || stricmp(tran_encoding, "chunked") != 0) { if (!strvEquals(encoding, strv("chunked"))) {
strview_t body = istrGetviewLen(&in, 0, SIZE_MAX); res.body = istrGetViewLen(&in, SIZE_MAX);
vecClear(ctx.body);
vecReserve(ctx.body, body.len);
memcpy(ctx.body, body.buf, body.len);
} }
else { else {
// fatal("chunked encoding not implemented yet"); err("chunked encoding not implemented yet! body ignored");
err("chunked encoding not implemented yet");
} }
return ctx; return res;
} }
void resFree(http_response_t ctx) { str_t httpReqToStr(arena_t *arena, http_req_t *req) {
for (http_field_t *it = ctx.fields; it != vecEnd(ctx.fields); ++it) { outstream_t out = ostrInit(arena);
free(it->key);
free(it->value); 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};
} }
vecFree(ctx.fields);
vecFree(ctx.body); 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);
} }
bool resHasField(http_response_t *ctx, const char *key) { str_t httpResToStr(arena_t *arena, http_res_t *res) {
for(uint32 i = 0; i < vecLen(ctx->fields); ++i) { outstream_t out = ostrInit(arena);
if(stricmp(ctx->fields[i].key, key) == 0) {
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; return true;
} }
h = h->next;
} }
return false; return false;
} }
void resSetField(http_response_t *ctx, const char *key, const char *value) { void httpSetHeader(http_header_t *headers, strview_t key, strview_t value) {
_setField(&ctx->fields, key, 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) { strview_t httpGetHeader(http_header_t *headers, strview_t key) {
for(uint32 i = 0; i < vecLen(ctx->fields); ++i) { http_header_t *h = headers;
if(stricmp(ctx->fields[i].key, field) == 0) { while (h) {
return ctx->fields[i].value; if (strvEquals(h->key, key)) {
return h->value;
} }
h = h->next;
} }
return NULL; return (strview_t){0};
} }
void resParseFields(http_response_t *ctx, str_istream_t *in) { str_t httpMakeUrlSafe(arena_t *arena, strview_t string) {
_parseFields(&ctx->fields, in); strview_t chars = strv(" !\"#$%%&'()*+,/:;=?@[]");
} usize final_len = string.len;
str_ostream_t resPrepare(http_response_t *ctx) { // find final string length first
str_ostream_t out = ostrInitLen(1024); for (usize i = 0; i < string.len; ++i) {
if (strvContains(chars, string.buf[i])) {
ostrPrintf( final_len += 2;
&out, "HTTP/%hhu.%hhu %d %s\r\n", }
ctx->version.major, ctx->version.minor, }
ctx->status_code, httpGetStatusString(ctx->status_code)
); str_t out = {
for (http_field_t *field = ctx->fields; field != vecEnd(ctx->fields); ++field) { .buf = alloc(arena, char, final_len + 1),
ostrPrintf(&out, "%s: %s\r\n", field->key, field->value); .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; return out;
} }
str_t resString(http_response_t *ctx) { http_url_t httpSplitUrl(strview_t url) {
str_ostream_t out = resPrepare(ctx); http_url_t out = {0};
return ostrAsStr(out);
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() { http_req_t req = {
return (http_client_t) { .version = (http_version_t){ 1, 1 },
.port = 80, .url = request->url,
.body = request->body,
.method = request->request_type,
}; };
}
void hcliFree(http_client_t ctx) { http_header_t *h = NULL;
strFree(ctx.host_name);
}
void hcliSetHost(http_client_t *ctx, strview_t hostname) { for (int i = 0; i < request->header_count; ++i) {
// if the hostname starts with http:// (case insensitive) http_header_t *header = request->headers + i;
if(strvICompare(strvSub(hostname, 0, 7), strvInit("http://")) == 0) { header->next = h;
ctx->host_name = strvCopy(strvSub(hostname, 7, SIZE_MAX)); h = header;
}
else if(strvICompare(strvSub(hostname, 0, 8), strvInit("https://")) == 0) {
err("HTTPS protocol not yet supported");
return;
}
else {
// undefined protocol, use HTTP
ctx->host_name = strvCopy(hostname);
}
}
http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *req) {
if (strBack(ctx->host_name) == '/') {
ctx->host_name.buf[--ctx->host_name.len] = '\0';
}
if(!reqHasField(req, "Host")) {
reqSetField(req, "Host", ctx->host_name.buf);
}
if(!reqHasField(req, "Content-Length")) {
if(req->body) {
str_ostream_t out = ostrInitLen(20);
ostrAppendu64(&out, strlen(req->body));
reqSetField(req, "Content-Length", out.buf);
ostrFree(out);
}
else {
reqSetField(req, "Content-Length", "0");
}
}
if(req->method == REQ_POST && !reqHasField(req, "Content-Type")) {
reqSetField(req, "Content-Type", "application/x-www-form-urlencoded");
}
if(httpVerNumber(req->version) >= 11 && !reqHasField(req, "Connection")) {
reqSetField(req, "Connection", "close");
} }
http_response_t res = {0}; req.headers = h;
str_t req_str = strInit();
str_ostream_t received = ostrInitLen(1024);
if(!skInit()) { http_url_t url = httpSplitUrl(req.url);
err("couldn't initialize sockets %s", skGetErrorString());
goto skopen_error; if (strvEndsWith(url.host, '/')) {
url.host = strvRemoveSuffix(url.host, 1);
} }
ctx->socket = skOpen(SOCK_TCP); if (!httpHasHeader(req.headers, strv("Host"))) {
if(ctx->socket == INVALID_SOCKET) { httpSetHeader(req.headers, strv("Host"), url.host);
err("couldn't open socket %s", skGetErrorString()); }
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; goto error;
} }
if(skConnect(ctx->socket, ctx->host_name.buf, ctx->port)) { socket_t sock = skOpen(SOCK_TCP);
req_str = reqString(req); if (!skIsValid(sock)) {
if(req_str.len == 0) { 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"); err("couldn't get string from request");
goto error; goto error;
} }
if(skSend(ctx->socket, req_str.buf, (int)req_str.len) == SOCKET_ERROR) { if (skSend(sock, reqstr.buf, (int)reqstr.len) == -1) {
err("couldn't send request to socket: %s", skGetErrorString()); err("couldn't send request to socket: %s", skGetErrorString());
goto error; goto error;
} }
char buffer[1024]; outstream_t response = ostrInit(request->arena);
char buffer[4096];
int read = 0; int read = 0;
do { do {
read = skReceive(ctx->socket, buffer, sizeof(buffer)); read = skReceive(sock, buffer, arrlen(buffer));
if(read == -1) { if (read == -1) {
err("couldn't get the data from the server: %s", skGetErrorString()); err("couldn't get the data from the server: %s", skGetErrorString());
goto error; goto error;
} }
ostrAppendview(&received, strvInitLen(buffer, read)); ostrPuts(&response, strv(buffer, read));
} while(read != 0); } while (read != 0);
// if the data received is not null terminated if (!skClose(sock)) {
if(*(received.buf + received.len) != '\0') { err("couldn't close socket: %s", skGetErrorString());
ostrPutc(&received, '\0');
received.len--;
} }
res = resParse(received.buf); if (!skCleanup()) {
} err("couldn't clean up sockets: %s", skGetErrorString());
else {
err("Couldn't connect to host %s -> %s", ctx->host_name, skGetErrorString());
} }
if(!skClose(ctx->socket)) { return httpParseRes(request->arena, ostrAsView(&response));
err("Couldn't close socket");
}
error: error:
if(!skCleanup()) { arenaRewind(request->arena, arena_begin);
err("couldn't clean up sockets %s", skGetErrorString()); skCleanup();
} return (http_res_t){0};
skopen_error:
strFree(req_str);
ostrFree(received);
return res;
} }
http_response_t httpGet(strview_t hostname, strview_t uri) { #if COLLA_WIN
http_request_t request = reqInit();
request.method = REQ_GET;
reqSetUri(&request, uri);
http_client_t client = hcliInit(); buffer_t httpsRequest(http_request_desc_t *req) {
hcliSetHost(&client, hostname); HINTERNET internet = InternetOpen(
TEXT("COLLA"),
http_response_t res = hcliSendRequest(&client, &request); INTERNET_OPEN_TYPE_PRECONFIG,
NULL,
reqFree(request); NULL,
hcliFree(client); 0
);
return res; if (!internet) {
} fatal("call to InternetOpen failed: %u", GetLastError());
url_split_t urlSplit(strview_t uri) {
url_split_t out = {0};
if (strvStartsWithView(uri, strvInit("https://"))) {
uri = strvRemovePrefix(uri, 8);
}
else if (strvStartsWithView(uri, strvInit("http://"))) {
uri = strvRemovePrefix(uri, 7);
} }
out.host = strvSub(uri, 0, strvFind(uri, '/', 0)); http_url_t split = httpSplitUrl(req->url);
out.uri = strvSub(uri, out.host.len, SIZE_MAX); strview_t server = split.host;
return out; 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 #pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
#include <stdbool.h>
#include "collatypes.h" #include "collatypes.h"
#include "str.h" #include "str.h"
#include "strstream.h"
#include "socket.h" typedef struct arena_t arena_t;
typedef uintptr_t socket_t;
typedef enum { typedef enum {
REQ_GET, HTTP_GET,
REQ_POST, HTTP_POST,
REQ_HEAD, HTTP_HEAD,
REQ_PUT, HTTP_PUT,
REQ_DELETE HTTP_DELETE
} reqtype_t; } http_method_e;
typedef enum { const char *httpGetStatusString(int status);
// 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);
typedef struct { typedef struct {
uint8 major; 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) // 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); int httpVerNumber(http_version_t ver);
typedef struct { typedef struct http_header_t {
char *key; strview_t key;
char *value; strview_t value;
} http_field_t; struct http_header_t *next;
} http_header_t;
#include "vec.h"
// == HTTP REQUEST ============================================================
typedef struct { typedef struct {
reqtype_t method; http_method_e method;
http_version_t version; http_version_t version;
vec(http_field_t) fields; http_header_t *headers;
char *uri; strview_t url;
char *body; strview_t body;
} http_request_t; } http_req_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 ===========================================================
typedef struct { typedef struct {
resstatus_t status_code; int status_code;
vec(http_field_t) fields;
http_version_t version; http_version_t version;
vec(uint8) body; http_header_t *headers;
} http_response_t; strview_t body;
} http_res_t;
http_response_t resParse(const char *data); // strview_t request needs to be valid for http_req_t to be valid!
void resFree(http_response_t ctx); 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); str_t httpReqToStr(arena_t *arena, http_req_t *req);
void resSetField(http_response_t *ctx, const char *key, const char *value); str_t httpResToStr(arena_t *arena, http_res_t *res);
const char *resGetField(http_response_t *ctx, const char *field);
// void resParse(http_response_t *ctx, const char *data); bool httpHasHeader(http_header_t *headers, strview_t key);
void resParseFields(http_response_t *ctx, str_istream_t *in); void httpSetHeader(http_header_t *headers, strview_t key, strview_t value);
str_ostream_t resPrepare(http_response_t *ctx); strview_t httpGetHeader(http_header_t *headers, strview_t key);
str_t resString(http_response_t *ctx);
// == HTTP CLIENT ============================================================= str_t httpMakeUrlSafe(arena_t *arena, strview_t string);
typedef struct {
str_t host_name;
uint16 port;
socket_t socket;
} http_client_t;
http_client_t hcliInit(void);
void hcliFree(http_client_t ctx);
void hcliSetHost(http_client_t *ctx, strview_t hostname);
http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *request);
// == HTTP ====================================================================
http_response_t httpGet(strview_t hostname, strview_t uri);
// == URL =====================================================================
typedef struct { typedef struct {
strview_t host; strview_t host;
strview_t uri; 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 typedef struct {
} // extern "C" arena_t *arena;
#endif 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 "ini.h"
#include "warnings/colla_warn_beg.h"
#include <assert.h>
#include "strstream.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 = { ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options) {
.key_value_divider = '=' 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); ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options) {
static initable_t *findTable(ini_t *ctx, strview_t name); str_t data = fileReadWholeStrFP(arena, file);
static inivalue_t *findValue(vec(inivalue_t) values, strview_t key); return iniParseStr(arena, strv(data), options);
static void addTable(ini_t *ctx, str_istream_t *in, const iniopts_t *options); }
static void addValue(initable_t *table, str_istream_t *in, const iniopts_t *options);
void _iniParseInternal(ini_t *ini, const iniopts_t *options) { ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options) {
// add root table ini_t out = {
vecAppend(ini->tables, (initable_t){0}); .text = str,
str_istream_t in = istrInitLen(ini->text.buf, ini->text.len); .tables = NULL,
istrSkipWhitespace(&in); };
while (!istrIsFinished(in)) { ini__parse(arena, &out, options);
switch(*in.cur) { return out;
case '[': }
addTable(ini, &in, options);
break; initable_t *iniGetTable(ini_t *ctx, strview_t name) {
case '#': case ';': initable_t *t = ctx ? ctx->tables : NULL;
istrIgnore(&in, '\n'); while (t) {
break; if (strvEquals(t->name, name)) {
default: return t;
addValue(&ini->tables[0], &in, options);
break;
} }
istrSkipWhitespace(&in); t = t->next;
} }
return NULL;
} }
ini_t iniParse(const char *filename, const iniopts_t *options) { inivalue_t *iniGet(initable_t *ctx, strview_t key) {
ini_t ini = { .text = fileReadWholeText(filename) }; inivalue_t *v = ctx ? ctx->values : NULL;
if (strIsEmpty(ini.text)) return ini; while (v) {
iniopts_t opts = setDefaultOptions(options); if (strvEquals(v->key, key)) {
_iniParseInternal(&ini, &opts); return v;
return ini;
}
ini_t iniParseString(const char *inistr, const iniopts_t *options) {
ini_t ini = { .text = strFromStr(inistr) };
if (!options) options = &default_opts;
_iniParseInternal(&ini, options);
return ini;
}
void iniFree(ini_t ctx) {
strFree(ctx.text);
for (uint32 i = 0; i < vecLen(ctx.tables); ++i) {
vecFree(ctx.tables[i].values);
} }
vecFree(ctx.tables); v = v->next;
}
initable_t *iniGetTable(ini_t *ctx, const char *name) {
if (!name) {
return &ctx->tables[0];
}
else {
return findTable(ctx, strvInit(name));
} }
return NULL;
} }
inivalue_t *iniGet(initable_t *ctx, const char *key) { iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim) {
return ctx ? findValue(ctx->values, strvInit(key)) : NULL; strview_t v = value ? value->value : (strview_t){0};
}
vec(strview_t) iniAsArray(const inivalue_t *value, char delim) {
if (!value) return NULL;
if (!delim) delim = ' '; if (!delim) delim = ' ';
vec(strview_t) out = NULL; strview_t *beg = (strview_t *)arena->current;
strview_t v = value->value; usize count = 0;
usize start = 0; usize start = 0;
for (usize i = 0; i < v.len; ++i) { for (usize i = 0; i < v.len; ++i) {
if (v.buf[i] == delim) { if (v.buf[i] == delim) {
strview_t arr_val = strvTrim(strvSub(v, start, i)); strview_t arrval = strvTrim(strvSub(v, start, i));
if (!strvIsEmpty(arr_val)) vecAppend(out, arr_val); if (!strvIsEmpty(arrval)) {
strview_t *newval = alloc(arena, strview_t);
*newval = arrval;
++count;
}
start = i + 1; start = i + 1;
} }
} }
strview_t last = strvTrim(strvSub(v, start, SIZE_MAX)); 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; return out;
} }
vec(strview_t) iniAsArrayU8(const inivalue_t *value, const char *delim) { int64 iniAsInt(inivalue_t *value) {
if (!value || !delim) return NULL; strview_t v = value ? value->value : (strview_t){0};
instream_t in = istrInitLen(v.buf, v.len);
rune cpdelim = utf8Decode(&delim); int64 out = 0;
vec(strview_t) out = NULL; if (!istrGetI64(&in, &out)) {
strview_t v = value->value; out = 0;
const char *start = v.buf;
const char *buf = v.buf;
const char *prevbuf = buf;
for(rune cp = utf8Decode(&buf);
buf != (v.buf + v.len);
cp = utf8Decode(&buf)
) {
if (cp == cpdelim) {
usize start_pos = start - v.buf;
usize end_pos = prevbuf - v.buf;
strview_t arr_val = strvTrim(strvSub(v, start_pos, end_pos));
if (!strvIsEmpty(arr_val)) vecAppend(out, arr_val);
// buf has already gone to the next codepoint, skipping the delimiter
start = buf;
} }
prevbuf = buf;
}
strview_t last = strvTrim(strvSub(v, start - v.buf, SIZE_MAX));
if (!strvIsEmpty(last)) vecAppend(out, last);
return out; return out;
} }
uint64 iniAsUInt(const inivalue_t *value) { double iniAsNum(inivalue_t *value) {
if (!value) return 0; strview_t v = value ? value->value : (strview_t){0};
str_istream_t in = istrInitLen(value->value.buf, value->value.len); instream_t in = istrInitLen(v.buf, v.len);
uint64 val = 0; double out = 0;
if (!istrGetu64(&in, &val)) val = 0; if (!istrGetDouble(&in, &out)) {
return val; out = 0.0;
} }
int64 iniAsInt(const inivalue_t *value) {
if (!value) return 0;
str_istream_t in = istrInitLen(value->value.buf, value->value.len);
int64 val = 0;
if (!istrGeti64(&in, &val)) val = 0;
return val;
}
double iniAsNum(const inivalue_t *value) {
if (!value) return 0.f;
str_istream_t in = istrInitLen(value->value.buf, value->value.len);
double val = 0;
if (!istrGetdouble(&in, &val)) val = 0;
return val;
}
bool iniAsBool(const inivalue_t *value) {
if (!value) return false;
return strvCompare(value->value, strvInit("true")) == 0;
}
// == INI WRITER ========================================================================
#include "strstream.h"
#include "file.h"
static const winiopts_t default_wopts = {0};
iniwriter_t winiInit() {
iniwriter_t out = {0};
vecAppend(out.tables, (winitable_t){0});
return out; return out;
} }
void winiFree(iniwriter_t ctx) { bool iniAsBool(inivalue_t *value) {
for (winitable_t *tab = ctx.tables; tab != vecEnd(ctx.tables); ++tab) { strview_t v = value ? value->value : (strview_t){0};
strFree(tab->key); instream_t in = istrInitLen(v.buf, v.len);
for (winivalue_t *val = tab->values; val != vecEnd(tab->values); ++val) { bool out = 0;
strFree(val->key); if (!istrGetBool(&in, &out)) {
strFree(val->value); out = false;
} }
vecFree(tab->values); return out;
}
// == PRIVATE FUNCTIONS ==============================================================================
#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);
} }
vecFree(ctx.tables);
#undef SETOPT
return out;
} }
str_t winiToString(iniwriter_t ctx, const winiopts_t *options) { static void ini__add_value(arena_t *arena, initable_t *table, instream_t *in, iniopts_t *opts) {
if (!options) options = &default_wopts; assert(table);
str_ostream_t out = ostrInitLen(1024 * 20); strview_t key = strvTrim(istrGetView(in, opts->key_value_divider));
if (!options->no_discalimer) ostrPuts(&out, "# auto-generated by colla's ini.h, do not modify!\n");
// add root values
winitable_t *root = &ctx.tables[0];
for (winivalue_t *val = root->values; val != vecEnd(root->values); ++val) {
ostrPrintf(&out, "%s = %s\n", val->key.buf, val->value.buf);
}
if (root->values) ostrPuts(&out, "\n");
// add each table
for (usize i = 1; i < vecLen(ctx.tables); ++i) {
winitable_t *tab = &ctx.tables[i];
ostrPrintf(&out, "[%s]\n", tab->key.buf);
for (winivalue_t *val = tab->values; val != vecEnd(tab->values); ++val) {
ostrPrintf(&out, "%s = %s\n", val->key.buf, val->value.buf);
}
if ((i + 1) < vecLen(ctx.tables)) ostrPuts(&out, "\n");
}
return ostrAsStr(out);
}
void winiToFile(iniwriter_t ctx, const char *filename, const winiopts_t *options) {
if (!options) options = &default_wopts;
file_t fp = fileOpen(filename, FILE_WRITE);
if (!fileIsValid(fp)) {
err("couldn't write ini to file %s", filename);
return;
}
str_t string = winiToString(ctx, options);
fileWriteWholeTextFP(fp, strvInitStr(string));
strFree(string);
fileClose(fp);
}
winivalue_t *winiAddValEmpty(winitable_t *table) {
if (!table) return NULL;
vecAppend(table->values, (winivalue_t){0});
return &vecBack(table->values);
}
winivalue_t *winiAddVal(winitable_t *table, const char *key, const char *value) {
if (!table) return NULL;
winivalue_t val = { .key = strFromStr(key), .value = strFromStr(value) };
vecAppend(table->values, val);
return &vecBack(table->values);
}
winivalue_t *winiAddValStr(winitable_t *table, str_t key, str_t value) {
if (!table) return NULL;
winivalue_t val = { .key = key, .value = value };
vecAppend(table->values, val);
return &vecBack(table->values);
}
winivalue_t *winiAddValView(winitable_t *table, strview_t key, strview_t value) {
if (!table) return NULL;
winivalue_t val = { .key = strFromView(key), .value = strFromView(value) };
vecAppend(table->values, val);
return &vecBack(table->values);
}
winitable_t *winiAddTablEmpty(iniwriter_t *ctx) {
vecAppend(ctx->tables, (winitable_t){0});
return &vecBack(ctx->tables);
}
winitable_t *winiAddTab(iniwriter_t *ctx, const char *name) {
winitable_t tab = { .key = strFromStr(name) };
vecAppend(ctx->tables, tab);
return &vecBack(ctx->tables);
}
winitable_t *winiAddTabStr(iniwriter_t *ctx, str_t name) {
winitable_t tab = { .key = name };
vecAppend(ctx->tables, tab);
return &vecBack(ctx->tables);
}
winitable_t *winiAddTabView(iniwriter_t *ctx, strview_t name) {
winitable_t tab = { .key = strFromView(name) };
vecAppend(ctx->tables, tab);
return &vecBack(ctx->tables);
}
// == PRIVATE FUNCTIONS ========================================================
static 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));
istrSkip(in, 1); istrSkip(in, 1);
strview_t value = strvTrim(istrGetview(in, '\n')); strview_t value = strvTrim(istrGetView(in, '\n'));
// value might be until EOF, in that case no use in skipping istrSkip(in, 1);
if (!istrIsFinished(*in)) istrSkip(in, 1); // skip newline inivalue_t *newval = NULL;
inivalue_t *new_value = options->merge_duplicate_keys ? findValue(table->values, key) : NULL;
if (!new_value) { if (opts->merge_duplicate_keys) {
inivalue_t ini_val = (inivalue_t){ key, value }; newval = table->values;
vecAppend(table->values, ini_val); while (newval) {
if (strvEquals(newval->key, key)) {
break;
}
newval = newval->next;
}
}
if (newval) {
newval->value = value;
} }
else { 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 #pragma once
#include <stdbool.h> #include "collatypes.h"
#include "str.h" #include "str.h"
#include "vec.h" #include "file.h"
#include "utf8.h"
#ifdef __cplusplus typedef struct arena_t arena_t;
extern "C" {
#endif
// == INI READER ======================================================================== typedef struct inivalue_t {
typedef struct {
strview_t key; strview_t key;
strview_t value; strview_t value;
struct inivalue_t *next;
} inivalue_t; } inivalue_t;
typedef struct { typedef struct initable_t {
strview_t name; strview_t name;
vec(inivalue_t) values; inivalue_t *values;
inivalue_t *tail;
struct initable_t *next;
} initable_t; } initable_t;
typedef struct { typedef struct {
str_t text; strview_t text;
vec(initable_t) tables; initable_t *tables;
initable_t *tail;
} ini_t; } ini_t;
typedef struct { typedef struct {
@ -32,58 +31,22 @@ typedef struct {
char key_value_divider; // default = char key_value_divider; // default =
} iniopts_t; } iniopts_t;
ini_t iniParse(const char *filename, const iniopts_t *options); ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options);
ini_t iniParseString(const char *inistr, const iniopts_t *options); ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options);
void iniFree(ini_t ctx); ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options);
initable_t *iniGetTable(ini_t *ctx, const char *name); #define INI_ROOT strv("__ROOT__")
inivalue_t *iniGet(initable_t *ctx, const char *key);
vec(strview_t) iniAsArray(const inivalue_t *value, char delim); initable_t *iniGetTable(ini_t *ctx, strview_t name);
// delim is expected to be a single utf8 character inivalue_t *iniGet(initable_t *ctx, strview_t key);
vec(strview_t) iniAsArrayU8(const inivalue_t *value, const char *delim);
uint64 iniAsUInt(const inivalue_t *value);
int64 iniAsInt(const inivalue_t *value);
double iniAsNum(const inivalue_t *value);
bool iniAsBool(const inivalue_t *value);
// == INI WRITER ========================================================================
typedef struct { typedef struct {
str_t key; strview_t *values;
str_t value; usize count;
} winivalue_t; } iniarray_t;
typedef struct { iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim);
str_t key; uint64 iniAsUInt(inivalue_t *value);
vec(winivalue_t) values; int64 iniAsInt(inivalue_t *value);
} winitable_t; double iniAsNum(inivalue_t *value);
bool iniAsBool(inivalue_t *value);
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

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,55 +1,71 @@
#include "socket.h" #include "socket.h"
#include <stdio.h> #if COLLA_WIN && COLLA_CMT_LIB
#include "tracelog.h" #pragma comment(lib, "Ws2_32")
#ifndef NDEBUG
// VERY MUCH NOT THREAD SAFE
static int initialize_count = 0;
#endif #endif
#if SOCK_WINDOWS #if COLLA_WIN
static bool _win_skInit(); #include <winsock2.h>
static bool _win_skCleanup();
static int _win_skGetError();
static const char *_win_skGetErrorString();
#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 <netdb.h>
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>
#include <string.h> // strerror #include <string.h> // strerror
#include <poll.h> #include <poll.h>
#define INVALID_SOCKET (-1) bool skInit(void) {
#define SOCKET_ERROR (-1) return true;
static bool _posix_skInit();
static bool _posix_skCleanup();
static int _posix_skGetError();
static const char *_posix_skGetErrorString();
#define SOCK_CALL(fun) _posix_##fun
#endif
bool skInit() {
#ifndef NDEBUG
++initialize_count;
#endif
return SOCK_CALL(skInit());
} }
bool skCleanup() { bool skCleanup(void) {
#ifndef NDEBUG return true;
--initialize_count;
#endif
return SOCK_CALL(skCleanup());
} }
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; int sock_type = 0;
switch(type) { switch(type) {
@ -62,58 +78,37 @@ socket_t skOpen(sktype_t type) {
} }
socket_t skOpenEx(const char *protocol) { socket_t skOpenEx(const char *protocol) {
#ifndef NDEBUG
if(initialize_count <= 0) {
fatal("skInit has not been called");
}
#endif
struct protoent *proto = getprotobyname(protocol); struct protoent *proto = getprotobyname(protocol);
if(!proto) { if(!proto) {
return INVALID_SOCKET; return (socket_t){0};
} }
return skOpenPro(AF_INET, SOCK_STREAM, proto->p_proto); return skOpenPro(AF_INET, SOCK_STREAM, proto->p_proto);
} }
socket_t skOpenPro(int af, int type, int protocol) { 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); return socket(af, type, protocol);
} }
sk_addrin_t skAddrinInit(const char *ip, uint16_t port) { bool skIsValid(socket_t sock) {
sk_addrin_t addr; return sock != (socket_t)-1;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip);
return addr;
} }
bool skClose(socket_t sock) { skaddrin_t skAddrinInit(const char *ip, uint16_t port) {
#if SOCK_WINDOWS return (skaddrin_t){
int error = closesocket(sock); .sin_family = AF_INET,
#elif SOCK_POSIX .sin_port = htons(port),
int error = close(sock); // TODO use inet_pton instead
#endif .sin_addr.s_addr = inet_addr(ip),
sock = INVALID_SOCKET; };
return error != SOCKET_ERROR;
} }
bool skBind(socket_t sock, const char *ip, uint16_t port) { bool skBind(socket_t sock, const char *ip, uint16_t port) {
sk_addrin_t addr; skaddrin_t addr = skAddrinInit(ip, port);
addr.sin_family = AF_INET; return skBindPro(sock, (skaddr_t *)&addr, sizeof(addr));
// TODO use inet_pton instead
addr.sin_addr.s_addr = inet_addr(ip);
addr.sin_port = htons(port);
return skBindPro(sock, (sk_addr_t *) &addr, sizeof(addr));
} }
bool skBindPro(socket_t sock, const sk_addr_t *name, sk_len_t namelen) { bool skBindPro(socket_t sock, const skaddr_t *name, int namelen) {
return bind(sock, name, namelen) != SOCKET_ERROR; return bind(sock, name, namelen) != -1;
} }
bool skListen(socket_t sock) { bool skListen(socket_t sock) {
@ -121,16 +116,16 @@ bool skListen(socket_t sock) {
} }
bool skListenPro(socket_t sock, int backlog) { bool skListenPro(socket_t sock, int backlog) {
return listen(sock, backlog) != SOCKET_ERROR; return listen(sock, backlog) != -1;
} }
socket_t skAccept(socket_t sock) { socket_t skAccept(socket_t sock) {
sk_addrin_t addr; skaddrin_t addr = {0};
sk_len_t addr_size = (sk_len_t)sizeof(addr); int addr_size = sizeof(addr);
return skAcceptPro(sock, (sk_addr_t *) &addr, &addr_size); 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); 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]); address = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]);
} }
sk_addrin_t addr; skaddrin_t addr = skAddrinInit(address, server_port);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(address);
addr.sin_port = htons(server_port);
return skConnectPro(sock, (sk_addr_t *) &addr, sizeof(addr)); return skConnectPro(sock, (skaddr_t *)&addr, sizeof(addr));
} }
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) {
return connect(sock, name, namelen) != SOCKET_ERROR; return connect(sock, name, namelen) != -1;
} }
int skSend(socket_t sock, const void *buf, int len) { 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); return send(sock, buf, len, flags);
} }
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) {
return skSendToPro(sock, buf, len, 0, (sk_addr_t*) to, sizeof(sk_addrin_t)); 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); 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); return recv(sock, buf, len, flags);
} }
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) {
sk_len_t fromlen = sizeof(sk_addr_t); int fromlen = sizeof(skaddr_t);
return skReceiveFromPro(sock, buf, len, 0, (sk_addr_t*)from, &fromlen); 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); return recvfrom(sock, buf, len, flags, from, fromlen);
} }
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) { // put this at the end of file to not make everything else unreadable
#if SOCK_WINDOWS #if COLLA_WIN
return WSAPoll(to_poll, num_to_poll, timeout); const char *skGetErrorString(void) {
#elif SOCK_POSIX switch(skGetError()) {
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()) {
case WSA_INVALID_HANDLE: return "Specified event object handle is invalid."; case WSA_INVALID_HANDLE: return "Specified event object handle is invalid.";
case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available."; case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available.";
case WSA_INVALID_PARAMETER: return "One or more parameters are invalid."; case WSA_INVALID_PARAMETER: return "One or more parameters are invalid.";
@ -324,22 +283,4 @@ static const char *_win_skGetErrorString() {
return "(nothing)"; 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 #pragma once
#ifdef __cplusplus #include "collatypes.h"
extern "C" {
#endif
#include <stdbool.h> #if COLLA_WIN
#include <stdint.h> #include <winsock2.h>
#ifdef _WIN32
#define SOCK_WINDOWS 1
#else #else
#define SOCK_POSIX 1 #include <sys/socket.h>
#endif #endif
#if SOCK_WINDOWS typedef uintptr_t socket_t;
#pragma warning(disable:4996) // _WINSOCK_DEPRECATED_NO_WARNINGS typedef struct sockaddr skaddr_t;
#include "win32_slim.h" typedef struct sockaddr_in skaddrin_t;
#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 struct pollfd skpoll_t; typedef struct pollfd skpoll_t;
typedef enum { typedef enum {
SOCK_TCP, SOCK_TCP,
SOCK_UDP, SOCK_UDP,
} sktype_t; } sktype_e;
// == RAW SOCKETS ==========================================
// Initialize sockets, returns true on success // Initialize sockets, returns true on success
bool skInit(void); bool skInit(void);
@ -47,7 +24,7 @@ bool skInit(void);
bool skCleanup(void); bool skCleanup(void);
// Opens a socket, check socket_t with skValid // 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 // Opens a socket using 'protocol', options are
// ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp // ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp
// check socket_t with skValid // check socket_t with skValid
@ -62,12 +39,12 @@ bool skIsValid(socket_t sock);
bool skClose(socket_t sock); bool skClose(socket_t sock);
// Fill out a sk_addrin_t structure with "ip" and "port" // 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 // Associate a local address with a socket
bool skBind(socket_t sock, const char *ip, uint16_t port); bool skBind(socket_t sock, const char *ip, uint16_t port);
// Associate a local address with a socket // 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 // Place a socket in a state in which it is listening for an incoming connection
bool skListen(socket_t sock); bool skListen(socket_t sock);
@ -77,29 +54,29 @@ bool skListenPro(socket_t sock, int backlog);
// Permits an incoming connection attempt on a socket // Permits an incoming connection attempt on a socket
socket_t skAccept(socket_t sock); socket_t skAccept(socket_t sock);
// Permits an incoming connection attempt on a socket // 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 // 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); bool skConnect(socket_t sock, const char *server, unsigned short server_port);
// Connects to a server, returns true on success // 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 // Sends data on a socket, returns true on success
int skSend(socket_t sock, const void *buf, int len); int skSend(socket_t sock, const void *buf, int len);
// Sends data on a socket, returns true on success // Sends data on a socket, returns true on success
int skSendPro(socket_t sock, const void *buf, int len, int flags); int skSendPro(socket_t sock, const void *buf, int len, int flags);
// Sends data to a specific destination // 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 // 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 // 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); 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 // 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); int skReceivePro(socket_t sock, void *buf, int len, int flags);
// Receives a datagram and stores the source address. // 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. // 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 // Wait for an event on some sockets
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout); 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); int skGetError(void);
// Returns a human-readable string from a skGetError // Returns a human-readable string from a skGetError
const char *skGetErrorString(void); 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 "str.h"
#include <string.h> #include "warnings/colla_warn_beg.h"
#include <stdlib.h>
#include <limits.h>
#include <ctype.h>
#include <assert.h>
#include <stdio.h>
#include "arena.h"
#include "format.h"
#include "tracelog.h" #include "tracelog.h"
#include "strstream.h"
#ifdef _WIN32 #if COLLA_WIN
#include "win32_slim.h" #include <stringapiset.h>
#else
#include <iconv.h>
#endif
#ifndef min
#define min(a, b) ((a) < (b) ? (a) : (b))
#endif #endif
// == STR_T ======================================================== // == STR_T ========================================================
str_t strInit(void) { str_t strInit(arena_t *arena, const char *buf) {
return (str_t) { return buf ? strInitLen(arena, buf, strlen(buf)) : (str_t){0};
.buf = NULL, }
.len = 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) { memcpy(out.buf, buf, len);
return cstr ? strFromBuf(cstr, strlen(cstr)) : strInit();
}
str_t strFromView(strview_t view) {
return strFromBuf(view.buf, view.len);
}
str_t strFromBuf(const char *buf, usize len) {
if (!buf) return strInit();
str_t str;
str.len = len;
str.buf = (char *)malloc(len + 1);
memcpy(str.buf, buf, len);
str.buf[len] = '\0';
return str;
}
str_t strFromFmt(const char *fmt, ...) {
str_ostream_t out = ostrInit();
va_list va;
va_start(va, fmt);
ostrPrintfV(&out, fmt, va);
va_end(va);
return ostrAsStr(out);
}
void strFree(str_t ctx) {
free(ctx.buf);
}
str_t strFromWCHAR(const wchar_t *src, usize len) {
if(len == 0) len = wcslen(src);
#ifdef _WIN32
// TODO CP_ACP should be CP_UTF8 but if i put CP_UTF8 it doesn't work??
int result_len = WideCharToMultiByte(
CP_ACP, 0,
src, (int)len,
NULL, 0,
NULL, NULL
);
char *buf = (char *)malloc(result_len + 1);
if(buf) {
WideCharToMultiByte(
CP_ACP, 0,
src, (int)len,
buf, result_len,
NULL, NULL
);
buf[result_len] = '\0';
}
return (str_t) {
.buf = buf,
.len = result_len
};
#else
usize actual_len = len * sizeof(wchar_t);
usize dest_len = len * 6;
char *dest = (char *)malloc(dest_len);
iconv_t cd = iconv_open("UTF-8", "WCHAR_T");
assert(cd);
usize dest_left = dest_len;
char *dest_temp = dest;
char *src_temp = (char*)src;
usize lost = iconv(cd, &src_temp, &actual_len, &dest_temp, &dest_left);
assert(lost != ((usize)-1));
(void)lost;
dest_len -= dest_left;
dest = (char *)realloc(dest, dest_len + 1);
dest[dest_len] = '\0';
iconv_close(cd);
return (str_t){
.buf = dest,
.len = dest_len
};
#endif
}
wchar_t *strToWCHAR(str_t ctx) {
#ifdef _WIN32
UINT codepage = CP_ACP;
int len = MultiByteToWideChar(
codepage, 0,
ctx.buf, (int)ctx.len,
NULL, 0
);
wchar_t *str = (wchar_t *)malloc(sizeof(wchar_t) * (len + 1));
if(!str) return NULL;
len = MultiByteToWideChar(
codepage, 0,
ctx.buf, (int)ctx.len,
str, len
);
str[len] = '\0';
return str;
#else
usize dest_len = ctx.len * sizeof(wchar_t);
char *dest = (char *)malloc(dest_len);
iconv_t cd = iconv_open("WCHAR_T", "UTF-8");
assert(cd);
usize dest_left = dest_len;
char *dest_temp = dest;
char *src_temp = ctx.buf;
usize lost = iconv(cd, &src_temp, &ctx.len, &dest_temp, &dest_left);
assert(lost != ((usize)-1));
(void)lost;
dest_len -= dest_left;
dest = (char *)realloc(dest, dest_len + 1);
dest[dest_len] = '\0';
iconv_close(cd);
return (wchar_t *)dest;
#endif
}
str_t strDup(str_t ctx) {
return strFromBuf(ctx.buf, ctx.len);
}
str_t strMove(str_t *ctx) {
str_t out = *ctx;
ctx->buf = NULL;
ctx->len = 0;
return out; return out;
} }
strview_t strGetView(str_t ctx) { str_t strInitView(arena_t *arena, strview_t view) {
return (strview_t) { return strInitLen(arena, view.buf, view.len);
.buf = ctx.buf,
.len = ctx.len
};
} }
char *strBegin(str_t ctx) { str_t strFmt(arena_t *arena, const char *fmt, ...) {
return ctx.buf; va_list args;
va_start(args, fmt);
str_t out = strFmtv(arena, fmt, args);
va_end(args);
return out;
} }
char *strEnd(str_t ctx) { str_t strFmtv(arena_t *arena, const char *fmt, va_list args) {
return ctx.buf ? ctx.buf + ctx.len : NULL; 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) { str_t strFromWChar(arena_t *arena, const wchar_t *src, usize srclen) {
return ctx.buf ? ctx.buf[ctx.len - 1] : '\0'; 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) { bool strIsEmpty(str_t ctx) {
return ctx.len == 0; return ctx.len == 0 || ctx.buf == NULL;
}
void strSwap(str_t *ctx, str_t *other) {
char *buf = other->buf;
usize len = other->len;
other->buf = ctx->buf;
other->len = ctx->len;
ctx->buf = buf;
ctx->len = len;
} }
void strReplace(str_t *ctx, char from, char to) { void strReplace(str_t *ctx, char from, char to) {
for(usize i = 0; i < ctx->len; ++i) { if (!ctx) return;
if(ctx->buf[i] == from) { char *buf = ctx->buf;
ctx->buf[i] = to; 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) { strview_t strSub(str_t ctx, usize from, usize to) {
if(strIsEmpty(ctx)) return strInit();
if (to > ctx.len) to = ctx.len; if (to > ctx.len) to = ctx.len;
if (from > to) from = to; if (from > to) from = to;
return strFromBuf(ctx.buf + from, to - from); return (strview_t){ ctx.buf + from, to - from };
}
strview_t strSubview(str_t ctx, usize from, usize to) {
if(strIsEmpty(ctx)) return strvInit(NULL);
if (to > ctx.len) to = ctx.len;
if (from > to) from = to;
return strvInitLen(ctx.buf + from, to - from);
} }
void strLower(str_t *ctx) { void strLower(str_t *ctx) {
for(usize i = 0; i < ctx->len; ++i) { char *buf = ctx->buf;
ctx->buf[i] = (char)tolower(ctx->buf[i]); 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) { void strUpper(str_t *ctx) {
for(usize i = 0; i < ctx->len; ++i) { char *buf = ctx->buf;
ctx->buf[i] = (char)toupper(ctx->buf[i]); 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 strToLower(arena_t *arena, str_t ctx) {
str_t str = strDup(ctx); strLower(&ctx);
strUpper(&str); return strDup(arena, ctx);
return str;
} }
str_t strToUpper(arena_t *arena, str_t ctx) {
strUpper(&ctx);
return strDup(arena, ctx);
}
// == STRVIEW_T ==================================================== // == STRVIEW_T ====================================================
strview_t strvInit(const char *cstr) { strview_t strvInit(const char *cstr) {
return strvInitLen(cstr, cstr ? strlen(cstr) : 0); return (strview_t){
} .buf = cstr,
.len = cstr ? strlen(cstr) : 0,
strview_t strvInitStr(str_t str) {
return strvInitLen(str.buf, str.len);
}
strview_t strvInitLen(const char *buf, usize size) {
return (strview_t) {
.buf = buf,
.len = size
}; };
} }
char strvFront(strview_t ctx) { strview_t strvInitLen(const char *buf, usize size) {
return ctx.buf[0]; return (strview_t){
.buf = buf,
.len = size,
};
} }
char strvBack(strview_t ctx) { strview_t strvInitStr(str_t str) {
return ctx.buf[ctx.len - 1]; 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) { 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) { strview_t strvRemovePrefix(strview_t ctx, usize n) {
if (n > ctx.len) n = ctx.len; if (n > ctx.len) n = ctx.len;
return (strview_t){ return (strview_t){
.buf = ctx.buf + n, .buf = ctx.buf + n,
.len = ctx.len - n .len = ctx.len - n,
}; };
} }
@ -293,7 +261,7 @@ strview_t strvRemoveSuffix(strview_t ctx, usize n) {
if (n > ctx.len) n = ctx.len; if (n > ctx.len) n = ctx.len;
return (strview_t){ return (strview_t){
.buf = ctx.buf, .buf = ctx.buf,
.len = ctx.len - n .len = ctx.len - n,
}; };
} }
@ -303,334 +271,115 @@ strview_t strvTrim(strview_t ctx) {
strview_t strvTrimLeft(strview_t ctx) { strview_t strvTrimLeft(strview_t ctx) {
strview_t out = ctx; strview_t out = ctx;
for (usize i = 0; i < ctx.len && isspace(ctx.buf[i]); ++i) { for (usize i = 0; i < ctx.len; ++i) {
++out.buf; char c = ctx.buf[i];
--out.len; if (c != ' ' || c < '\t' || c > '\r') {
break;
}
out.buf++;
out.len--;
} }
return out; return out;
} }
strview_t strvTrimRight(strview_t ctx) { strview_t strvTrimRight(strview_t ctx) {
strview_t out = ctx; strview_t out = ctx;
for (isize i = ctx.len - 1; i >= 0 && isspace(ctx.buf[i]); --i) { for (isize i = ctx.len - 1; i >= 0; --i) {
--out.len; char c = ctx.buf[i];
if (c != ' ' || c < '\t' || c > '\r') {
break;
}
out.len--;
} }
return out; 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) { strview_t strvSub(strview_t ctx, usize from, usize to) {
if (to > ctx.len) to = ctx.len; if (to > ctx.len) to = ctx.len;
if (from > to) from = to; if (from > to) from = to;
return strvInitLen(ctx.buf + from, to - from); return (strview_t){ ctx.buf + from, to - from };
}
int strvCompare(strview_t ctx, strview_t other) {
if(ctx.len < other.len) return -1;
if(ctx.len > other.len) return 1;
return memcmp(ctx.buf, other.buf, ctx.len);
}
int strvICompare(strview_t ctx, strview_t other) {
if(ctx.len < other.len) return -1;
if(ctx.len > other.len) return 1;
for(usize i = 0; i < ctx.len; ++i) {
int a = tolower(ctx.buf[i]);
int b = tolower(other.buf[i]);
if(a != b) return a - b;
}
return 0;
} }
bool strvStartsWith(strview_t ctx, char c) { 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) { bool strvStartsWithView(strview_t ctx, strview_t view) {
if(ctx.len < view.len) return false; return ctx.len >= view.len && memcmp(ctx.buf, view.buf, view.len) == 0;
return memcmp(ctx.buf, view.buf, view.len) == 0;
} }
bool strvEndsWith(strview_t ctx, char c) { 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) { bool strvEndsWithView(strview_t ctx, strview_t view) {
if(ctx.len < view.len) return false; return ctx.len >= view.len && memcmp(ctx.buf + ctx.len, view.buf, view.len) == 0;
return memcmp(ctx.buf + ctx.len - view.len, view.buf, view.len) == 0;
} }
bool strvContains(strview_t ctx, char c) { bool strvContains(strview_t ctx, char c) {
for(usize i = 0; i < ctx.len; ++i) { for(usize i = 0; i < ctx.len; ++i) {
if(ctx.buf[i] == c) return true; if(ctx.buf[i] == c) {
return true;
}
} }
return false; return false;
} }
bool strvContainsView(strview_t ctx, strview_t view) { 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; usize end = ctx.len - view.len;
for(usize i = 0; i < end; ++i) { for (usize i = 0; i < end; ++i) {
if(memcmp(ctx.buf + i, view.buf, view.len) == 0) return true; if (memcmp(ctx.buf + i, view.buf, view.len) == 0) {
return true;
}
} }
return false; return false;
} }
usize strvFind(strview_t ctx, char c, usize from) { usize strvFind(strview_t ctx, char c, usize from) {
for(usize i = from; i < ctx.len; ++i) { for (usize i = from; i < ctx.len; ++i) {
if(ctx.buf[i] == c) return i; if (ctx.buf[i] == c) {
}
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])) {
return i; return i;
} }
} }
return SIZE_MAX; return STR_NONE;
} }
usize strvFindLastNot(strview_t ctx, char c, usize from) { usize strvFindView(strview_t ctx, strview_t view, usize from) {
if(from >= ctx.len) { if (ctx.len < view.len) return STR_NONE;
from = ctx.len - 1; usize end = ctx.len - view.len;
} for (usize i = 0; i < end; ++i) {
if (memcmp(ctx.buf + i, view.buf, view.len) == 0) {
const char *buf = ctx.buf + from; return i;
for(; buf >= ctx.buf; --buf) {
if(*buf != c) {
return buf - ctx.buf;
} }
} }
return STR_NONE;
return SIZE_MAX;
} }
usize strvFindLastNotOf(strview_t ctx, strview_t view, usize from) { usize strvRFind(strview_t ctx, char c, usize from_right) {
if(from >= ctx.len) { if (from_right > ctx.len) from_right = ctx.len;
from = ctx.len - 1; isize end = (isize)(ctx.len - from_right);
} for (isize i = end; i >= 0; --i) {
if (ctx.buf[i] == c) {
const char *buf = ctx.buf + from; return (usize)i;
for(; buf >= ctx.buf; --buf) {
if(!strvContains(view, *buf)) {
return buf - ctx.buf;
} }
} }
return STR_NONE;
return SIZE_MAX;
} }
#ifdef STR_TESTING usize strvRFindView(strview_t ctx, strview_t view, usize from_right) {
#include <stdio.h> if (from_right > ctx.len) from_right = ctx.len;
#include "tracelog.h" isize end = (isize)(ctx.len - from_right);
if (end < view.len) return STR_NONE;
void strTest(void) { for (isize i = end - view.len; i >= 0; --i) {
str_t s; if (memcmp(ctx.buf + i, view.buf, view.len) == 0) {
debug("== testing init ================="); return (usize)i;
{
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 ============"); return STR_NONE;
{
s = strInitStr("hello world");
char *beg = strBegin(&s);
char *end = strEnd(&s);
printf("[ ");
for(; beg < end; ++beg) {
printf("%c ", *beg);
}
printf("]\n");
strFree(&s);
}
debug("== testing back/isempty =========");
{
s = strInitStr("hello world");
printf("[ ");
while(!strIsEmpty(&s)) {
printf("%c ", strBack(&s));
strPop(&s);
}
printf("]\n");
strFree(&s);
}
debug("== testing append ===============");
{
s = strInitStr("hello ");
printf("\"%s\" %zu\n", s.buf, s.len);
strAppend(&s, "world");
printf("\"%s\" %zu\n", s.buf, s.len);
strAppendView(&s, strvInit(", how is it "));
printf("\"%s\" %zu\n", s.buf, s.len);
uint8 buf[] = { 'g', 'o', 'i', 'n', 'g' };
strAppendBuf(&s, (char*)buf, sizeof(buf));
printf("\"%s\" %zu\n", s.buf, s.len);
strAppendChars(&s, '?', 2);
printf("\"%s\" %zu\n", s.buf, s.len);
strFree(&s);
}
debug("== testing push/pop =============");
{
s = strInit();
str_t s2 = strInitStr("hello world");
printf("%-14s %-14s\n", "s", "s2");
printf("----------------------------\n");
while(!strIsEmpty(&s2)) {
printf("%-14s %-14s\n", s.buf, s2.buf);
strPush(&s, strPop(&s2));
}
printf("%-14s %-14s\n", s.buf, s2.buf);
strFree(&s);
strFree(&s2);
}
debug("== testing swap =================");
{
s = strInitStr("hello");
str_t s2 = strInitStr("world");
printf("%-8s %-8s\n", "s", "s2");
printf("----------------\n");
printf("%-8s %-8s\n", s.buf, s2.buf);
strSwap(&s, &s2);
printf("%-8s %-8s\n", s.buf, s2.buf);
strFree(&s);
strFree(&s2);
}
debug("== testing substr ===============");
{
s = strInitStr("hello world");
printf("s: %s\n", s.buf);
printf("-- string\n");
str_t s2 = strSubstr(&s, 0, 5);
printf("0..5: \"%s\"\n", s2.buf);
strFree(&s2);
s2 = strSubstr(&s, 5, SIZE_MAX);
printf("6..SIZE_MAX: \"%s\"\n", s2.buf);
strFree(&s2);
printf("-- view\n");
strview_t v = strSubview(&s, 0, 5);
printf("0..5: \"%.*s\"\n", (int)v.len, v.buf);
v = strSubview(&s, 5, SIZE_MAX);
printf("6..SIZE_MAX: \"%.*s\"\n", (int)v.len, v.buf);
strFree(&s);
}
strFree(&s);
} }
#endif
#include "warnings/colla_warn_beg.h"
#undef CP_UTF8
#undef ERROR_NO_UNICODE_TRANSLATION

View file

@ -1,19 +1,15 @@
#pragma once #pragma once
#ifdef __cplusplus #include <stdarg.h> // va_list
extern "C" { #include <string.h> // strlen
#endif
#include <stdbool.h>
#include <stddef.h>
#include <limits.h>
#include <wchar.h>
#include "collatypes.h" #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; char *buf;
usize len; usize len;
} str_t; } str_t;
@ -25,73 +21,77 @@ typedef struct {
// == STR_T ======================================================== // == STR_T ========================================================
str_t strInit(void); #define str__1(arena, x) \
str_t strFromStr(const char *cstr); _Generic((x), \
str_t strFromView(strview_t view); const char *: strInit, \
str_t strFromBuf(const char *buf, usize len); char *: strInit, \
str_t strFromFmt(const char *fmt, ...); strview_t: strInitView \
)(arena, x)
str_t strFromWCHAR(const wchar_t *src, usize len); #define str__2(arena, cstr, clen) strInitLen(arena, cstr, clen)
wchar_t *strToWCHAR(str_t ctx); #define str__impl(_1, _2, n, ...) str__##n
void strFree(str_t ctx); // either:
str_t strDup(str_t ctx); // arena_t arena, [const] char *cstr, [usize len]
str_t strMove(str_t *ctx); // 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); str_t strFromWChar(arena_t *arena, const wchar_t *src, usize srclen);
char *strEnd(str_t ctx);
char strBack(str_t ctx);
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); 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 // if len == SIZE_MAX, copies until end
str_t strSubstr(str_t ctx, usize from, usize to); strview_t strSub(str_t ctx, usize from, usize to);
// if len == SIZE_MAX, returns until end
strview_t strSubview(str_t ctx, usize from, usize to);
void strLower(str_t *ctx); void strLower(str_t *ctx);
str_t strToLower(str_t ctx);
void strUpper(str_t *ctx); void strUpper(str_t *ctx);
str_t strToUpper(str_t ctx);
#ifdef STR_TESTING str_t strToLower(arena_t *arena, str_t ctx);
void strTest(void); str_t strToUpper(arena_t *arena, str_t ctx);
#endif
// == STRVIEW_T ==================================================== // == STRVIEW_T ====================================================
strview_t strvInit(const char *cstr); #define strv__1(x) \
strview_t strvInitStr(str_t str); _Generic((x), \
strview_t strvInitLen(const char *buf, usize size); char *: strvInit, \
const char *: strvInit, \
str_t: strvInitStr \
)(x)
char strvFront(strview_t ctx); #define strv__2(cstr, clen) strvInitLen(cstr, clen)
char strvBack(strview_t ctx); #define strv__impl(_1, _2, n, ...) strv__##n
const char *strvBegin(strview_t ctx);
const char *strvEnd(strview_t ctx); #define strv(...) strv__impl(__VA_ARGS__, 2, 1, 0)(__VA_ARGS__)
// move view forward by n characters
strview_t strvRemovePrefix(strview_t ctx, usize n); strview_t strvInit(const char *cstr);
// move view backwards by n characters strview_t strvInitLen(const char *buf, usize size);
strview_t strvRemoveSuffix(strview_t ctx, usize n); strview_t strvInitStr(str_t str);
// removes whitespace from the beginning and the end
strview_t strvTrim(strview_t ctx);
// removes whitespace from the beginning
strview_t strvTrimLeft(strview_t ctx);
// removes whitespace from the end
strview_t strvTrimRight(strview_t ctx);
bool strvIsEmpty(strview_t ctx); 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); wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen);
str_t strvCopyN(strview_t ctx, usize count, usize from); TCHAR *strvToTChar(arena_t *arena, strview_t str);
usize strvCopyBuf(strview_t ctx, char *buf, usize len, usize from);
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); 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 strvStartsWith(strview_t ctx, char c);
bool strvStartsWithView(strview_t ctx, strview_t view); 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 strvFind(strview_t ctx, char c, usize from);
usize strvFindView(strview_t ctx, strview_t view, usize from); usize strvFindView(strview_t ctx, strview_t view, usize from);
usize strvRFind(strview_t ctx, char c, usize from); usize strvRFind(strview_t ctx, char c, usize from_right);
usize strvRFindView(strview_t ctx, strview_t view, usize from); usize strvRFindView(strview_t ctx, strview_t view, usize from_right);
// 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

View file

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

View file

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

View file

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

View file

@ -12,7 +12,7 @@ extern "C" {
* -> TLOG_MUTEX: use a mutex on every traceLog call * -> TLOG_MUTEX: use a mutex on every traceLog call
*/ */
#include <stdbool.h> #include "collatypes.h"
#include <stdarg.h> #include <stdarg.h>
enum { 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" { extern "C" {
#endif #endif
#include <stdbool.h> // bool
#include <string.h> // memset #include <string.h> // memset
#include "collatypes.h"
#include "tracelog.h" // fatal #include "tracelog.h" // fatal
// heavily inspired by https://gist.github.com/Enichan/5f01140530ff0133fde19c9549a2a973 // heavily inspired by https://gist.github.com/Enichan/5f01140530ff0133fde19c9549a2a973

View file

@ -1,7 +1,7 @@
#include "dir.h" #include "dir.h"
#include "tracelog.h" #include "tracelog.h"
#ifdef _WIN32 #if COLLA_WIN
#include "win32_slim.h" #include "win32_slim.h"
#include <stdlib.h> #include <stdlib.h>
#include <assert.h> #include <assert.h>
@ -19,7 +19,7 @@ static dir_entry_t _fillDirEntry(WIN32_FIND_DATAW *data) {
.type = .type =
data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ? data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ?
FS_TYPE_DIR : FS_TYPE_FILE, FS_TYPE_DIR : FS_TYPE_FILE,
.name = strFromWCHAR(data->cFileName, 0) .name = strFromWChar(data->cFileName, 0)
}; };
} }
@ -52,7 +52,7 @@ static void _getNext(_dir_internal_t *ctx) {
dir_t dirOpen(const char *path) { dir_t dirOpen(const char *path) {
DWORD n = GetFullPathName(path, 0, NULL, NULL); DWORD n = GetFullPathName(path, 0, NULL, NULL);
str_ostream_t out = ostrInitLen(n + 3); outstream_t out = ostrInitLen(n + 3);
n = GetFullPathName(path, n, out.buf, NULL); n = GetFullPathName(path, n, out.buf, NULL);
assert(n > 0); assert(n > 0);
out.len += n; out.len += n;
@ -196,7 +196,7 @@ bool dirRemove(const char *path) {
} }
} }
dirClose(dir); dirClose(dir);
#ifdef _WIN32 #if COLLA_WIN
return RemoveDirectoryA(path); return RemoveDirectoryA(path);
#else #else
return rmdir(path) == 0; return rmdir(path) == 0;

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
#include "jobpool.h" #include "jobpool.h"
#include <vec.h> #include "vec.h"
typedef struct { typedef struct {
cthread_func_t func; cthread_func_t func;

View file

@ -1,7 +1,7 @@
#pragma once #pragma once
#include <collatypes.h> #include "collatypes.h"
#include <cthreads.h> #include "cthreads.h"
typedef void *jobpool_t; typedef void *jobpool_t;

View file

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

View file

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