use arena everywhere + bunch of cool new stuff
This commit is contained in:
parent
8fdecd89a5
commit
ae59f269c2
51 changed files with 5443 additions and 7233 deletions
|
|
@ -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
1
build.bat
Normal file
|
|
@ -0,0 +1 @@
|
|||
zig cc -o test.exe main.c -lWs2_32 -lWininet -Wall -Wpedantic -Wno-newline-eof
|
||||
17
build.c
Normal file
17
build.c
Normal 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"
|
||||
198
colla/arena.c
Normal file
198
colla/arena.c
Normal 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
57
colla/arena.h
Normal 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
100
colla/base64.c
Normal 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
9
colla/base64.h
Normal 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
79
colla/colladefines.h
Normal 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
|
||||
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "colladefines.h"
|
||||
|
||||
typedef unsigned char uchar;
|
||||
typedef unsigned short ushort;
|
||||
|
|
@ -19,3 +22,16 @@ typedef int64_t int64;
|
|||
|
||||
typedef size_t usize;
|
||||
typedef ptrdiff_t isize;
|
||||
|
||||
typedef uint8 byte;
|
||||
|
||||
typedef struct {
|
||||
uint8 *data;
|
||||
usize len;
|
||||
} buffer_t;
|
||||
|
||||
#if COLLA_WIN && defined(UNICODE)
|
||||
typedef wchar_t TCHAR;
|
||||
#else
|
||||
typedef char TCHAR;
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,13 +1,23 @@
|
|||
#include "cthreads.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct {
|
||||
cthread_func_t func;
|
||||
void *arg;
|
||||
} _thr_internal_t;
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "win32_slim.h"
|
||||
#include <stdlib.h>
|
||||
#if COLLA_WIN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <handleapi.h>
|
||||
#include <processthreadsapi.h>
|
||||
#include <synchapi.h>
|
||||
|
||||
#undef INFINITE
|
||||
#undef WAIT_FAILED
|
||||
// couple of defines to avoid including windows.h
|
||||
#define INFINITE 0xFFFFFFFF // Infinite timeout
|
||||
#define WAIT_FAILED ((DWORD)0xFFFFFFFF)
|
||||
|
||||
// == THREAD ===========================================
|
||||
|
||||
|
|
@ -21,7 +31,7 @@ static DWORD _thrFuncInternal(void *arg) {
|
|||
|
||||
cthread_t thrCreate(cthread_func_t func, void *arg) {
|
||||
HANDLE thread = INVALID_HANDLE_VALUE;
|
||||
_thr_internal_t *params = malloc(sizeof(_thr_internal_t));
|
||||
_thr_internal_t *params = calloc(1, sizeof(_thr_internal_t));
|
||||
|
||||
if(params) {
|
||||
params->func = func;
|
||||
|
|
@ -68,7 +78,7 @@ bool thrJoin(cthread_t ctx, int *code) {
|
|||
// == MUTEX ============================================
|
||||
|
||||
cmutex_t mtxInit(void) {
|
||||
CRITICAL_SECTION *crit_sec = malloc(sizeof(CRITICAL_SECTION));
|
||||
CRITICAL_SECTION *crit_sec = calloc(1, sizeof(CRITICAL_SECTION));
|
||||
if(crit_sec) {
|
||||
InitializeCriticalSection(crit_sec);
|
||||
}
|
||||
|
|
@ -100,10 +110,10 @@ bool mtxUnlock(cmutex_t ctx) {
|
|||
|
||||
// == CONDITION VARIABLE ===============================
|
||||
|
||||
#include <tracelog.h>
|
||||
#include "tracelog.h"
|
||||
|
||||
condvar_t condInit(void) {
|
||||
CONDITION_VARIABLE *cond = malloc(sizeof(CONDITION_VARIABLE));
|
||||
CONDITION_VARIABLE *cond = calloc(1, sizeof(CONDITION_VARIABLE));
|
||||
InitializeConditionVariable(cond);
|
||||
return (condvar_t)cond;
|
||||
}
|
||||
|
|
@ -121,7 +131,7 @@ void condWakeAll(condvar_t cond) {
|
|||
}
|
||||
|
||||
void condWait(condvar_t cond, cmutex_t mtx) {
|
||||
BOOL res = SleepConditionVariableCS((CONDITION_VARIABLE *)cond, (CRITICAL_SECTION *)mtx, INFINITE);
|
||||
SleepConditionVariableCS((CONDITION_VARIABLE *)cond, (CRITICAL_SECTION *)mtx, INFINITE);
|
||||
}
|
||||
|
||||
void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) {
|
||||
|
|
@ -130,7 +140,6 @@ void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) {
|
|||
|
||||
#else
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/types.h>
|
||||
|
|
@ -150,7 +159,7 @@ static void *_thrFuncInternal(void *arg) {
|
|||
cthread_t thrCreate(cthread_func_t func, void *arg) {
|
||||
pthread_t handle = (pthread_t)NULL;
|
||||
|
||||
_thr_internal_t *params = malloc(sizeof(_thr_internal_t));
|
||||
_thr_internal_t *params = calloc(1, sizeof(_thr_internal_t));
|
||||
|
||||
if(params) {
|
||||
params->func = func;
|
||||
|
|
@ -195,7 +204,7 @@ bool thrJoin(cthread_t ctx, int *code) {
|
|||
// == MUTEX ============================================
|
||||
|
||||
cmutex_t mtxInit(void) {
|
||||
pthread_mutex_t *mutex = malloc(sizeof(pthread_mutex_t));
|
||||
pthread_mutex_t *mutex = calloc(1, sizeof(pthread_mutex_t));
|
||||
|
||||
if(mutex) {
|
||||
if(pthread_mutex_init(mutex, NULL)) {
|
||||
|
|
@ -230,7 +239,7 @@ bool mtxUnlock(cmutex_t ctx) {
|
|||
// == CONDITION VARIABLE ===============================
|
||||
|
||||
condvar_t condInit(void) {
|
||||
pthread_cond_t *cond = malloc(sizeof(pthread_cond_t));
|
||||
pthread_cond_t *cond = calloc(1, sizeof(pthread_cond_t));
|
||||
|
||||
if(cond) {
|
||||
if(pthread_cond_init(cond, NULL)) {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "collatypes.h"
|
||||
|
||||
// == THREAD ===========================================
|
||||
|
||||
|
|
@ -37,31 +32,6 @@ bool mtxLock(cmutex_t ctx);
|
|||
bool mtxTryLock(cmutex_t ctx);
|
||||
bool mtxUnlock(cmutex_t ctx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
// small c++ class to make mutexes easier to use
|
||||
struct lock_t {
|
||||
inline lock_t(cmutex_t mutex)
|
||||
: mutex(mutex) {
|
||||
if (mtxValid(mutex)) {
|
||||
mtxLock(mutex);
|
||||
}
|
||||
}
|
||||
|
||||
inline ~lock_t() {
|
||||
unlock();
|
||||
}
|
||||
|
||||
inline void unlock() {
|
||||
if (mtxValid(mutex)) {
|
||||
mtxUnlock(mutex);
|
||||
}
|
||||
mutex = 0;
|
||||
}
|
||||
|
||||
cmutex_t mutex;
|
||||
};
|
||||
#endif
|
||||
|
||||
// == CONDITION VARIABLE ===============================
|
||||
|
||||
typedef uintptr_t condvar_t;
|
||||
|
|
@ -76,7 +46,3 @@ void condWakeAll(condvar_t cond);
|
|||
|
||||
void condWait(condvar_t cond, cmutex_t mtx);
|
||||
void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
|
|
|||
487
colla/file.c
487
colla/file.c
|
|
@ -1,311 +1,288 @@
|
|||
#include "file.h"
|
||||
|
||||
#include "warnings/colla_warn_beg.h"
|
||||
|
||||
#include "tracelog.h"
|
||||
#include "format.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "win32_slim.h"
|
||||
#include <stdlib.h>
|
||||
#if COLLA_WIN
|
||||
|
||||
static DWORD _toWin32Access(int mode) {
|
||||
if(mode & FILE_READ) return GENERIC_READ;
|
||||
if(mode & FILE_WRITE) return GENERIC_WRITE;
|
||||
if(mode & FILE_BOTH) return GENERIC_READ | GENERIC_WRITE;
|
||||
fatal("unrecognized access mode: %d", mode);
|
||||
return 0;
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <fileapi.h>
|
||||
#include <handleapi.h>
|
||||
|
||||
#undef FILE_BEGIN
|
||||
#undef FILE_CURRENT
|
||||
#undef FILE_END
|
||||
|
||||
#define FILE_BEGIN 0
|
||||
#define FILE_CURRENT 1
|
||||
#define FILE_END 2
|
||||
|
||||
static DWORD file__mode_to_access(filemode_e mode) {
|
||||
if (mode & FILE_APPEND) return FILE_APPEND_DATA;
|
||||
|
||||
DWORD out = 0;
|
||||
if (mode & FILE_READ) out |= GENERIC_READ;
|
||||
if (mode & FILE_WRITE) out |= GENERIC_WRITE;
|
||||
return out;
|
||||
}
|
||||
|
||||
static DWORD _toWin32Creation(filemode_t mode) {
|
||||
if(mode & FILE_READ) return OPEN_EXISTING;
|
||||
if(mode == (FILE_WRITE | FILE_CLEAR)) return CREATE_ALWAYS;
|
||||
if(mode & FILE_WRITE) return OPEN_ALWAYS;
|
||||
if(mode & FILE_BOTH) return OPEN_ALWAYS;
|
||||
fatal("unrecognized creation mode: %d", mode);
|
||||
return 0;
|
||||
static DWORD file__mode_to_creation(filemode_e mode) {
|
||||
if (mode == FILE_READ) return OPEN_EXISTING;
|
||||
if (mode == FILE_WRITE) return CREATE_ALWAYS;
|
||||
return OPEN_ALWAYS;
|
||||
}
|
||||
|
||||
bool fileExists(const char *fname) {
|
||||
return GetFileAttributesA(fname) != INVALID_FILE_ATTRIBUTES;
|
||||
bool fileExists(const char *name) {
|
||||
return GetFileAttributesA(name) != INVALID_FILE_ATTRIBUTES;
|
||||
}
|
||||
|
||||
file_t fileOpen(const char *fname, filemode_t mode) {
|
||||
return (file_t)CreateFileA(
|
||||
fname,
|
||||
_toWin32Access(mode),
|
||||
file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode) {
|
||||
TCHAR long_path_prefix[] = TEXT("\\\\?\\");
|
||||
const usize prefix_len = arrlen(long_path_prefix) - 1;
|
||||
|
||||
TCHAR *rel_path = strvToTChar(&scratch, name);
|
||||
DWORD pathlen = GetFullPathName(rel_path, 0, NULL, NULL);
|
||||
|
||||
TCHAR *full_path = alloc(&scratch, TCHAR, pathlen + prefix_len + 1);
|
||||
memcpy(full_path, long_path_prefix, prefix_len * sizeof(TCHAR));
|
||||
|
||||
GetFullPathName(rel_path, pathlen + 1, full_path + prefix_len, NULL);
|
||||
|
||||
HANDLE handle = CreateFile(
|
||||
full_path,
|
||||
file__mode_to_access(mode),
|
||||
0,
|
||||
NULL,
|
||||
_toWin32Creation(mode),
|
||||
file__mode_to_creation(mode),
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
NULL
|
||||
);
|
||||
|
||||
return (file_t){
|
||||
.handle = (uintptr_t)handle,
|
||||
};
|
||||
}
|
||||
|
||||
void fileClose(file_t ctx) {
|
||||
if (ctx) {
|
||||
CloseHandle((HANDLE)ctx);
|
||||
if (!fileIsValid(ctx)) return;
|
||||
CloseHandle((HANDLE)ctx.handle);
|
||||
}
|
||||
|
||||
bool fileIsValid(file_t ctx) {
|
||||
return (HANDLE)ctx.handle != 0 &&
|
||||
(HANDLE)ctx.handle != INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
usize fileRead(file_t ctx, void *buf, usize len) {
|
||||
if (!fileIsValid(ctx)) return 0;
|
||||
DWORD read = 0;
|
||||
ReadFile((HANDLE)ctx.handle, buf, len, &read, NULL);
|
||||
return (usize)read;
|
||||
}
|
||||
|
||||
usize fileWrite(file_t ctx, const void *buf, usize len) {
|
||||
if (!fileIsValid(ctx)) return 0;
|
||||
DWORD written = 0;
|
||||
WriteFile((HANDLE)ctx.handle, buf, len, &written, NULL);
|
||||
return (usize)written;
|
||||
}
|
||||
|
||||
bool fileSeekEnd(file_t ctx) {
|
||||
if (!fileIsValid(ctx)) return false;
|
||||
DWORD result = SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_END);
|
||||
return result != INVALID_SET_FILE_POINTER;
|
||||
}
|
||||
|
||||
void fileRewind(file_t ctx) {
|
||||
if (!fileIsValid(ctx)) return;
|
||||
SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_BEGIN);
|
||||
}
|
||||
|
||||
usize fileTell(file_t ctx) {
|
||||
if (!fileIsValid(ctx)) return 0;
|
||||
LARGE_INTEGER tell = {0};
|
||||
BOOL result = SetFilePointerEx((HANDLE)ctx.handle, (LARGE_INTEGER){0}, &tell, FILE_CURRENT);
|
||||
return result == TRUE ? (usize)tell.QuadPart : 0;
|
||||
}
|
||||
|
||||
usize fileSize(file_t ctx) {
|
||||
if (!fileIsValid(ctx)) return 0;
|
||||
LARGE_INTEGER size = {0};
|
||||
BOOL result = GetFileSizeEx((HANDLE)ctx.handle, &size);
|
||||
return result == TRUE ? (usize)size.QuadPart : 0;
|
||||
}
|
||||
|
||||
uint64 fileGetTimeFP(file_t ctx) {
|
||||
if (!fileIsValid(ctx)) return 0;
|
||||
FILETIME time = {0};
|
||||
GetFileTime((HANDLE)ctx.handle, NULL, NULL, &time);
|
||||
ULARGE_INTEGER utime = {
|
||||
.HighPart = time.dwHighDateTime,
|
||||
.LowPart = time.dwLowDateTime,
|
||||
};
|
||||
return (uint64)utime.QuadPart;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static const char *file__mode_to_stdio(filemode_e mode) {
|
||||
if (mode == FILE_READ) return "rb";
|
||||
if (mode == FILE_WRITE) return "wb";
|
||||
if (mode == FILE_APPEND) return "ab";
|
||||
if (mode == (FILE_READ | FILE_WRITE)) return "rb+";
|
||||
|
||||
return "ab+";
|
||||
}
|
||||
|
||||
bool fileExists(const char *name) {
|
||||
FILE *fp = fopen(name, "rb");
|
||||
bool exists = fp != NULL;
|
||||
fclose(fp);
|
||||
return exists;
|
||||
}
|
||||
|
||||
file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode) {
|
||||
str_t filename = str(&scratch, name);
|
||||
return (file_t) {
|
||||
.handle = (uintptr_t)fopen(filename.buf, file__mode_to_stdio(mode))
|
||||
};
|
||||
}
|
||||
|
||||
void fileClose(file_t ctx) {
|
||||
FILE *fp = (FILE *)ctx.handle;
|
||||
if (fp) {
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
bool fileIsValid(file_t ctx) {
|
||||
return (HANDLE)ctx != INVALID_HANDLE_VALUE;
|
||||
return (FILE *)ctx.handle != NULL;
|
||||
}
|
||||
|
||||
usize fileRead(file_t ctx, void *buf, usize len) {
|
||||
if (!fileIsValid(ctx)) return 0;
|
||||
return fread(buf, 1, len, (FILE *)ctx.handle);
|
||||
}
|
||||
|
||||
usize fileWrite(file_t ctx, const void *buf, usize len) {
|
||||
if (!fileIsValid(ctx)) return 0;
|
||||
return fwrite(buf, 1, len, (FILE *)ctx.handle);
|
||||
}
|
||||
|
||||
bool fileSeekEnd(file_t ctx) {
|
||||
if (!fileIsValid(ctx)) return false;
|
||||
return fseek((FILE *)ctx.handle, 0, SEEK_END) == 0;
|
||||
}
|
||||
|
||||
void fileRewind(file_t ctx) {
|
||||
if (!fileIsValid(ctx)) return;
|
||||
return fseek((FILE *)ctx.handle, 0, SEEK_SET) == 0;
|
||||
}
|
||||
|
||||
usize fileTell(file_t ctx) {
|
||||
if (!fileIsValid(ctx)) return 0;
|
||||
return ftell((FILE *)ctx.handle);
|
||||
}
|
||||
|
||||
usize fileSize(file_t ctx) {
|
||||
if (!fileIsValid(ctx)) return 0;
|
||||
FILE *fp = (FILE *)ctx.handle;
|
||||
fseek(fp, 0, SEEK_END);
|
||||
long len = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
return (usize)len;
|
||||
}
|
||||
|
||||
uint64 fileGetTimeFP(file_t ctx) {
|
||||
#if COLLA_LIN
|
||||
#else
|
||||
fatal("fileGetTime not implemented yet outside of linux and windows");
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool filePutc(file_t ctx, char c) {
|
||||
return fileWrite(ctx, &c, 1) == 1;
|
||||
}
|
||||
|
||||
bool filePuts(file_t ctx, const char *str) {
|
||||
usize len = strlen(str);
|
||||
return fileWrite(ctx, str, len) == len;
|
||||
bool filePuts(file_t ctx, strview_t v) {
|
||||
return fileWrite(ctx, v.buf, v.len) == v.len;
|
||||
}
|
||||
|
||||
bool filePutstr(file_t ctx, str_t str) {
|
||||
return fileWrite(ctx, str.buf, str.len) == str.len;
|
||||
bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
bool result = filePrintfv(scratch, ctx, fmt, args);
|
||||
va_end(args);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool filePutview(file_t ctx, strview_t view) {
|
||||
return fileWrite(ctx, view.buf, view.len) == view.len;
|
||||
bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args) {
|
||||
str_t string = strFmtv(&scratch, fmt, args);
|
||||
return fileWrite(ctx, string.buf, string.len) == string.len;
|
||||
}
|
||||
|
||||
usize fileRead(file_t ctx, void *buf, usize len) {
|
||||
DWORD bytes_read = 0;
|
||||
BOOL result = ReadFile((HANDLE)ctx, buf, (DWORD)len, &bytes_read, NULL);
|
||||
return result == TRUE ? (usize)bytes_read : 0;
|
||||
buffer_t fileReadWhole(arena_t *arena, arena_t scratch, strview_t name) {
|
||||
return fileReadWholeFP(arena, fileOpen(scratch, name, FILE_READ));
|
||||
}
|
||||
|
||||
usize fileWrite(file_t ctx, const void *buf, usize len) {
|
||||
DWORD bytes_read = 0;
|
||||
BOOL result = WriteFile((HANDLE)ctx, buf, (DWORD)len, &bytes_read, NULL);
|
||||
return result == TRUE ? (usize)bytes_read : 0;
|
||||
}
|
||||
buffer_t fileReadWholeFP(arena_t *arena, file_t ctx) {
|
||||
if (!fileIsValid(ctx)) return (buffer_t){0};
|
||||
buffer_t out = {0};
|
||||
|
||||
bool fileSeekEnd(file_t ctx) {
|
||||
return SetFilePointerEx((HANDLE)ctx, (LARGE_INTEGER){0}, NULL, FILE_END) == TRUE;
|
||||
}
|
||||
out.len = fileSize(ctx);
|
||||
out.data = alloc(arena, uint8, out.len);
|
||||
usize read = fileRead(ctx, out.data, out.len);
|
||||
|
||||
void fileRewind(file_t ctx) {
|
||||
SetFilePointerEx((HANDLE)ctx, (LARGE_INTEGER){0}, NULL, FILE_BEGIN);
|
||||
}
|
||||
|
||||
uint64 fileTell(file_t ctx) {
|
||||
LARGE_INTEGER tell;
|
||||
BOOL result = SetFilePointerEx((HANDLE)ctx, (LARGE_INTEGER){0}, &tell, FILE_CURRENT);
|
||||
return result == TRUE ? (uint64)tell.QuadPart : 0;
|
||||
}
|
||||
|
||||
uint64 fileGetTime(file_t ctx) {
|
||||
uint64 fp_time = 0;
|
||||
GetFileTime((HANDLE)ctx, NULL, NULL, (FILETIME *)&fp_time);
|
||||
return fp_time;
|
||||
}
|
||||
|
||||
#else
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
const char *_toStdioMode(filemode_t mode) {
|
||||
switch(mode) {
|
||||
case FILE_READ: return "rb";
|
||||
case FILE_BOTH: return "r+b";
|
||||
case FILE_WRITE: return "wb";
|
||||
default: fatal("mode not recognized: %d", mode); return "";
|
||||
}
|
||||
}
|
||||
|
||||
bool fileExists(const char *fname) {
|
||||
return access(fname, F_OK) == 0;
|
||||
}
|
||||
|
||||
file_t fileOpen(const char *fname, filemode_t mode) {
|
||||
return (file_t)(void*) fopen(fname, _toStdioMode(mode));
|
||||
}
|
||||
|
||||
void fileClose(file_t ctx) {
|
||||
if(ctx) {
|
||||
fclose((FILE*)ctx);
|
||||
}
|
||||
}
|
||||
|
||||
bool fileIsValid(file_t ctx) {
|
||||
return (FILE *)ctx != NULL;
|
||||
}
|
||||
|
||||
bool filePutc(file_t ctx, char c) {
|
||||
return fputc(c, (FILE*)ctx) == c;
|
||||
}
|
||||
|
||||
bool filePuts(file_t ctx, const char *str) {
|
||||
return fputs(str, (FILE*)ctx) != EOF;
|
||||
}
|
||||
|
||||
bool filePutstr(file_t ctx, str_t str) {
|
||||
return fileWrite(ctx, str.buf, str.len) == str.len;
|
||||
}
|
||||
|
||||
bool filePutview(file_t ctx, strview_t view) {
|
||||
return fileWrite(ctx, view.buf, view.len) == view.len;
|
||||
}
|
||||
|
||||
usize fileRead(file_t ctx, void *buf, usize len) {
|
||||
return fread(buf, 1, len, (FILE*)ctx);
|
||||
}
|
||||
|
||||
usize fileWrite(file_t ctx, const void *buf, usize len) {
|
||||
return fwrite(buf, 1, len, (FILE*)ctx);
|
||||
}
|
||||
|
||||
bool fileSeekEnd(file_t ctx) {
|
||||
return fseek((FILE*)ctx, 0, SEEK_END) == 0;
|
||||
}
|
||||
|
||||
void fileRewind(file_t ctx) {
|
||||
rewind((FILE*)ctx);
|
||||
}
|
||||
|
||||
uint64 fileTell(file_t ctx) {
|
||||
return (uint64)ftell((FILE*)ctx);
|
||||
}
|
||||
|
||||
uint64 fileGetTime(file_t ctx) {
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
static str_t _readWholeInternalStr(file_t ctx) {
|
||||
str_t contents = strInit();
|
||||
uint64 fsize = 0;
|
||||
usize read = 0;
|
||||
|
||||
if(!fileSeekEnd(ctx)) {
|
||||
err("file: couldn't read until end");
|
||||
goto failed;
|
||||
if (read != out.len) {
|
||||
err("fileReadWholeFP: fileRead failed, should be %zu but is %zu", out.len, read);
|
||||
arenaPop(arena, out.len);
|
||||
return (buffer_t){0};
|
||||
}
|
||||
|
||||
fsize = fileTell(ctx);
|
||||
fileRewind(ctx);
|
||||
|
||||
contents.buf = (char *)malloc(fsize + 1);
|
||||
contents.len = fsize;
|
||||
if(!contents.buf) {
|
||||
err("file: couldn't allocate buffer");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
read = fileRead(ctx, contents.buf, fsize);
|
||||
if(read != fsize) {
|
||||
err("file: read wrong amount of bytes: %zu instead of %zu", read, fsize);
|
||||
goto failed_free;
|
||||
}
|
||||
|
||||
contents.buf[contents.len] = '\0';
|
||||
|
||||
failed:
|
||||
return contents;
|
||||
failed_free:
|
||||
strFree(contents);
|
||||
return strInit();
|
||||
return out;
|
||||
}
|
||||
|
||||
static vec(uint8) _readWholeInternalVec(file_t ctx) {
|
||||
vec(uint8) contents = NULL;
|
||||
uint64 fsize = 0;
|
||||
usize read = 0;
|
||||
str_t fileReadWholeStr(arena_t *arena, arena_t scratch, strview_t name) {
|
||||
return fileReadWholeStrFP(arena, fileOpen(scratch, name, FILE_READ));
|
||||
}
|
||||
|
||||
if(!fileSeekEnd(ctx)) {
|
||||
err("file: couldn't read until end");
|
||||
goto failed;
|
||||
str_t fileReadWholeStrFP(arena_t *arena, file_t ctx) {
|
||||
if (!fileIsValid(ctx)) return (str_t){0};
|
||||
|
||||
str_t out = {0};
|
||||
|
||||
out.len = fileSize(ctx);
|
||||
out.buf = alloc(arena, uint8, out.len + 1);
|
||||
usize read = fileRead(ctx, out.buf, out.len);
|
||||
|
||||
if (read != out.len) {
|
||||
err("fileReadWholeFP: fileRead failed, should be %zu but is %zu", out.len, read);
|
||||
arenaPop(arena, out.len + 1);
|
||||
return (str_t){0};
|
||||
}
|
||||
|
||||
fsize = fileTell(ctx);
|
||||
fileRewind(ctx);
|
||||
|
||||
vecReserve(contents, fsize);
|
||||
if(!contents) {
|
||||
err("file: couldn't allocate buffer");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
read = fileRead(ctx, contents, fsize);
|
||||
if(read != fsize) {
|
||||
err("file: read wrong amount of bytes: %zu instead of %zu", read, fsize);
|
||||
goto failed_free;
|
||||
}
|
||||
|
||||
_veclen(contents) = read;
|
||||
|
||||
failed:
|
||||
return contents;
|
||||
failed_free:
|
||||
vecFree(contents);
|
||||
return contents;
|
||||
return out;
|
||||
}
|
||||
|
||||
vec(uint8) fileReadWhole(const char *fname) {
|
||||
file_t fp = fileOpen(fname, FILE_READ);
|
||||
if (!fileIsValid(fp)) return NULL;
|
||||
vec(uint8) contents = fileReadWholeFP(fp);
|
||||
fileClose(fp);
|
||||
return contents;
|
||||
}
|
||||
|
||||
vec(uint8) fileReadWholeFP(file_t ctx) {
|
||||
return _readWholeInternalVec(ctx);
|
||||
}
|
||||
|
||||
str_t fileReadWholeText(const char *fname) {
|
||||
file_t fp = fileOpen(fname, FILE_READ);
|
||||
if(!fileIsValid(fp)) {
|
||||
err("couldn't open file %s", fname);
|
||||
return strInit();
|
||||
}
|
||||
str_t contents = fileReadWholeTextFP(fp);
|
||||
fileClose(fp);
|
||||
return contents;
|
||||
}
|
||||
|
||||
str_t fileReadWholeTextFP(file_t ctx) {
|
||||
return _readWholeInternalStr(ctx);
|
||||
}
|
||||
|
||||
bool fileWriteWhole(const char *fname, filebuf_t data) {
|
||||
file_t fp = fileOpen(fname, FILE_WRITE);
|
||||
bool fileWriteWhole(arena_t scratch, strview_t name, const void *buf, usize len) {
|
||||
file_t fp = fileOpen(scratch, name, FILE_WRITE);
|
||||
if (!fileIsValid(fp)) {
|
||||
err("couldn't open file %s", fname);
|
||||
return false;
|
||||
}
|
||||
bool res = fileWriteWholeFP(fp, data);
|
||||
usize written = fileWrite(fp, buf, len);
|
||||
fileClose(fp);
|
||||
return res;
|
||||
return written == len;
|
||||
}
|
||||
|
||||
bool fileWriteWholeFP(file_t ctx, filebuf_t data) {
|
||||
usize written = fileWrite(ctx, data.buf, data.len);
|
||||
return written == data.len;
|
||||
uint64 fileGetTime(arena_t scratch, strview_t name) {
|
||||
return fileGetTimeFP(fileOpen(scratch, name, FILE_READ));
|
||||
}
|
||||
|
||||
bool fileWriteWholeText(const char *fname, strview_t string) {
|
||||
file_t fp = fileOpen(fname, FILE_WRITE);
|
||||
if (!fileIsValid(fp)) {
|
||||
err("couldn't open file %s", fname);
|
||||
return false;
|
||||
}
|
||||
bool res = fileWriteWholeTextFP(fp, string);
|
||||
fileClose(fp);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool fileWriteWholeTextFP(file_t ctx, strview_t string) {
|
||||
return fileWriteWholeFP(ctx, (filebuf_t){ (uint8 *)string.buf, string.len });
|
||||
}
|
||||
|
||||
uint64 fileGetTimePath(const char *path) {
|
||||
file_t fp = fileOpen(path, FILE_READ);
|
||||
if (!fileIsValid(fp)) {
|
||||
return 0;
|
||||
}
|
||||
uint64 fp_time = fileGetTime(fp);
|
||||
fileClose(fp);
|
||||
return fp_time;
|
||||
}
|
||||
#include "warnings/colla_warn_end.h"
|
||||
|
|
|
|||
57
colla/file.h
57
colla/file.h
|
|
@ -1,38 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "collatypes.h"
|
||||
#include "str.h"
|
||||
#include "vec.h"
|
||||
#include "arena.h"
|
||||
|
||||
typedef enum {
|
||||
FILE_READ = 1 << 0,
|
||||
FILE_WRITE = 1 << 1,
|
||||
FILE_CLEAR = 1 << 2,
|
||||
FILE_BOTH = 1 << 3
|
||||
} filemode_t;
|
||||
|
||||
typedef uintptr_t file_t;
|
||||
FILE_READ = 1 << 0,
|
||||
FILE_WRITE = 1 << 1,
|
||||
FILE_APPEND = 1 << 2,
|
||||
} filemode_e;
|
||||
|
||||
typedef struct {
|
||||
const uint8 *buf;
|
||||
usize len;
|
||||
} filebuf_t;
|
||||
uintptr_t handle;
|
||||
} file_t;
|
||||
|
||||
bool fileExists(const char *fname);
|
||||
bool fileExists(const char *name);
|
||||
|
||||
file_t fileOpen(const char *fname, filemode_t mode);
|
||||
file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode);
|
||||
void fileClose(file_t ctx);
|
||||
|
||||
bool fileIsValid(file_t ctx);
|
||||
|
||||
bool filePutc(file_t ctx, char c);
|
||||
bool filePuts(file_t ctx, const char *str);
|
||||
bool filePutstr(file_t ctx, str_t str);
|
||||
bool filePutview(file_t ctx, strview_t view);
|
||||
bool filePuts(file_t ctx, strview_t v);
|
||||
bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...);
|
||||
bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args);
|
||||
|
||||
usize fileRead(file_t ctx, void *buf, usize len);
|
||||
usize fileWrite(file_t ctx, const void *buf, usize len);
|
||||
|
|
@ -40,23 +34,16 @@ usize fileWrite(file_t ctx, const void *buf, usize len);
|
|||
bool fileSeekEnd(file_t ctx);
|
||||
void fileRewind(file_t ctx);
|
||||
|
||||
uint64 fileTell(file_t ctx);
|
||||
usize fileTell(file_t ctx);
|
||||
usize fileSize(file_t ctx);
|
||||
|
||||
vec(uint8) fileReadWhole(const char *fname);
|
||||
vec(uint8) fileReadWholeFP(file_t ctx);
|
||||
buffer_t fileReadWhole(arena_t *arena, arena_t scratch, strview_t name);
|
||||
buffer_t fileReadWholeFP(arena_t *arena, file_t ctx);
|
||||
|
||||
str_t fileReadWholeText(const char *fname);
|
||||
str_t fileReadWholeTextFP(file_t ctx);
|
||||
str_t fileReadWholeStr(arena_t *arena, arena_t scratch, strview_t name);
|
||||
str_t fileReadWholeStrFP(arena_t *arena, file_t ctx);
|
||||
|
||||
bool fileWriteWhole(const char *fname, filebuf_t data);
|
||||
bool fileWriteWholeFP(file_t ctx, filebuf_t data);
|
||||
bool fileWriteWhole(arena_t scratch, strview_t name, const void *buf, usize len);
|
||||
|
||||
bool fileWriteWholeText(const char *fname, strview_t string);
|
||||
bool fileWriteWholeTextFP(file_t ctx, strview_t string);
|
||||
|
||||
uint64 fileGetTime(file_t ctx);
|
||||
uint64 fileGetTimePath(const char *path);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
uint64 fileGetTime(arena_t scratch, strview_t name);
|
||||
uint64 fileGetTimeFP(file_t ctx);
|
||||
60
colla/format.c
Normal file
60
colla/format.c
Normal 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
11
colla/format.h
Normal 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);
|
||||
763
colla/http.c
763
colla/http.c
|
|
@ -1,474 +1,507 @@
|
|||
#include "http.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "warnings/colla_warn_beg.h"
|
||||
|
||||
// #include "os.h"
|
||||
#include "arena.h"
|
||||
#include "strstream.h"
|
||||
#include "format.h"
|
||||
#include "socket.h"
|
||||
#include "tracelog.h"
|
||||
|
||||
#include "vec.h"
|
||||
#if COLLA_WIN
|
||||
#if COLLA_CMT_LIB
|
||||
#pragma comment(lib, "Wininet")
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#define stricmp _stricmp
|
||||
#else
|
||||
#include <strings.h> // strcasecmp
|
||||
#define stricmp strcasecmp
|
||||
#include <windows.h>
|
||||
#include <wininet.h>
|
||||
#endif
|
||||
|
||||
// == INTERNAL ================================================================
|
||||
static const TCHAR *https__get_method_str(http_method_e method);
|
||||
|
||||
static void _setField(vec(http_field_t) *fields_vec, const char *key, const char *value) {
|
||||
vec(http_field_t) fields = *fields_vec;
|
||||
|
||||
for (uint32 i = 0; i < vecLen(fields); ++i) {
|
||||
if (stricmp(fields[i].key, key) == 0) {
|
||||
char **curval = &fields[i].value;
|
||||
usize curlen = strlen(*curval);
|
||||
usize newlen = strlen(value);
|
||||
if(newlen > curlen) {
|
||||
*curval = (char *)realloc(*curval, newlen + 1);
|
||||
}
|
||||
memcpy(*curval, value, newlen);
|
||||
(*curval)[newlen] = '\0';
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise, add it to the list
|
||||
http_field_t field;
|
||||
usize klen = strlen(key);
|
||||
usize vlen = strlen(value);
|
||||
field.key = (char *)malloc(klen + 1);
|
||||
field.value = (char *)malloc(vlen + 1);
|
||||
memcpy(field.key, key, klen);
|
||||
memcpy(field.value, value, vlen);
|
||||
field.key[klen] = field.value[vlen] = '\0';
|
||||
|
||||
vecAppend(*fields_vec, field);
|
||||
}
|
||||
|
||||
static void _parseFields(vec(http_field_t) *fields, str_istream_t *in) {
|
||||
strview_t line;
|
||||
static http_header_t *http__parse_headers(arena_t *arena, instream_t *in) {
|
||||
http_header_t *head = NULL;
|
||||
strview_t line = (strview_t){0};
|
||||
|
||||
do {
|
||||
line = istrGetview(in, '\r');
|
||||
line = istrGetView(in, '\r');
|
||||
|
||||
usize pos = strvFind(line, ':', 0);
|
||||
if(pos != STRV_NOT_FOUND) {
|
||||
strview_t key = strvSub(line, 0, pos);
|
||||
strview_t value = strvSub(line, pos + 2, SIZE_MAX);
|
||||
if (pos != STR_NONE) {
|
||||
http_header_t *h = alloc(arena, http_header_t);
|
||||
|
||||
char *key_str = NULL;
|
||||
char *value_str = NULL;
|
||||
h->key = strvSub(line, 0, pos);
|
||||
h->value = strvSub(line, pos + 2, SIZE_MAX);
|
||||
|
||||
key_str = strvCopy(key).buf;
|
||||
value_str = strvCopy(value).buf;
|
||||
|
||||
_setField(fields, key_str, value_str);
|
||||
|
||||
free(key_str);
|
||||
free(value_str);
|
||||
h->next = head;
|
||||
head = h;
|
||||
}
|
||||
|
||||
istrSkip(in, 2); // skip \r\n
|
||||
} while(line.len > 2);
|
||||
} while (line.len > 2); // while line != "\r\n"
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
// == HTTP STATUS =============================================================
|
||||
|
||||
const char *httpGetStatusString(resstatus_t status) {
|
||||
const char *httpGetStatusString(int status) {
|
||||
switch (status) {
|
||||
case STATUS_OK: return "OK";
|
||||
case STATUS_CREATED: return "CREATED";
|
||||
case STATUS_ACCEPTED: return "ACCEPTED";
|
||||
case STATUS_NO_CONTENT: return "NO CONTENT";
|
||||
case STATUS_RESET_CONTENT: return "RESET CONTENT";
|
||||
case STATUS_PARTIAL_CONTENT: return "PARTIAL CONTENT";
|
||||
case STATUS_MULTIPLE_CHOICES: return "MULTIPLE CHOICES";
|
||||
case STATUS_MOVED_PERMANENTLY: return "MOVED PERMANENTLY";
|
||||
case STATUS_MOVED_TEMPORARILY: return "MOVED TEMPORARILY";
|
||||
case STATUS_NOT_MODIFIED: return "NOT MODIFIED";
|
||||
case STATUS_BAD_REQUEST: return "BAD REQUEST";
|
||||
case STATUS_UNAUTHORIZED: return "UNAUTHORIZED";
|
||||
case STATUS_FORBIDDEN: return "FORBIDDEN";
|
||||
case STATUS_NOT_FOUND: return "NOT FOUND";
|
||||
case STATUS_RANGE_NOT_SATISFIABLE: return "RANGE NOT SATISFIABLE";
|
||||
case STATUS_INTERNAL_SERVER_ERROR: return "INTERNAL SERVER_ERROR";
|
||||
case STATUS_NOT_IMPLEMENTED: return "NOT IMPLEMENTED";
|
||||
case STATUS_BAD_GATEWAY: return "BAD GATEWAY";
|
||||
case STATUS_SERVICE_NOT_AVAILABLE: return "SERVICE NOT AVAILABLE";
|
||||
case STATUS_GATEWAY_TIMEOUT: return "GATEWAY TIMEOUT";
|
||||
case STATUS_VERSION_NOT_SUPPORTED: return "VERSION NOT SUPPORTED";
|
||||
case 200: return "OK";
|
||||
case 201: return "CREATED";
|
||||
case 202: return "ACCEPTED";
|
||||
case 204: return "NO CONTENT";
|
||||
case 205: return "RESET CONTENT";
|
||||
case 206: return "PARTIAL CONTENT";
|
||||
|
||||
case 300: return "MULTIPLE CHOICES";
|
||||
case 301: return "MOVED PERMANENTLY";
|
||||
case 302: return "MOVED TEMPORARILY";
|
||||
case 304: return "NOT MODIFIED";
|
||||
|
||||
case 400: return "BAD REQUEST";
|
||||
case 401: return "UNAUTHORIZED";
|
||||
case 403: return "FORBIDDEN";
|
||||
case 404: return "NOT FOUND";
|
||||
case 407: return "RANGE NOT SATISFIABLE";
|
||||
|
||||
case 500: return "INTERNAL SERVER_ERROR";
|
||||
case 501: return "NOT IMPLEMENTED";
|
||||
case 502: return "BAD GATEWAY";
|
||||
case 503: return "SERVICE NOT AVAILABLE";
|
||||
case 504: return "GATEWAY TIMEOUT";
|
||||
case 505: return "VERSION NOT SUPPORTED";
|
||||
}
|
||||
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
// == HTTP VERSION ============================================================
|
||||
|
||||
int httpVerNumber(http_version_t ver) {
|
||||
return (ver.major * 10) + ver.minor;
|
||||
}
|
||||
|
||||
// == HTTP REQUEST ============================================================
|
||||
http_req_t httpParseReq(arena_t *arena, strview_t request) {
|
||||
http_req_t req = {0};
|
||||
instream_t in = istrInitLen(request.buf, request.len);
|
||||
|
||||
http_request_t reqInit() {
|
||||
http_request_t req = {0};
|
||||
reqSetUri(&req, strvInit(""));
|
||||
req.version = (http_version_t){1, 1};
|
||||
return req;
|
||||
}
|
||||
|
||||
http_request_t reqParse(const char *request) {
|
||||
http_request_t req = {0};
|
||||
str_istream_t in = istrInit(request);
|
||||
|
||||
// get data
|
||||
|
||||
strview_t method = strvTrim(istrGetview(&in, '/'));
|
||||
strview_t method = strvTrim(istrGetView(&in, '/'));
|
||||
istrSkip(&in, 1); // skip /
|
||||
strview_t page = strvTrim(istrGetview(&in, ' '));
|
||||
strview_t http = strvTrim(istrGetview(&in, '\n'));
|
||||
req.url = strvTrim(istrGetView(&in, ' '));
|
||||
strview_t http = strvTrim(istrGetView(&in, '\n'));
|
||||
|
||||
istrSkip(&in, 1); // skip \n
|
||||
|
||||
_parseFields(&req.fields, &in);
|
||||
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
|
||||
const char *methods[] = { "GET", "POST", "HEAD", "PUT", "DELETE" };
|
||||
const int methods_count = sizeof(methods) / sizeof(*methods);
|
||||
|
||||
for (int i = 0; i < methods_count; ++i) {
|
||||
if (strvCompare(method, strvInit(methods[i])) == 0) {
|
||||
req.method = (reqtype_t)i;
|
||||
for (usize i = 0; i < methods_count; ++i) {
|
||||
if (strvEquals(method, methods[i])) {
|
||||
req.method = (http_method_e)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// -- page
|
||||
req.uri = strvCopy(page).buf;
|
||||
|
||||
// -- http
|
||||
in = istrInitLen(http.buf, http.len);
|
||||
istrIgnoreAndSkip(&in, '/'); // skip HTTP/
|
||||
istrGetu8(&in, &req.version.major);
|
||||
istrGetU8(&in, &req.version.major);
|
||||
istrSkip(&in, 1); // skip .
|
||||
istrGetu8(&in, &req.version.minor);
|
||||
|
||||
// -- body
|
||||
req.body = strvCopy(body).buf;
|
||||
istrGetU8(&in, &req.version.minor);
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
void reqFree(http_request_t ctx) {
|
||||
for (http_field_t *it = ctx.fields; it != vecEnd(ctx.fields); ++it) {
|
||||
free(it->key);
|
||||
free(it->value);
|
||||
}
|
||||
vecFree(ctx.fields);
|
||||
free(ctx.uri);
|
||||
free(ctx.body);
|
||||
}
|
||||
http_res_t httpParseRes(arena_t *arena, strview_t response) {
|
||||
http_res_t res = {0};
|
||||
instream_t in = istrInitLen(response.buf, response.len);
|
||||
|
||||
bool reqHasField(http_request_t *ctx, const char *key) {
|
||||
for(uint32 i = 0; i < vecLen(ctx->fields); ++i) {
|
||||
if(stricmp(ctx->fields[i].key, key) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void reqSetField(http_request_t *ctx, const char *key, const char *value) {
|
||||
_setField(&ctx->fields, key, value);
|
||||
}
|
||||
|
||||
void reqSetUri(http_request_t *ctx, strview_t uri) {
|
||||
if (strvIsEmpty(uri)) return;
|
||||
free(ctx->uri);
|
||||
if (uri.buf[0] == '/') {
|
||||
strvRemovePrefix(uri, 1);
|
||||
}
|
||||
ctx->uri = strvCopy(uri).buf;
|
||||
}
|
||||
|
||||
str_ostream_t reqPrepare(http_request_t *ctx) {
|
||||
str_ostream_t out = ostrInitLen(1024);
|
||||
|
||||
const char *method = NULL;
|
||||
switch(ctx->method) {
|
||||
case REQ_GET: method = "GET"; break;
|
||||
case REQ_POST: method = "POST"; break;
|
||||
case REQ_HEAD: method = "HEAD"; break;
|
||||
case REQ_PUT: method = "PUT"; break;
|
||||
case REQ_DELETE: method = "DELETE"; break;
|
||||
default: err("unrecognized method: %d", method); goto error;
|
||||
}
|
||||
|
||||
ostrPrintf(&out, "%s /%s HTTP/%hhu.%hhu\r\n",
|
||||
method, ctx->uri, ctx->version.major, ctx->version.minor
|
||||
);
|
||||
|
||||
for(uint32 i = 0; i < vecLen(ctx->fields); ++i) {
|
||||
ostrPrintf(&out, "%s: %s\r\n", ctx->fields[i].key, ctx->fields[i].value);
|
||||
}
|
||||
|
||||
ostrAppendview(&out, strvInit("\r\n"));
|
||||
if(ctx->body) {
|
||||
ostrAppendview(&out, strvInit(ctx->body));
|
||||
}
|
||||
|
||||
error:
|
||||
return out;
|
||||
}
|
||||
|
||||
str_t reqString(http_request_t *ctx) {
|
||||
str_ostream_t out = reqPrepare(ctx);
|
||||
return ostrAsStr(out);
|
||||
}
|
||||
|
||||
// == HTTP RESPONSE ===========================================================
|
||||
|
||||
http_response_t resParse(const char *data) {
|
||||
http_response_t ctx = {0};
|
||||
str_istream_t in = istrInit(data);
|
||||
|
||||
char hp[5];
|
||||
istrGetstringBuf(&in, hp, 5);
|
||||
if(stricmp(hp, "http") != 0) {
|
||||
err("response doesn't start with 'HTTP', instead with %c%c%c%c", hp[0], hp[1], hp[2], hp[3]);
|
||||
return ctx;
|
||||
strview_t http = istrGetViewLen(&in, 5);
|
||||
if (!strvEquals(http, strv("HTTP"))) {
|
||||
err("response doesn't start with 'HTTP', instead with %v", http);
|
||||
return (http_res_t){0};
|
||||
}
|
||||
istrSkip(&in, 1); // skip /
|
||||
istrGetu8(&in, &ctx.version.major);
|
||||
istrGetU8(&in, &res.version.major);
|
||||
istrSkip(&in, 1); // skip .
|
||||
istrGetu8(&in, &ctx.version.minor);
|
||||
istrGeti32(&in, (int32*)&ctx.status_code);
|
||||
istrGetU8(&in, &res.version.minor);
|
||||
istrGetI32(&in, (int32*)&res.status_code);
|
||||
|
||||
istrIgnore(&in, '\n');
|
||||
istrSkip(&in, 1); // skip \n
|
||||
|
||||
resParseFields(&ctx, &in);
|
||||
res.headers = http__parse_headers(arena, &in);
|
||||
|
||||
const char *tran_encoding = resGetField(&ctx, "transfer-encoding");
|
||||
if(tran_encoding == NULL || stricmp(tran_encoding, "chunked") != 0) {
|
||||
strview_t body = istrGetviewLen(&in, 0, SIZE_MAX);
|
||||
vecClear(ctx.body);
|
||||
vecReserve(ctx.body, body.len);
|
||||
memcpy(ctx.body, body.buf, body.len);
|
||||
strview_t encoding = httpGetHeader(res.headers, strv("transfer-encoding"));
|
||||
if (!strvEquals(encoding, strv("chunked"))) {
|
||||
res.body = istrGetViewLen(&in, SIZE_MAX);
|
||||
}
|
||||
else {
|
||||
// fatal("chunked encoding not implemented yet");
|
||||
err("chunked encoding not implemented yet");
|
||||
err("chunked encoding not implemented yet! body ignored");
|
||||
}
|
||||
|
||||
return ctx;
|
||||
return res;
|
||||
}
|
||||
|
||||
void resFree(http_response_t ctx) {
|
||||
for (http_field_t *it = ctx.fields; it != vecEnd(ctx.fields); ++it) {
|
||||
free(it->key);
|
||||
free(it->value);
|
||||
str_t httpReqToStr(arena_t *arena, http_req_t *req) {
|
||||
outstream_t out = ostrInit(arena);
|
||||
|
||||
const char *method = NULL;
|
||||
switch (req->method) {
|
||||
case HTTP_GET: method = "GET"; break;
|
||||
case HTTP_POST: method = "POST"; break;
|
||||
case HTTP_HEAD: method = "HEAD"; break;
|
||||
case HTTP_PUT: method = "PUT"; break;
|
||||
case HTTP_DELETE: method = "DELETE"; break;
|
||||
default: err("unrecognised method: %d", method); return (str_t){0};
|
||||
}
|
||||
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) {
|
||||
for(uint32 i = 0; i < vecLen(ctx->fields); ++i) {
|
||||
if(stricmp(ctx->fields[i].key, key) == 0) {
|
||||
str_t httpResToStr(arena_t *arena, http_res_t *res) {
|
||||
outstream_t out = ostrInit(arena);
|
||||
|
||||
ostrPrintf(
|
||||
&out,
|
||||
"HTTP/%hhu.%hhu %d %s\r\n",
|
||||
res->version.major,
|
||||
res->version.minor,
|
||||
res->status_code,
|
||||
httpGetStatusString(res->status_code)
|
||||
);
|
||||
ostrPuts(&out, strv("\r\n"));
|
||||
ostrPuts(&out, res->body);
|
||||
|
||||
return ostrAsStr(&out);
|
||||
}
|
||||
|
||||
bool httpHasHeader(http_header_t *headers, strview_t key) {
|
||||
http_header_t *h = headers;
|
||||
while (h) {
|
||||
if (strvEquals(h->key, key)) {
|
||||
return true;
|
||||
}
|
||||
h = h->next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void resSetField(http_response_t *ctx, const char *key, const char *value) {
|
||||
_setField(&ctx->fields, key, value);
|
||||
void httpSetHeader(http_header_t *headers, strview_t key, strview_t value) {
|
||||
http_header_t *h = headers;
|
||||
while (h) {
|
||||
if (strvEquals(h->key, key)) {
|
||||
h->value = value;
|
||||
break;
|
||||
}
|
||||
h = h->next;
|
||||
}
|
||||
}
|
||||
|
||||
const char *resGetField(http_response_t *ctx, const char *field) {
|
||||
for(uint32 i = 0; i < vecLen(ctx->fields); ++i) {
|
||||
if(stricmp(ctx->fields[i].key, field) == 0) {
|
||||
return ctx->fields[i].value;
|
||||
strview_t httpGetHeader(http_header_t *headers, strview_t key) {
|
||||
http_header_t *h = headers;
|
||||
while (h) {
|
||||
if (strvEquals(h->key, key)) {
|
||||
return h->value;
|
||||
}
|
||||
h = h->next;
|
||||
}
|
||||
return (strview_t){0};
|
||||
}
|
||||
|
||||
str_t httpMakeUrlSafe(arena_t *arena, strview_t string) {
|
||||
strview_t chars = strv(" !\"#$%%&'()*+,/:;=?@[]");
|
||||
usize final_len = string.len;
|
||||
|
||||
// find final string length first
|
||||
for (usize i = 0; i < string.len; ++i) {
|
||||
if (strvContains(chars, string.buf[i])) {
|
||||
final_len += 2;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void resParseFields(http_response_t *ctx, str_istream_t *in) {
|
||||
_parseFields(&ctx->fields, in);
|
||||
}
|
||||
|
||||
str_ostream_t resPrepare(http_response_t *ctx) {
|
||||
str_ostream_t out = ostrInitLen(1024);
|
||||
|
||||
ostrPrintf(
|
||||
&out, "HTTP/%hhu.%hhu %d %s\r\n",
|
||||
ctx->version.major, ctx->version.minor,
|
||||
ctx->status_code, httpGetStatusString(ctx->status_code)
|
||||
);
|
||||
for (http_field_t *field = ctx->fields; field != vecEnd(ctx->fields); ++field) {
|
||||
ostrPrintf(&out, "%s: %s\r\n", field->key, field->value);
|
||||
str_t out = {
|
||||
.buf = alloc(arena, char, final_len + 1),
|
||||
.len = final_len
|
||||
};
|
||||
usize cur = 0;
|
||||
// substitute characters
|
||||
for (usize i = 0; i < string.len; ++i) {
|
||||
if (strvContains(chars, string.buf[i])) {
|
||||
fmtBuffer(out.buf + cur, 4, "%%%X", string.buf[i]);
|
||||
cur += 3;
|
||||
}
|
||||
else {
|
||||
out.buf[cur++] = string.buf[i];
|
||||
}
|
||||
}
|
||||
ostrPuts(&out, "\r\n");
|
||||
ostrAppendview(&out, strvInitLen(ctx->body, vecLen(ctx->body)));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
str_t resString(http_response_t *ctx) {
|
||||
str_ostream_t out = resPrepare(ctx);
|
||||
return ostrAsStr(out);
|
||||
http_url_t httpSplitUrl(strview_t url) {
|
||||
http_url_t out = {0};
|
||||
|
||||
if (strvStartsWithView(url, strv("https://"))) {
|
||||
url = strvRemovePrefix(url, 8);
|
||||
}
|
||||
else if (strvStartsWithView(url, strv("http://"))) {
|
||||
url = strvRemovePrefix(url, 7);
|
||||
}
|
||||
|
||||
out.host = strvSub(url, 0, strvFind(url, '/', 0));
|
||||
out.uri = strvSub(url, out.host.len, SIZE_MAX);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// == HTTP CLIENT =============================================================
|
||||
http_res_t httpRequest(http_request_desc_t *request) {
|
||||
usize arena_begin = arenaTell(request->arena);
|
||||
|
||||
http_client_t hcliInit() {
|
||||
return (http_client_t) {
|
||||
.port = 80,
|
||||
http_req_t req = {
|
||||
.version = (http_version_t){ 1, 1 },
|
||||
.url = request->url,
|
||||
.body = request->body,
|
||||
.method = request->request_type,
|
||||
};
|
||||
}
|
||||
|
||||
void hcliFree(http_client_t ctx) {
|
||||
strFree(ctx.host_name);
|
||||
}
|
||||
http_header_t *h = NULL;
|
||||
|
||||
void hcliSetHost(http_client_t *ctx, strview_t hostname) {
|
||||
// if the hostname starts with http:// (case insensitive)
|
||||
if(strvICompare(strvSub(hostname, 0, 7), strvInit("http://")) == 0) {
|
||||
ctx->host_name = strvCopy(strvSub(hostname, 7, SIZE_MAX));
|
||||
}
|
||||
else if(strvICompare(strvSub(hostname, 0, 8), strvInit("https://")) == 0) {
|
||||
err("HTTPS protocol not yet supported");
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// undefined protocol, use HTTP
|
||||
ctx->host_name = strvCopy(hostname);
|
||||
}
|
||||
}
|
||||
|
||||
http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *req) {
|
||||
if (strBack(ctx->host_name) == '/') {
|
||||
ctx->host_name.buf[--ctx->host_name.len] = '\0';
|
||||
}
|
||||
if(!reqHasField(req, "Host")) {
|
||||
reqSetField(req, "Host", ctx->host_name.buf);
|
||||
}
|
||||
if(!reqHasField(req, "Content-Length")) {
|
||||
if(req->body) {
|
||||
str_ostream_t out = ostrInitLen(20);
|
||||
ostrAppendu64(&out, strlen(req->body));
|
||||
reqSetField(req, "Content-Length", out.buf);
|
||||
ostrFree(out);
|
||||
}
|
||||
else {
|
||||
reqSetField(req, "Content-Length", "0");
|
||||
}
|
||||
}
|
||||
if(req->method == REQ_POST && !reqHasField(req, "Content-Type")) {
|
||||
reqSetField(req, "Content-Type", "application/x-www-form-urlencoded");
|
||||
}
|
||||
if(httpVerNumber(req->version) >= 11 && !reqHasField(req, "Connection")) {
|
||||
reqSetField(req, "Connection", "close");
|
||||
for (int i = 0; i < request->header_count; ++i) {
|
||||
http_header_t *header = request->headers + i;
|
||||
header->next = h;
|
||||
h = header;
|
||||
}
|
||||
|
||||
http_response_t res = {0};
|
||||
str_t req_str = strInit();
|
||||
str_ostream_t received = ostrInitLen(1024);
|
||||
req.headers = h;
|
||||
|
||||
if(!skInit()) {
|
||||
err("couldn't initialize sockets %s", skGetErrorString());
|
||||
goto skopen_error;
|
||||
http_url_t url = httpSplitUrl(req.url);
|
||||
|
||||
if (strvEndsWith(url.host, '/')) {
|
||||
url.host = strvRemoveSuffix(url.host, 1);
|
||||
}
|
||||
|
||||
ctx->socket = skOpen(SOCK_TCP);
|
||||
if(ctx->socket == INVALID_SOCKET) {
|
||||
err("couldn't open socket %s", skGetErrorString());
|
||||
if (!httpHasHeader(req.headers, strv("Host"))) {
|
||||
httpSetHeader(req.headers, strv("Host"), url.host);
|
||||
}
|
||||
if (!httpHasHeader(req.headers, strv("Content-Length"))) {
|
||||
char tmp[16] = {0};
|
||||
fmtBuffer(tmp, arrlen(tmp), "%zu", req.body.len);
|
||||
httpSetHeader(req.headers, strv("Content-Length"), strv(tmp));
|
||||
}
|
||||
if (req.method == HTTP_POST && !httpHasHeader(req.headers, strv("Content-Type"))) {
|
||||
httpSetHeader(req.headers, strv("Content-Type"), strv("application/x-www-form-urlencoded"));
|
||||
}
|
||||
if (!httpHasHeader(req.headers, strv("Connection"))) {
|
||||
httpSetHeader(req.headers, strv("Connection"), strv("close"));
|
||||
}
|
||||
|
||||
if (!skInit()) {
|
||||
err("couldn't initialise sockets: %s", skGetErrorString());
|
||||
goto error;
|
||||
}
|
||||
|
||||
if(skConnect(ctx->socket, ctx->host_name.buf, ctx->port)) {
|
||||
req_str = reqString(req);
|
||||
if(req_str.len == 0) {
|
||||
err("couldn't get string from request");
|
||||
socket_t sock = skOpen(SOCK_TCP);
|
||||
if (!skIsValid(sock)) {
|
||||
err("couldn't open socket: %s", skGetErrorString());
|
||||
goto error;
|
||||
}
|
||||
|
||||
char hostname[64] = {0};
|
||||
assert(url.host.len < arrlen(hostname));
|
||||
memcpy(hostname, url.host.buf, url.host.len);
|
||||
|
||||
const int DEFAULT_HTTP_PORT = 80;
|
||||
if (!skConnect(sock, hostname, DEFAULT_HTTP_PORT)) {
|
||||
err("Couldn't connect to host %s: %s", hostname, skGetErrorString());
|
||||
goto error;
|
||||
}
|
||||
|
||||
str_t reqstr = httpReqToStr(request->arena, &req);
|
||||
if (strIsEmpty(reqstr)) {
|
||||
err("couldn't get string from request");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (skSend(sock, reqstr.buf, (int)reqstr.len) == -1) {
|
||||
err("couldn't send request to socket: %s", skGetErrorString());
|
||||
goto error;
|
||||
}
|
||||
|
||||
outstream_t response = ostrInit(request->arena);
|
||||
char buffer[4096];
|
||||
int read = 0;
|
||||
do {
|
||||
read = skReceive(sock, buffer, arrlen(buffer));
|
||||
if (read == -1) {
|
||||
err("couldn't get the data from the server: %s", skGetErrorString());
|
||||
goto error;
|
||||
}
|
||||
ostrPuts(&response, strv(buffer, read));
|
||||
} while (read != 0);
|
||||
|
||||
if(skSend(ctx->socket, req_str.buf, (int)req_str.len) == SOCKET_ERROR) {
|
||||
err("couldn't send request to socket: %s", skGetErrorString());
|
||||
goto error;
|
||||
}
|
||||
|
||||
char buffer[1024];
|
||||
int read = 0;
|
||||
do {
|
||||
read = skReceive(ctx->socket, buffer, sizeof(buffer));
|
||||
if(read == -1) {
|
||||
err("couldn't get the data from the server: %s", skGetErrorString());
|
||||
goto error;
|
||||
}
|
||||
ostrAppendview(&received, strvInitLen(buffer, read));
|
||||
} while(read != 0);
|
||||
|
||||
// if the data received is not null terminated
|
||||
if(*(received.buf + received.len) != '\0') {
|
||||
ostrPutc(&received, '\0');
|
||||
received.len--;
|
||||
}
|
||||
|
||||
res = resParse(received.buf);
|
||||
}
|
||||
else {
|
||||
err("Couldn't connect to host %s -> %s", ctx->host_name, skGetErrorString());
|
||||
if (!skClose(sock)) {
|
||||
err("couldn't close socket: %s", skGetErrorString());
|
||||
}
|
||||
|
||||
if(!skClose(ctx->socket)) {
|
||||
err("Couldn't close socket");
|
||||
if (!skCleanup()) {
|
||||
err("couldn't clean up sockets: %s", skGetErrorString());
|
||||
}
|
||||
|
||||
return httpParseRes(request->arena, ostrAsView(&response));
|
||||
|
||||
error:
|
||||
if(!skCleanup()) {
|
||||
err("couldn't clean up sockets %s", skGetErrorString());
|
||||
}
|
||||
skopen_error:
|
||||
strFree(req_str);
|
||||
ostrFree(received);
|
||||
return res;
|
||||
arenaRewind(request->arena, arena_begin);
|
||||
skCleanup();
|
||||
return (http_res_t){0};
|
||||
}
|
||||
|
||||
http_response_t httpGet(strview_t hostname, strview_t uri) {
|
||||
http_request_t request = reqInit();
|
||||
request.method = REQ_GET;
|
||||
reqSetUri(&request, uri);
|
||||
#if COLLA_WIN
|
||||
|
||||
http_client_t client = hcliInit();
|
||||
hcliSetHost(&client, hostname);
|
||||
|
||||
http_response_t res = hcliSendRequest(&client, &request);
|
||||
|
||||
reqFree(request);
|
||||
hcliFree(client);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
url_split_t urlSplit(strview_t uri) {
|
||||
url_split_t out = {0};
|
||||
|
||||
if (strvStartsWithView(uri, strvInit("https://"))) {
|
||||
uri = strvRemovePrefix(uri, 8);
|
||||
}
|
||||
else if (strvStartsWithView(uri, strvInit("http://"))) {
|
||||
uri = strvRemovePrefix(uri, 7);
|
||||
buffer_t httpsRequest(http_request_desc_t *req) {
|
||||
HINTERNET internet = InternetOpen(
|
||||
TEXT("COLLA"),
|
||||
INTERNET_OPEN_TYPE_PRECONFIG,
|
||||
NULL,
|
||||
NULL,
|
||||
0
|
||||
);
|
||||
if (!internet) {
|
||||
fatal("call to InternetOpen failed: %u", GetLastError());
|
||||
}
|
||||
|
||||
out.host = strvSub(uri, 0, strvFind(uri, '/', 0));
|
||||
out.uri = strvSub(uri, out.host.len, SIZE_MAX);
|
||||
return out;
|
||||
http_url_t split = httpSplitUrl(req->url);
|
||||
strview_t server = split.host;
|
||||
strview_t page = split.uri;
|
||||
|
||||
if (strvStartsWithView(server, strv("http://"))) {
|
||||
server = strvRemovePrefix(server, 7);
|
||||
}
|
||||
|
||||
if (strvStartsWithView(server, strv("https://"))) {
|
||||
server = strvRemovePrefix(server, 8);
|
||||
}
|
||||
|
||||
arena_t scratch = *req->arena;
|
||||
const TCHAR *tserver = strvToTChar(&scratch, server);
|
||||
const TCHAR *tpage = strvToTChar(&scratch, page);
|
||||
|
||||
HINTERNET connection = InternetConnect(
|
||||
internet,
|
||||
tserver,
|
||||
INTERNET_DEFAULT_HTTPS_PORT,
|
||||
NULL,
|
||||
NULL,
|
||||
INTERNET_SERVICE_HTTP,
|
||||
0,
|
||||
(DWORD_PTR)NULL // userdata
|
||||
);
|
||||
if (!connection) {
|
||||
fatal("call to InternetConnect failed: %u", GetLastError());
|
||||
}
|
||||
|
||||
const TCHAR *accepted_types[] = { TEXT("*/*"), NULL };
|
||||
|
||||
HINTERNET request = HttpOpenRequest(
|
||||
connection,
|
||||
https__get_method_str(req->request_type),
|
||||
tpage,
|
||||
TEXT("HTTP/1.1"),
|
||||
NULL,
|
||||
accepted_types,
|
||||
INTERNET_FLAG_SECURE,
|
||||
(DWORD_PTR)NULL // userdata
|
||||
);
|
||||
if (!request) {
|
||||
fatal("call to HttpOpenRequest failed: %u", GetLastError());
|
||||
}
|
||||
|
||||
outstream_t header = ostrInit(&scratch);
|
||||
|
||||
for (int i = 0; i < req->header_count; ++i) {
|
||||
http_header_t *h = &req->headers[i];
|
||||
ostrClear(&header);
|
||||
ostrPrintf(
|
||||
&header,
|
||||
"%.*s: %.*s\r\n",
|
||||
h->key.len, h->key.buf,
|
||||
h->value.len, h->value.buf
|
||||
);
|
||||
str_t header_str = ostrAsStr(&header);
|
||||
HttpAddRequestHeadersA(
|
||||
request,
|
||||
header_str.buf,
|
||||
header_str.len,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
BOOL request_sent = HttpSendRequest(
|
||||
request,
|
||||
NULL,
|
||||
0,
|
||||
(void *)req->body.buf,
|
||||
req->body.len
|
||||
);
|
||||
if (!request_sent) {
|
||||
fatal("call to HttpSendRequest failed: %u", GetLastError());
|
||||
}
|
||||
|
||||
outstream_t out = ostrInit(req->arena);
|
||||
|
||||
while (true) {
|
||||
DWORD bytes_read = 0;
|
||||
char buffer[4096];
|
||||
BOOL read = InternetReadFile(
|
||||
request,
|
||||
buffer,
|
||||
sizeof(buffer),
|
||||
&bytes_read
|
||||
);
|
||||
if (!read || bytes_read == 0) {
|
||||
break;
|
||||
}
|
||||
ostrPuts(&out, strv(buffer, bytes_read));
|
||||
}
|
||||
|
||||
InternetCloseHandle(request);
|
||||
InternetCloseHandle(connection);
|
||||
InternetCloseHandle(internet);
|
||||
|
||||
str_t outstr = ostrAsStr(&out);
|
||||
|
||||
return (buffer_t) {
|
||||
.data = (uint8 *)outstr.buf,
|
||||
.len = outstr.len
|
||||
};
|
||||
}
|
||||
|
||||
static const TCHAR *https__get_method_str(http_method_e method) {
|
||||
switch (method) {
|
||||
case HTTP_GET: return TEXT("GET");
|
||||
case HTTP_POST: return TEXT("POST");
|
||||
case HTTP_HEAD: return TEXT("HEAD");
|
||||
case HTTP_PUT: return TEXT("PUT");
|
||||
case HTTP_DELETE: return TEXT("DELETE");
|
||||
}
|
||||
// default GET
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
#include "warnings/colla_warn_end.h"
|
||||
|
|
|
|||
161
colla/http.h
161
colla/http.h
|
|
@ -1,57 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "collatypes.h"
|
||||
#include "str.h"
|
||||
#include "strstream.h"
|
||||
#include "socket.h"
|
||||
|
||||
typedef struct arena_t arena_t;
|
||||
typedef uintptr_t socket_t;
|
||||
|
||||
typedef enum {
|
||||
REQ_GET,
|
||||
REQ_POST,
|
||||
REQ_HEAD,
|
||||
REQ_PUT,
|
||||
REQ_DELETE
|
||||
} reqtype_t;
|
||||
HTTP_GET,
|
||||
HTTP_POST,
|
||||
HTTP_HEAD,
|
||||
HTTP_PUT,
|
||||
HTTP_DELETE
|
||||
} http_method_e;
|
||||
|
||||
typedef enum {
|
||||
// 2xx: success
|
||||
STATUS_OK = 200,
|
||||
STATUS_CREATED = 201,
|
||||
STATUS_ACCEPTED = 202,
|
||||
STATUS_NO_CONTENT = 204,
|
||||
STATUS_RESET_CONTENT = 205,
|
||||
STATUS_PARTIAL_CONTENT = 206,
|
||||
|
||||
// 3xx: redirection
|
||||
STATUS_MULTIPLE_CHOICES = 300,
|
||||
STATUS_MOVED_PERMANENTLY = 301,
|
||||
STATUS_MOVED_TEMPORARILY = 302,
|
||||
STATUS_NOT_MODIFIED = 304,
|
||||
|
||||
// 4xx: client error
|
||||
STATUS_BAD_REQUEST = 400,
|
||||
STATUS_UNAUTHORIZED = 401,
|
||||
STATUS_FORBIDDEN = 403,
|
||||
STATUS_NOT_FOUND = 404,
|
||||
STATUS_RANGE_NOT_SATISFIABLE = 407,
|
||||
|
||||
// 5xx: server error
|
||||
STATUS_INTERNAL_SERVER_ERROR = 500,
|
||||
STATUS_NOT_IMPLEMENTED = 501,
|
||||
STATUS_BAD_GATEWAY = 502,
|
||||
STATUS_SERVICE_NOT_AVAILABLE = 503,
|
||||
STATUS_GATEWAY_TIMEOUT = 504,
|
||||
STATUS_VERSION_NOT_SUPPORTED = 505,
|
||||
} resstatus_t;
|
||||
|
||||
const char *httpGetStatusString(resstatus_t status);
|
||||
const char *httpGetStatusString(int status);
|
||||
|
||||
typedef struct {
|
||||
uint8 major;
|
||||
|
|
@ -61,83 +24,59 @@ typedef struct {
|
|||
// translates a http_version_t to a single readable number (e.g. 1.1 -> 11, 1.0 -> 10, etc)
|
||||
int httpVerNumber(http_version_t ver);
|
||||
|
||||
typedef struct {
|
||||
char *key;
|
||||
char *value;
|
||||
} http_field_t;
|
||||
|
||||
#include "vec.h"
|
||||
|
||||
// == HTTP REQUEST ============================================================
|
||||
typedef struct http_header_t {
|
||||
strview_t key;
|
||||
strview_t value;
|
||||
struct http_header_t *next;
|
||||
} http_header_t;
|
||||
|
||||
typedef struct {
|
||||
reqtype_t method;
|
||||
http_method_e method;
|
||||
http_version_t version;
|
||||
vec(http_field_t) fields;
|
||||
char *uri;
|
||||
char *body;
|
||||
} http_request_t;
|
||||
|
||||
http_request_t reqInit(void);
|
||||
http_request_t reqParse(const char *request);
|
||||
void reqFree(http_request_t ctx);
|
||||
|
||||
bool reqHasField(http_request_t *ctx, const char *key);
|
||||
|
||||
void reqSetField(http_request_t *ctx, const char *key, const char *value);
|
||||
void reqSetUri(http_request_t *ctx, strview_t uri);
|
||||
|
||||
str_ostream_t reqPrepare(http_request_t *ctx);
|
||||
str_t reqString(http_request_t *ctx);
|
||||
|
||||
// == HTTP RESPONSE ===========================================================
|
||||
http_header_t *headers;
|
||||
strview_t url;
|
||||
strview_t body;
|
||||
} http_req_t;
|
||||
|
||||
typedef struct {
|
||||
resstatus_t status_code;
|
||||
vec(http_field_t) fields;
|
||||
int status_code;
|
||||
http_version_t version;
|
||||
vec(uint8) body;
|
||||
} http_response_t;
|
||||
http_header_t *headers;
|
||||
strview_t body;
|
||||
} http_res_t;
|
||||
|
||||
http_response_t resParse(const char *data);
|
||||
void resFree(http_response_t ctx);
|
||||
// strview_t request needs to be valid for http_req_t to be valid!
|
||||
http_req_t httpParseReq(arena_t *arena, strview_t request);
|
||||
http_res_t httpParseRes(arena_t *arena, strview_t response);
|
||||
|
||||
bool resHasField(http_response_t *ctx, const char *key);
|
||||
void resSetField(http_response_t *ctx, const char *key, const char *value);
|
||||
const char *resGetField(http_response_t *ctx, const char *field);
|
||||
str_t httpReqToStr(arena_t *arena, http_req_t *req);
|
||||
str_t httpResToStr(arena_t *arena, http_res_t *res);
|
||||
|
||||
// void resParse(http_response_t *ctx, const char *data);
|
||||
void resParseFields(http_response_t *ctx, str_istream_t *in);
|
||||
str_ostream_t resPrepare(http_response_t *ctx);
|
||||
str_t resString(http_response_t *ctx);
|
||||
bool httpHasHeader(http_header_t *headers, strview_t key);
|
||||
void httpSetHeader(http_header_t *headers, strview_t key, strview_t value);
|
||||
strview_t httpGetHeader(http_header_t *headers, strview_t key);
|
||||
|
||||
// == HTTP CLIENT =============================================================
|
||||
|
||||
typedef struct {
|
||||
str_t host_name;
|
||||
uint16 port;
|
||||
socket_t socket;
|
||||
} http_client_t;
|
||||
|
||||
http_client_t hcliInit(void);
|
||||
void hcliFree(http_client_t ctx);
|
||||
|
||||
void hcliSetHost(http_client_t *ctx, strview_t hostname);
|
||||
http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *request);
|
||||
|
||||
// == HTTP ====================================================================
|
||||
|
||||
http_response_t httpGet(strview_t hostname, strview_t uri);
|
||||
|
||||
// == URL =====================================================================
|
||||
str_t httpMakeUrlSafe(arena_t *arena, strview_t string);
|
||||
|
||||
typedef struct {
|
||||
strview_t host;
|
||||
strview_t uri;
|
||||
} url_split_t;
|
||||
} http_url_t;
|
||||
|
||||
url_split_t urlSplit(strview_t uri);
|
||||
http_url_t httpSplitUrl(strview_t url);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
typedef struct {
|
||||
arena_t *arena;
|
||||
strview_t url;
|
||||
http_method_e request_type;
|
||||
http_header_t *headers;
|
||||
int header_count;
|
||||
strview_t body;
|
||||
} http_request_desc_t;
|
||||
|
||||
// arena_t *arena, strview_t url, [ http_header_t *headers, int header_count, strview_t body ]
|
||||
#define httpGet(arena, url, ...) httpRequest(&(http_request_desc_t){ arena, url, .request_type = HTTP_GET, __VA_ARGS__ })
|
||||
#define httpsGet(arena, url, ...) httpsRequest(&(http_request_desc_t){ arena, url, .request_type = HTTP_GET, __VA_ARGS__ })
|
||||
|
||||
http_res_t httpRequest(http_request_desc_t *request);
|
||||
buffer_t httpsRequest(http_request_desc_t *request);
|
||||
|
|
|
|||
520
colla/ini.c
520
colla/ini.c
|
|
@ -1,347 +1,267 @@
|
|||
#include "ini.h"
|
||||
|
||||
#include "warnings/colla_warn_beg.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "strstream.h"
|
||||
#include "file.h"
|
||||
#include "tracelog.h"
|
||||
|
||||
// == INI READER ========================================================================
|
||||
static void ini__parse(arena_t *arena, ini_t *ini, const iniopts_t *options);
|
||||
|
||||
static const iniopts_t default_opts = {
|
||||
.key_value_divider = '='
|
||||
};
|
||||
ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options) {
|
||||
file_t fp = fileOpen(*arena, filename, FILE_READ);
|
||||
ini_t out = iniParseFile(arena, fp, options);
|
||||
fileClose(fp);
|
||||
return out;
|
||||
}
|
||||
|
||||
static iniopts_t setDefaultOptions(const iniopts_t *options);
|
||||
static initable_t *findTable(ini_t *ctx, strview_t name);
|
||||
static inivalue_t *findValue(vec(inivalue_t) values, strview_t key);
|
||||
static void addTable(ini_t *ctx, str_istream_t *in, const iniopts_t *options);
|
||||
static void addValue(initable_t *table, str_istream_t *in, const iniopts_t *options);
|
||||
ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options) {
|
||||
str_t data = fileReadWholeStrFP(arena, file);
|
||||
return iniParseStr(arena, strv(data), options);
|
||||
}
|
||||
|
||||
void _iniParseInternal(ini_t *ini, const iniopts_t *options) {
|
||||
// add root table
|
||||
vecAppend(ini->tables, (initable_t){0});
|
||||
str_istream_t in = istrInitLen(ini->text.buf, ini->text.len);
|
||||
istrSkipWhitespace(&in);
|
||||
while (!istrIsFinished(in)) {
|
||||
switch(*in.cur) {
|
||||
case '[':
|
||||
addTable(ini, &in, options);
|
||||
break;
|
||||
case '#': case ';':
|
||||
istrIgnore(&in, '\n');
|
||||
break;
|
||||
default:
|
||||
addValue(&ini->tables[0], &in, options);
|
||||
break;
|
||||
ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options) {
|
||||
ini_t out = {
|
||||
.text = str,
|
||||
.tables = NULL,
|
||||
};
|
||||
ini__parse(arena, &out, options);
|
||||
return out;
|
||||
}
|
||||
|
||||
initable_t *iniGetTable(ini_t *ctx, strview_t name) {
|
||||
initable_t *t = ctx ? ctx->tables : NULL;
|
||||
while (t) {
|
||||
if (strvEquals(t->name, name)) {
|
||||
return t;
|
||||
}
|
||||
istrSkipWhitespace(&in);
|
||||
t = t->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ini_t iniParse(const char *filename, const iniopts_t *options) {
|
||||
ini_t ini = { .text = fileReadWholeText(filename) };
|
||||
if (strIsEmpty(ini.text)) return ini;
|
||||
iniopts_t opts = setDefaultOptions(options);
|
||||
_iniParseInternal(&ini, &opts);
|
||||
return ini;
|
||||
}
|
||||
|
||||
ini_t iniParseString(const char *inistr, const iniopts_t *options) {
|
||||
ini_t ini = { .text = strFromStr(inistr) };
|
||||
if (!options) options = &default_opts;
|
||||
_iniParseInternal(&ini, options);
|
||||
return ini;
|
||||
}
|
||||
|
||||
void iniFree(ini_t ctx) {
|
||||
strFree(ctx.text);
|
||||
for (uint32 i = 0; i < vecLen(ctx.tables); ++i) {
|
||||
vecFree(ctx.tables[i].values);
|
||||
inivalue_t *iniGet(initable_t *ctx, strview_t key) {
|
||||
inivalue_t *v = ctx ? ctx->values : NULL;
|
||||
while (v) {
|
||||
if (strvEquals(v->key, key)) {
|
||||
return v;
|
||||
}
|
||||
v = v->next;
|
||||
}
|
||||
vecFree(ctx.tables);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
initable_t *iniGetTable(ini_t *ctx, const char *name) {
|
||||
if (!name) {
|
||||
return &ctx->tables[0];
|
||||
}
|
||||
else {
|
||||
return findTable(ctx, strvInit(name));
|
||||
}
|
||||
}
|
||||
|
||||
inivalue_t *iniGet(initable_t *ctx, const char *key) {
|
||||
return ctx ? findValue(ctx->values, strvInit(key)) : NULL;
|
||||
}
|
||||
|
||||
vec(strview_t) iniAsArray(const inivalue_t *value, char delim) {
|
||||
if (!value) return NULL;
|
||||
iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim) {
|
||||
strview_t v = value ? value->value : (strview_t){0};
|
||||
if (!delim) delim = ' ';
|
||||
|
||||
vec(strview_t) out = NULL;
|
||||
strview_t v = value->value;
|
||||
strview_t *beg = (strview_t *)arena->current;
|
||||
usize count = 0;
|
||||
|
||||
usize start = 0;
|
||||
for (usize i = 0; i < v.len; ++i) {
|
||||
if (v.buf[i] == delim) {
|
||||
strview_t arr_val = strvTrim(strvSub(v, start, i));
|
||||
if (!strvIsEmpty(arr_val)) vecAppend(out, arr_val);
|
||||
strview_t arrval = strvTrim(strvSub(v, start, i));
|
||||
if (!strvIsEmpty(arrval)) {
|
||||
strview_t *newval = alloc(arena, strview_t);
|
||||
*newval = arrval;
|
||||
++count;
|
||||
}
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
strview_t last = strvTrim(strvSub(v, start, SIZE_MAX));
|
||||
if (!strvIsEmpty(last)) vecAppend(out, last);
|
||||
if (!strvIsEmpty(last)) {
|
||||
strview_t *newval = alloc(arena, strview_t);
|
||||
*newval = last;
|
||||
++count;
|
||||
}
|
||||
|
||||
return (iniarray_t){
|
||||
.values = beg,
|
||||
.count = count,
|
||||
};
|
||||
}
|
||||
|
||||
uint64 iniAsUInt(inivalue_t *value) {
|
||||
strview_t v = value ? value->value : (strview_t){0};
|
||||
instream_t in = istrInitLen(v.buf, v.len);
|
||||
uint64 out = 0;
|
||||
if (!istrGetU64(&in, &out)) {
|
||||
out = 0;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
vec(strview_t) iniAsArrayU8(const inivalue_t *value, const char *delim) {
|
||||
if (!value || !delim) return NULL;
|
||||
|
||||
rune cpdelim = utf8Decode(&delim);
|
||||
vec(strview_t) out = NULL;
|
||||
strview_t v = value->value;
|
||||
|
||||
const char *start = v.buf;
|
||||
const char *buf = v.buf;
|
||||
const char *prevbuf = buf;
|
||||
|
||||
for(rune cp = utf8Decode(&buf);
|
||||
buf != (v.buf + v.len);
|
||||
cp = utf8Decode(&buf)
|
||||
) {
|
||||
if (cp == cpdelim) {
|
||||
usize start_pos = start - v.buf;
|
||||
usize end_pos = prevbuf - v.buf;
|
||||
strview_t arr_val = strvTrim(strvSub(v, start_pos, end_pos));
|
||||
if (!strvIsEmpty(arr_val)) vecAppend(out, arr_val);
|
||||
// buf has already gone to the next codepoint, skipping the delimiter
|
||||
start = buf;
|
||||
}
|
||||
prevbuf = buf;
|
||||
int64 iniAsInt(inivalue_t *value) {
|
||||
strview_t v = value ? value->value : (strview_t){0};
|
||||
instream_t in = istrInitLen(v.buf, v.len);
|
||||
int64 out = 0;
|
||||
if (!istrGetI64(&in, &out)) {
|
||||
out = 0;
|
||||
}
|
||||
|
||||
strview_t last = strvTrim(strvSub(v, start - v.buf, SIZE_MAX));
|
||||
if (!strvIsEmpty(last)) vecAppend(out, last);
|
||||
return out;
|
||||
}
|
||||
|
||||
uint64 iniAsUInt(const inivalue_t *value) {
|
||||
if (!value) return 0;
|
||||
str_istream_t in = istrInitLen(value->value.buf, value->value.len);
|
||||
uint64 val = 0;
|
||||
if (!istrGetu64(&in, &val)) val = 0;
|
||||
return val;
|
||||
}
|
||||
|
||||
int64 iniAsInt(const inivalue_t *value) {
|
||||
if (!value) return 0;
|
||||
str_istream_t in = istrInitLen(value->value.buf, value->value.len);
|
||||
int64 val = 0;
|
||||
if (!istrGeti64(&in, &val)) val = 0;
|
||||
return val;
|
||||
}
|
||||
|
||||
double iniAsNum(const inivalue_t *value) {
|
||||
if (!value) return 0.f;
|
||||
str_istream_t in = istrInitLen(value->value.buf, value->value.len);
|
||||
double val = 0;
|
||||
if (!istrGetdouble(&in, &val)) val = 0;
|
||||
return val;
|
||||
}
|
||||
|
||||
bool iniAsBool(const inivalue_t *value) {
|
||||
if (!value) return false;
|
||||
return strvCompare(value->value, strvInit("true")) == 0;
|
||||
}
|
||||
|
||||
// == INI WRITER ========================================================================
|
||||
|
||||
#include "strstream.h"
|
||||
#include "file.h"
|
||||
|
||||
static const winiopts_t default_wopts = {0};
|
||||
|
||||
iniwriter_t winiInit() {
|
||||
iniwriter_t out = {0};
|
||||
vecAppend(out.tables, (winitable_t){0});
|
||||
double iniAsNum(inivalue_t *value) {
|
||||
strview_t v = value ? value->value : (strview_t){0};
|
||||
instream_t in = istrInitLen(v.buf, v.len);
|
||||
double out = 0;
|
||||
if (!istrGetDouble(&in, &out)) {
|
||||
out = 0.0;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void winiFree(iniwriter_t ctx) {
|
||||
for (winitable_t *tab = ctx.tables; tab != vecEnd(ctx.tables); ++tab) {
|
||||
strFree(tab->key);
|
||||
for (winivalue_t *val = tab->values; val != vecEnd(tab->values); ++val) {
|
||||
strFree(val->key);
|
||||
strFree(val->value);
|
||||
}
|
||||
vecFree(tab->values);
|
||||
bool iniAsBool(inivalue_t *value) {
|
||||
strview_t v = value ? value->value : (strview_t){0};
|
||||
instream_t in = istrInitLen(v.buf, v.len);
|
||||
bool out = 0;
|
||||
if (!istrGetBool(&in, &out)) {
|
||||
out = false;
|
||||
}
|
||||
vecFree(ctx.tables);
|
||||
return out;
|
||||
}
|
||||
|
||||
str_t winiToString(iniwriter_t ctx, const winiopts_t *options) {
|
||||
if (!options) options = &default_wopts;
|
||||
// == PRIVATE FUNCTIONS ==============================================================================
|
||||
|
||||
str_ostream_t out = ostrInitLen(1024 * 20);
|
||||
if (!options->no_discalimer) ostrPuts(&out, "# auto-generated by colla's ini.h, do not modify!\n");
|
||||
// add root values
|
||||
winitable_t *root = &ctx.tables[0];
|
||||
for (winivalue_t *val = root->values; val != vecEnd(root->values); ++val) {
|
||||
ostrPrintf(&out, "%s = %s\n", val->key.buf, val->value.buf);
|
||||
#define INIPUSH(head, tail, val) \
|
||||
do { \
|
||||
if (!head) { \
|
||||
head = val; \
|
||||
tail = val; \
|
||||
} \
|
||||
else { \
|
||||
tail->next = val; \
|
||||
val = tail; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static iniopts_t ini__get_options(const iniopts_t *options) {
|
||||
iniopts_t out = {
|
||||
.key_value_divider = '=',
|
||||
};
|
||||
|
||||
#define SETOPT(v) out.v = options->v ? options->v : out.v
|
||||
|
||||
if (options) {
|
||||
SETOPT(key_value_divider);
|
||||
SETOPT(merge_duplicate_keys);
|
||||
SETOPT(merge_duplicate_tables);
|
||||
}
|
||||
if (root->values) ostrPuts(&out, "\n");
|
||||
// add each table
|
||||
for (usize i = 1; i < vecLen(ctx.tables); ++i) {
|
||||
winitable_t *tab = &ctx.tables[i];
|
||||
ostrPrintf(&out, "[%s]\n", tab->key.buf);
|
||||
for (winivalue_t *val = tab->values; val != vecEnd(tab->values); ++val) {
|
||||
ostrPrintf(&out, "%s = %s\n", val->key.buf, val->value.buf);
|
||||
}
|
||||
if ((i + 1) < vecLen(ctx.tables)) ostrPuts(&out, "\n");
|
||||
}
|
||||
return ostrAsStr(out);
|
||||
|
||||
#undef SETOPT
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void winiToFile(iniwriter_t ctx, const char *filename, const winiopts_t *options) {
|
||||
if (!options) options = &default_wopts;
|
||||
static void ini__add_value(arena_t *arena, initable_t *table, instream_t *in, iniopts_t *opts) {
|
||||
assert(table);
|
||||
|
||||
file_t fp = fileOpen(filename, FILE_WRITE);
|
||||
if (!fileIsValid(fp)) {
|
||||
err("couldn't write ini to file %s", filename);
|
||||
return;
|
||||
}
|
||||
str_t string = winiToString(ctx, options);
|
||||
fileWriteWholeTextFP(fp, strvInitStr(string));
|
||||
strFree(string);
|
||||
fileClose(fp);
|
||||
}
|
||||
|
||||
winivalue_t *winiAddValEmpty(winitable_t *table) {
|
||||
if (!table) return NULL;
|
||||
vecAppend(table->values, (winivalue_t){0});
|
||||
return &vecBack(table->values);
|
||||
}
|
||||
|
||||
winivalue_t *winiAddVal(winitable_t *table, const char *key, const char *value) {
|
||||
if (!table) return NULL;
|
||||
winivalue_t val = { .key = strFromStr(key), .value = strFromStr(value) };
|
||||
vecAppend(table->values, val);
|
||||
return &vecBack(table->values);
|
||||
}
|
||||
|
||||
winivalue_t *winiAddValStr(winitable_t *table, str_t key, str_t value) {
|
||||
if (!table) return NULL;
|
||||
winivalue_t val = { .key = key, .value = value };
|
||||
vecAppend(table->values, val);
|
||||
return &vecBack(table->values);
|
||||
}
|
||||
|
||||
winivalue_t *winiAddValView(winitable_t *table, strview_t key, strview_t value) {
|
||||
if (!table) return NULL;
|
||||
winivalue_t val = { .key = strFromView(key), .value = strFromView(value) };
|
||||
vecAppend(table->values, val);
|
||||
return &vecBack(table->values);
|
||||
}
|
||||
|
||||
winitable_t *winiAddTablEmpty(iniwriter_t *ctx) {
|
||||
vecAppend(ctx->tables, (winitable_t){0});
|
||||
return &vecBack(ctx->tables);
|
||||
}
|
||||
|
||||
winitable_t *winiAddTab(iniwriter_t *ctx, const char *name) {
|
||||
winitable_t tab = { .key = strFromStr(name) };
|
||||
vecAppend(ctx->tables, tab);
|
||||
return &vecBack(ctx->tables);
|
||||
}
|
||||
|
||||
winitable_t *winiAddTabStr(iniwriter_t *ctx, str_t name) {
|
||||
winitable_t tab = { .key = name };
|
||||
vecAppend(ctx->tables, tab);
|
||||
return &vecBack(ctx->tables);
|
||||
}
|
||||
|
||||
winitable_t *winiAddTabView(iniwriter_t *ctx, strview_t name) {
|
||||
winitable_t tab = { .key = strFromView(name) };
|
||||
vecAppend(ctx->tables, tab);
|
||||
return &vecBack(ctx->tables);
|
||||
}
|
||||
|
||||
// == PRIVATE FUNCTIONS ========================================================
|
||||
|
||||
static iniopts_t setDefaultOptions(const iniopts_t *options) {
|
||||
if (!options) return default_opts;
|
||||
|
||||
iniopts_t opts = default_opts;
|
||||
|
||||
if (options->merge_duplicate_keys)
|
||||
opts.merge_duplicate_keys = options->merge_duplicate_keys;
|
||||
|
||||
if (options->merge_duplicate_tables)
|
||||
opts.merge_duplicate_tables = options->merge_duplicate_tables;
|
||||
|
||||
if (options->key_value_divider)
|
||||
opts.key_value_divider = options->key_value_divider;
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
static initable_t *findTable(ini_t *ctx, strview_t name) {
|
||||
if (strvIsEmpty(name)) return NULL;
|
||||
for (uint32 i = 1; i < vecLen(ctx->tables); ++i) {
|
||||
if (strvCompare(ctx->tables[i].name, name) == 0) {
|
||||
return &ctx->tables[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inivalue_t *findValue(vec(inivalue_t) values, strview_t key) {
|
||||
if (strvIsEmpty(key)) return NULL;
|
||||
for (uint32 i = 0; i < vecLen(values); ++i) {
|
||||
if (strvCompare(values[i].key, key) == 0) {
|
||||
return &values[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void addTable(ini_t *ctx, str_istream_t *in, const iniopts_t *options) {
|
||||
istrSkip(in, 1); // skip [
|
||||
strview_t name = istrGetview(in, ']');
|
||||
istrSkip(in, 1); // skip ]
|
||||
initable_t *table = options->merge_duplicate_tables ? findTable(ctx, name) : NULL;
|
||||
if (!table) {
|
||||
vecAppend(ctx->tables, (initable_t){ name });
|
||||
table = &vecBack(ctx->tables);
|
||||
}
|
||||
istrIgnore(in, '\n'); istrSkip(in, 1);
|
||||
while (!istrIsFinished(*in)) {
|
||||
switch (*in->cur) {
|
||||
case '\n': case '\r':
|
||||
return;
|
||||
case '#': case ';':
|
||||
istrIgnore(in, '\n');
|
||||
break;
|
||||
default:
|
||||
addValue(table, in, options);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void addValue(initable_t *table, str_istream_t *in, const iniopts_t *options) {
|
||||
if (!table) fatal("table is null");
|
||||
|
||||
strview_t key = strvTrim(istrGetview(in, options->key_value_divider));
|
||||
strview_t key = strvTrim(istrGetView(in, opts->key_value_divider));
|
||||
istrSkip(in, 1);
|
||||
strview_t value = strvTrim(istrGetview(in, '\n'));
|
||||
// value might be until EOF, in that case no use in skipping
|
||||
if (!istrIsFinished(*in)) istrSkip(in, 1); // skip newline
|
||||
inivalue_t *new_value = options->merge_duplicate_keys ? findValue(table->values, key) : NULL;
|
||||
if (!new_value) {
|
||||
inivalue_t ini_val = (inivalue_t){ key, value };
|
||||
vecAppend(table->values, ini_val);
|
||||
strview_t value = strvTrim(istrGetView(in, '\n'));
|
||||
istrSkip(in, 1);
|
||||
inivalue_t *newval = NULL;
|
||||
|
||||
if (opts->merge_duplicate_keys) {
|
||||
newval = table->values;
|
||||
while (newval) {
|
||||
if (strvEquals(newval->key, key)) {
|
||||
break;
|
||||
}
|
||||
newval = newval->next;
|
||||
}
|
||||
}
|
||||
|
||||
if (newval) {
|
||||
newval->value = value;
|
||||
}
|
||||
else {
|
||||
new_value->value = value;
|
||||
newval = alloc(arena, inivalue_t);
|
||||
newval->key = key;
|
||||
newval->value = value;
|
||||
|
||||
if (!table->values) {
|
||||
table->values = newval;
|
||||
}
|
||||
else {
|
||||
table->tail->next = newval;
|
||||
}
|
||||
|
||||
table->tail = newval;
|
||||
}
|
||||
}
|
||||
|
||||
static void ini__add_table(arena_t *arena, ini_t *ctx, instream_t *in, iniopts_t *options) {
|
||||
istrSkip(in, 1); // skip [
|
||||
strview_t name = istrGetView(in, ']');
|
||||
istrSkip(in, 1); // skip ]
|
||||
initable_t *table = NULL;
|
||||
|
||||
if (options->merge_duplicate_tables) {
|
||||
table = ctx->tables;
|
||||
while (table) {
|
||||
if (strvEquals(table->name, name)) {
|
||||
break;
|
||||
}
|
||||
table = table->next;
|
||||
}
|
||||
}
|
||||
|
||||
if (!table) {
|
||||
table = alloc(arena, initable_t);
|
||||
|
||||
if (!ctx->tables) {
|
||||
ctx->tables = table;
|
||||
}
|
||||
else {
|
||||
ctx->tail->next = table;
|
||||
}
|
||||
|
||||
ctx->tail = table;
|
||||
}
|
||||
|
||||
istrIgnoreAndSkip(in, '\n');
|
||||
while (!istrIsFinished(*in)) {
|
||||
switch (istrPeek(in)) {
|
||||
case '\n': // fallthrough
|
||||
case '\r':
|
||||
return;
|
||||
case '#': // fallthrough
|
||||
case ';':
|
||||
istrIgnoreAndSkip(in, '\n');
|
||||
break;
|
||||
default:
|
||||
ini__add_value(arena, table, in, options);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ini__parse(arena_t *arena, ini_t *ini, const iniopts_t *options) {
|
||||
iniopts_t opts = ini__get_options(options);
|
||||
|
||||
instream_t in = istrInitLen(ini->text.buf, ini->text.len);
|
||||
|
||||
while (!istrIsFinished(in)) {
|
||||
istrSkipWhitespace(&in);
|
||||
switch (istrPeek(&in)) {
|
||||
case '[':
|
||||
ini__add_table(arena, ini, &in, &opts);
|
||||
break;
|
||||
case '#': // fallthrough
|
||||
case ';':
|
||||
istrIgnoreAndSkip(&in, '\n');
|
||||
break;
|
||||
default:
|
||||
ini__add_value(arena, ini->tables, &in, &opts);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#undef INIPUSH
|
||||
|
||||
#include "warnings/colla_warn_end.h"
|
||||
|
|
|
|||
89
colla/ini.h
89
colla/ini.h
|
|
@ -1,29 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "collatypes.h"
|
||||
#include "str.h"
|
||||
#include "vec.h"
|
||||
#include "utf8.h"
|
||||
#include "file.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
typedef struct arena_t arena_t;
|
||||
|
||||
// == INI READER ========================================================================
|
||||
|
||||
typedef struct {
|
||||
typedef struct inivalue_t {
|
||||
strview_t key;
|
||||
strview_t value;
|
||||
struct inivalue_t *next;
|
||||
} inivalue_t;
|
||||
|
||||
typedef struct {
|
||||
typedef struct initable_t {
|
||||
strview_t name;
|
||||
vec(inivalue_t) values;
|
||||
inivalue_t *values;
|
||||
inivalue_t *tail;
|
||||
struct initable_t *next;
|
||||
} initable_t;
|
||||
|
||||
typedef struct {
|
||||
str_t text;
|
||||
vec(initable_t) tables;
|
||||
strview_t text;
|
||||
initable_t *tables;
|
||||
initable_t *tail;
|
||||
} ini_t;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -32,58 +31,22 @@ typedef struct {
|
|||
char key_value_divider; // default =
|
||||
} iniopts_t;
|
||||
|
||||
ini_t iniParse(const char *filename, const iniopts_t *options);
|
||||
ini_t iniParseString(const char *inistr, const iniopts_t *options);
|
||||
void iniFree(ini_t ctx);
|
||||
ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options);
|
||||
ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options);
|
||||
ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options);
|
||||
|
||||
initable_t *iniGetTable(ini_t *ctx, const char *name);
|
||||
inivalue_t *iniGet(initable_t *ctx, const char *key);
|
||||
#define INI_ROOT strv("__ROOT__")
|
||||
|
||||
vec(strview_t) iniAsArray(const inivalue_t *value, char delim);
|
||||
// delim is expected to be a single utf8 character
|
||||
vec(strview_t) iniAsArrayU8(const inivalue_t *value, const char *delim);
|
||||
uint64 iniAsUInt(const inivalue_t *value);
|
||||
int64 iniAsInt(const inivalue_t *value);
|
||||
double iniAsNum(const inivalue_t *value);
|
||||
bool iniAsBool(const inivalue_t *value);
|
||||
|
||||
// == INI WRITER ========================================================================
|
||||
initable_t *iniGetTable(ini_t *ctx, strview_t name);
|
||||
inivalue_t *iniGet(initable_t *ctx, strview_t key);
|
||||
|
||||
typedef struct {
|
||||
str_t key;
|
||||
str_t value;
|
||||
} winivalue_t;
|
||||
strview_t *values;
|
||||
usize count;
|
||||
} iniarray_t;
|
||||
|
||||
typedef struct {
|
||||
str_t key;
|
||||
vec(winivalue_t) values;
|
||||
} winitable_t;
|
||||
|
||||
typedef struct {
|
||||
vec(winitable_t) tables;
|
||||
} iniwriter_t;
|
||||
|
||||
typedef struct {
|
||||
bool no_discalimer; // default false
|
||||
char key_value_divider; // default =
|
||||
} winiopts_t;
|
||||
|
||||
iniwriter_t winiInit();
|
||||
void winiFree(iniwriter_t ctx);
|
||||
|
||||
str_t winiToString(iniwriter_t ctx, const winiopts_t *options);
|
||||
void winiToFile(iniwriter_t ctx, const char *filename, const winiopts_t *options);
|
||||
|
||||
winivalue_t *winiAddValEmpty(winitable_t *table);
|
||||
winivalue_t *winiAddVal(winitable_t *table, const char *key, const char *value);
|
||||
winivalue_t *winiAddValStr(winitable_t *table, str_t key, str_t value);
|
||||
winivalue_t *winiAddValView(winitable_t *table, strview_t key, strview_t value);
|
||||
|
||||
winitable_t *winiAddTablEmpty(iniwriter_t *ctx);
|
||||
winitable_t *winiAddTab(iniwriter_t *ctx, const char *name);
|
||||
winitable_t *winiAddTabStr(iniwriter_t *ctx, str_t name);
|
||||
winitable_t *winiAddTabView(iniwriter_t *ctx, strview_t name);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim);
|
||||
uint64 iniAsUInt(inivalue_t *value);
|
||||
int64 iniAsInt(inivalue_t *value);
|
||||
double iniAsNum(inivalue_t *value);
|
||||
bool iniAsBool(inivalue_t *value);
|
||||
288
colla/json.c
Normal file
288
colla/json.c
Normal 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
47
colla/json.h
Normal 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
382
colla/server.c
Normal 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
36
colla/server.h
Normal 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);
|
||||
237
colla/socket.c
237
colla/socket.c
|
|
@ -1,119 +1,114 @@
|
|||
#include "socket.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include "tracelog.h"
|
||||
|
||||
#ifndef NDEBUG
|
||||
// VERY MUCH NOT THREAD SAFE
|
||||
static int initialize_count = 0;
|
||||
#if COLLA_WIN && COLLA_CMT_LIB
|
||||
#pragma comment(lib, "Ws2_32")
|
||||
#endif
|
||||
|
||||
#if SOCK_WINDOWS
|
||||
static bool _win_skInit();
|
||||
static bool _win_skCleanup();
|
||||
static int _win_skGetError();
|
||||
static const char *_win_skGetErrorString();
|
||||
#if COLLA_WIN
|
||||
#include <winsock2.h>
|
||||
|
||||
#define SOCK_CALL(fun) _win_##fun
|
||||
bool skInit(void) {
|
||||
WSADATA w;
|
||||
int error = WSAStartup(0x0202, &w);
|
||||
return error == 0;
|
||||
}
|
||||
|
||||
#elif SOCK_POSIX
|
||||
bool skCleanup(void) {
|
||||
return WSACleanup() == 0;
|
||||
}
|
||||
|
||||
bool skClose(socket_t sock) {
|
||||
return closesocket(sock) != -1;
|
||||
}
|
||||
|
||||
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) {
|
||||
return WSAPoll(to_poll, num_to_poll, timeout);
|
||||
}
|
||||
|
||||
int skGetError(void) {
|
||||
return WSAGetLastError();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <string.h> // strerror
|
||||
#include <poll.h>
|
||||
|
||||
#define INVALID_SOCKET (-1)
|
||||
#define SOCKET_ERROR (-1)
|
||||
|
||||
static bool _posix_skInit();
|
||||
static bool _posix_skCleanup();
|
||||
static int _posix_skGetError();
|
||||
static const char *_posix_skGetErrorString();
|
||||
|
||||
#define SOCK_CALL(fun) _posix_##fun
|
||||
|
||||
#endif
|
||||
|
||||
bool skInit() {
|
||||
#ifndef NDEBUG
|
||||
++initialize_count;
|
||||
#endif
|
||||
return SOCK_CALL(skInit());
|
||||
bool skInit(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool skCleanup() {
|
||||
#ifndef NDEBUG
|
||||
--initialize_count;
|
||||
#endif
|
||||
return SOCK_CALL(skCleanup());
|
||||
bool skCleanup(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
socket_t skOpen(sktype_t type) {
|
||||
bool skClose(socket_t sock) {
|
||||
return close(sock) != -1;
|
||||
}
|
||||
|
||||
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) {
|
||||
return poll(to_poll, num_to_poll, timeout);
|
||||
}
|
||||
|
||||
int skGetError(void) {
|
||||
return errno;
|
||||
}
|
||||
|
||||
const char *skGetErrorString(void) {
|
||||
return strerror(errno);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
socket_t skOpen(sktype_e type) {
|
||||
int sock_type = 0;
|
||||
|
||||
switch(type) {
|
||||
case SOCK_TCP: sock_type = SOCK_STREAM; break;
|
||||
case SOCK_UDP: sock_type = SOCK_DGRAM; break;
|
||||
default: fatal("skType not recognized: %d", type); break;
|
||||
case SOCK_TCP: sock_type = SOCK_STREAM; break;
|
||||
case SOCK_UDP: sock_type = SOCK_DGRAM; break;
|
||||
default: fatal("skType not recognized: %d", type); break;
|
||||
}
|
||||
|
||||
return skOpenPro(AF_INET, sock_type, 0);
|
||||
}
|
||||
|
||||
socket_t skOpenEx(const char *protocol) {
|
||||
#ifndef NDEBUG
|
||||
if(initialize_count <= 0) {
|
||||
fatal("skInit has not been called");
|
||||
}
|
||||
#endif
|
||||
struct protoent *proto = getprotobyname(protocol);
|
||||
if(!proto) {
|
||||
return INVALID_SOCKET;
|
||||
return (socket_t){0};
|
||||
}
|
||||
return skOpenPro(AF_INET, SOCK_STREAM, proto->p_proto);
|
||||
}
|
||||
|
||||
socket_t skOpenPro(int af, int type, int protocol) {
|
||||
#ifndef NDEBUG
|
||||
if(initialize_count <= 0) {
|
||||
fatal("skInit has not been called");
|
||||
}
|
||||
#endif
|
||||
return socket(af, type, protocol);
|
||||
}
|
||||
|
||||
sk_addrin_t skAddrinInit(const char *ip, uint16_t port) {
|
||||
sk_addrin_t addr;
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
addr.sin_addr.s_addr = inet_addr(ip);
|
||||
return addr;
|
||||
bool skIsValid(socket_t sock) {
|
||||
return sock != (socket_t)-1;
|
||||
}
|
||||
|
||||
bool skClose(socket_t sock) {
|
||||
#if SOCK_WINDOWS
|
||||
int error = closesocket(sock);
|
||||
#elif SOCK_POSIX
|
||||
int error = close(sock);
|
||||
#endif
|
||||
sock = INVALID_SOCKET;
|
||||
return error != SOCKET_ERROR;
|
||||
skaddrin_t skAddrinInit(const char *ip, uint16_t port) {
|
||||
return (skaddrin_t){
|
||||
.sin_family = AF_INET,
|
||||
.sin_port = htons(port),
|
||||
// TODO use inet_pton instead
|
||||
.sin_addr.s_addr = inet_addr(ip),
|
||||
};
|
||||
}
|
||||
|
||||
bool skBind(socket_t sock, const char *ip, uint16_t port) {
|
||||
sk_addrin_t addr;
|
||||
addr.sin_family = AF_INET;
|
||||
// TODO use inet_pton instead
|
||||
addr.sin_addr.s_addr = inet_addr(ip);
|
||||
|
||||
addr.sin_port = htons(port);
|
||||
|
||||
return skBindPro(sock, (sk_addr_t *) &addr, sizeof(addr));
|
||||
skaddrin_t addr = skAddrinInit(ip, port);
|
||||
return skBindPro(sock, (skaddr_t *)&addr, sizeof(addr));
|
||||
}
|
||||
|
||||
bool skBindPro(socket_t sock, const sk_addr_t *name, sk_len_t namelen) {
|
||||
return bind(sock, name, namelen) != SOCKET_ERROR;
|
||||
bool skBindPro(socket_t sock, const skaddr_t *name, int namelen) {
|
||||
return bind(sock, name, namelen) != -1;
|
||||
}
|
||||
|
||||
bool skListen(socket_t sock) {
|
||||
|
|
@ -121,16 +116,16 @@ bool skListen(socket_t sock) {
|
|||
}
|
||||
|
||||
bool skListenPro(socket_t sock, int backlog) {
|
||||
return listen(sock, backlog) != SOCKET_ERROR;
|
||||
return listen(sock, backlog) != -1;
|
||||
}
|
||||
|
||||
socket_t skAccept(socket_t sock) {
|
||||
sk_addrin_t addr;
|
||||
sk_len_t addr_size = (sk_len_t)sizeof(addr);
|
||||
return skAcceptPro(sock, (sk_addr_t *) &addr, &addr_size);
|
||||
skaddrin_t addr = {0};
|
||||
int addr_size = sizeof(addr);
|
||||
return skAcceptPro(sock, (skaddr_t *)&addr, &addr_size);
|
||||
}
|
||||
|
||||
socket_t skAcceptPro(socket_t sock, sk_addr_t *addr, sk_len_t *addrlen) {
|
||||
socket_t skAcceptPro(socket_t sock, skaddr_t *addr, int *addrlen) {
|
||||
return accept(sock, addr, addrlen);
|
||||
}
|
||||
|
||||
|
|
@ -143,16 +138,13 @@ bool skConnect(socket_t sock, const char *server, unsigned short server_port) {
|
|||
address = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]);
|
||||
}
|
||||
|
||||
sk_addrin_t addr;
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = inet_addr(address);
|
||||
addr.sin_port = htons(server_port);
|
||||
skaddrin_t addr = skAddrinInit(address, server_port);
|
||||
|
||||
return skConnectPro(sock, (sk_addr_t *) &addr, sizeof(addr));
|
||||
return skConnectPro(sock, (skaddr_t *)&addr, sizeof(addr));
|
||||
}
|
||||
|
||||
bool skConnectPro(socket_t sock, const sk_addr_t *name, sk_len_t namelen) {
|
||||
return connect(sock, name, namelen) != SOCKET_ERROR;
|
||||
bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen) {
|
||||
return connect(sock, name, namelen) != -1;
|
||||
}
|
||||
|
||||
int skSend(socket_t sock, const void *buf, int len) {
|
||||
|
|
@ -163,11 +155,11 @@ int skSendPro(socket_t sock, const void *buf, int len, int flags) {
|
|||
return send(sock, buf, len, flags);
|
||||
}
|
||||
|
||||
int skSendTo(socket_t sock, const void *buf, int len, const sk_addrin_t *to) {
|
||||
return skSendToPro(sock, buf, len, 0, (sk_addr_t*) to, sizeof(sk_addrin_t));
|
||||
int skSendTo(socket_t sock, const void *buf, int len, const skaddrin_t *to) {
|
||||
return skSendToPro(sock, buf, len, 0, (skaddr_t*)to, sizeof(skaddrin_t));
|
||||
}
|
||||
|
||||
int skSendToPro(socket_t sock, const void *buf, int len, int flags, const sk_addr_t *to, int tolen) {
|
||||
int skSendToPro(socket_t sock, const void *buf, int len, int flags, const skaddr_t *to, int tolen) {
|
||||
return sendto(sock, buf, len, flags, to, tolen);
|
||||
}
|
||||
|
||||
|
|
@ -179,52 +171,19 @@ int skReceivePro(socket_t sock, void *buf, int len, int flags) {
|
|||
return recv(sock, buf, len, flags);
|
||||
}
|
||||
|
||||
int skReceiveFrom(socket_t sock, void *buf, int len, sk_addrin_t *from) {
|
||||
sk_len_t fromlen = sizeof(sk_addr_t);
|
||||
return skReceiveFromPro(sock, buf, len, 0, (sk_addr_t*)from, &fromlen);
|
||||
int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from) {
|
||||
int fromlen = sizeof(skaddr_t);
|
||||
return skReceiveFromPro(sock, buf, len, 0, (skaddr_t*)from, &fromlen);
|
||||
}
|
||||
|
||||
int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, sk_addr_t *from, sk_len_t *fromlen) {
|
||||
int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, skaddr_t *from, int *fromlen) {
|
||||
return recvfrom(sock, buf, len, flags, from, fromlen);
|
||||
}
|
||||
|
||||
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) {
|
||||
#if SOCK_WINDOWS
|
||||
return WSAPoll(to_poll, num_to_poll, timeout);
|
||||
#elif SOCK_POSIX
|
||||
return poll(to_poll, num_to_poll, timeout);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool skIsValid(socket_t sock) {
|
||||
return sock != INVALID_SOCKET;
|
||||
}
|
||||
|
||||
int skGetError() {
|
||||
return SOCK_CALL(skGetError());
|
||||
}
|
||||
|
||||
const char *skGetErrorString() {
|
||||
return SOCK_CALL(skGetErrorString());
|
||||
}
|
||||
|
||||
#ifdef SOCK_WINDOWS
|
||||
static bool _win_skInit() {
|
||||
WSADATA w;
|
||||
int error = WSAStartup(0x0202, &w);
|
||||
return error == 0;
|
||||
}
|
||||
|
||||
static bool _win_skCleanup() {
|
||||
return WSACleanup() == 0;
|
||||
}
|
||||
|
||||
static int _win_skGetError() {
|
||||
return WSAGetLastError();
|
||||
}
|
||||
|
||||
static const char *_win_skGetErrorString() {
|
||||
switch(_win_skGetError()) {
|
||||
// put this at the end of file to not make everything else unreadable
|
||||
#if COLLA_WIN
|
||||
const char *skGetErrorString(void) {
|
||||
switch(skGetError()) {
|
||||
case WSA_INVALID_HANDLE: return "Specified event object handle is invalid.";
|
||||
case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available.";
|
||||
case WSA_INVALID_PARAMETER: return "One or more parameters are invalid.";
|
||||
|
|
@ -324,22 +283,4 @@ static const char *_win_skGetErrorString() {
|
|||
|
||||
return "(nothing)";
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static bool _posix_skInit() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _posix_skCleanup() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static int _posix_skGetError() {
|
||||
return errno;
|
||||
}
|
||||
|
||||
static const char *_posix_skGetErrorString() {
|
||||
return strerror(errno);
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,45 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#include "collatypes.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define SOCK_WINDOWS 1
|
||||
#if COLLA_WIN
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#define SOCK_POSIX 1
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#if SOCK_WINDOWS
|
||||
#pragma warning(disable:4996) // _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
#include "win32_slim.h"
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
typedef SOCKET socket_t;
|
||||
typedef int sk_len_t;
|
||||
#elif SOCK_POSIX
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
typedef int socket_t;
|
||||
typedef uint32_t sk_len_t;
|
||||
#define INVALID_SOCKET (-1)
|
||||
#define SOCKET_ERROR (-1)
|
||||
#endif
|
||||
|
||||
typedef struct sockaddr sk_addr_t;
|
||||
typedef struct sockaddr_in sk_addrin_t;
|
||||
typedef uintptr_t socket_t;
|
||||
typedef struct sockaddr skaddr_t;
|
||||
typedef struct sockaddr_in skaddrin_t;
|
||||
typedef struct pollfd skpoll_t;
|
||||
|
||||
typedef enum {
|
||||
SOCK_TCP,
|
||||
SOCK_UDP,
|
||||
} sktype_t;
|
||||
|
||||
// == RAW SOCKETS ==========================================
|
||||
} sktype_e;
|
||||
|
||||
// Initialize sockets, returns true on success
|
||||
bool skInit(void);
|
||||
|
|
@ -47,7 +24,7 @@ bool skInit(void);
|
|||
bool skCleanup(void);
|
||||
|
||||
// Opens a socket, check socket_t with skValid
|
||||
socket_t skOpen(sktype_t type);
|
||||
socket_t skOpen(sktype_e type);
|
||||
// Opens a socket using 'protocol', options are
|
||||
// ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp
|
||||
// check socket_t with skValid
|
||||
|
|
@ -62,12 +39,12 @@ bool skIsValid(socket_t sock);
|
|||
bool skClose(socket_t sock);
|
||||
|
||||
// Fill out a sk_addrin_t structure with "ip" and "port"
|
||||
sk_addrin_t skAddrinInit(const char *ip, uint16_t port);
|
||||
skaddrin_t skAddrinInit(const char *ip, uint16_t port);
|
||||
|
||||
// Associate a local address with a socket
|
||||
bool skBind(socket_t sock, const char *ip, uint16_t port);
|
||||
// Associate a local address with a socket
|
||||
bool skBindPro(socket_t sock, const sk_addr_t *name, sk_len_t namelen);
|
||||
bool skBindPro(socket_t sock, const skaddr_t *name, int namelen);
|
||||
|
||||
// Place a socket in a state in which it is listening for an incoming connection
|
||||
bool skListen(socket_t sock);
|
||||
|
|
@ -77,29 +54,29 @@ bool skListenPro(socket_t sock, int backlog);
|
|||
// Permits an incoming connection attempt on a socket
|
||||
socket_t skAccept(socket_t sock);
|
||||
// Permits an incoming connection attempt on a socket
|
||||
socket_t skAcceptPro(socket_t sock, sk_addr_t *addr, sk_len_t *addrlen);
|
||||
socket_t skAcceptPro(socket_t sock, skaddr_t *addr, int *addrlen);
|
||||
|
||||
// Connects to a server (e.g. "127.0.0.1" or "google.com") with a port(e.g. 1234), returns true on success
|
||||
bool skConnect(socket_t sock, const char *server, unsigned short server_port);
|
||||
// Connects to a server, returns true on success
|
||||
bool skConnectPro(socket_t sock, const sk_addr_t *name, sk_len_t namelen);
|
||||
bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen);
|
||||
|
||||
// Sends data on a socket, returns true on success
|
||||
int skSend(socket_t sock, const void *buf, int len);
|
||||
// Sends data on a socket, returns true on success
|
||||
int skSendPro(socket_t sock, const void *buf, int len, int flags);
|
||||
// Sends data to a specific destination
|
||||
int skSendTo(socket_t sock, const void *buf, int len, const sk_addrin_t *to);
|
||||
int skSendTo(socket_t sock, const void *buf, int len, const skaddrin_t *to);
|
||||
// Sends data to a specific destination
|
||||
int skSendToPro(socket_t sock, const void *buf, int len, int flags, const sk_addr_t *to, int tolen);
|
||||
int skSendToPro(socket_t sock, const void *buf, int len, int flags, const skaddr_t *to, int tolen);
|
||||
// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error
|
||||
int skReceive(socket_t sock, void *buf, int len);
|
||||
// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error
|
||||
int skReceivePro(socket_t sock, void *buf, int len, int flags);
|
||||
// Receives a datagram and stores the source address.
|
||||
int skReceiveFrom(socket_t sock, void *buf, int len, sk_addrin_t *from);
|
||||
int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from);
|
||||
// Receives a datagram and stores the source address.
|
||||
int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, sk_addr_t *from, sk_len_t *fromlen);
|
||||
int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, skaddr_t *from, int *fromlen);
|
||||
|
||||
// Wait for an event on some sockets
|
||||
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout);
|
||||
|
|
@ -108,14 +85,3 @@ int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout);
|
|||
int skGetError(void);
|
||||
// Returns a human-readable string from a skGetError
|
||||
const char *skGetErrorString(void);
|
||||
|
||||
// == UDP SOCKETS ==========================================
|
||||
|
||||
typedef socket_t udpsock_t;
|
||||
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
|
|
|||
1930
colla/stb/stb_sprintf.h
Normal file
1930
colla/stb/stb_sprintf.h
Normal file
File diff suppressed because it is too large
Load diff
755
colla/str.c
755
colla/str.c
|
|
@ -1,291 +1,259 @@
|
|||
#include "str.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include "warnings/colla_warn_beg.h"
|
||||
|
||||
#include "arena.h"
|
||||
#include "format.h"
|
||||
#include "tracelog.h"
|
||||
#include "strstream.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "win32_slim.h"
|
||||
#else
|
||||
#include <iconv.h>
|
||||
#endif
|
||||
|
||||
#ifndef min
|
||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||
#if COLLA_WIN
|
||||
#include <stringapiset.h>
|
||||
#endif
|
||||
|
||||
// == STR_T ========================================================
|
||||
|
||||
str_t strInit(void) {
|
||||
return (str_t) {
|
||||
.buf = NULL,
|
||||
.len = 0
|
||||
str_t strInit(arena_t *arena, const char *buf) {
|
||||
return buf ? strInitLen(arena, buf, strlen(buf)) : (str_t){0};
|
||||
}
|
||||
|
||||
str_t strInitLen(arena_t *arena, const char *buf, usize len) {
|
||||
if (!buf || !len) return (str_t){0};
|
||||
|
||||
str_t out = {
|
||||
.buf = alloc(arena, char, len + 1),
|
||||
.len = len
|
||||
};
|
||||
}
|
||||
|
||||
str_t strFromStr(const char *cstr) {
|
||||
return cstr ? strFromBuf(cstr, strlen(cstr)) : strInit();
|
||||
}
|
||||
memcpy(out.buf, buf, len);
|
||||
|
||||
str_t strFromView(strview_t view) {
|
||||
return strFromBuf(view.buf, view.len);
|
||||
}
|
||||
|
||||
str_t strFromBuf(const char *buf, usize len) {
|
||||
if (!buf) return strInit();
|
||||
str_t str;
|
||||
str.len = len;
|
||||
str.buf = (char *)malloc(len + 1);
|
||||
memcpy(str.buf, buf, len);
|
||||
str.buf[len] = '\0';
|
||||
return str;
|
||||
}
|
||||
|
||||
str_t strFromFmt(const char *fmt, ...) {
|
||||
str_ostream_t out = ostrInit();
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
ostrPrintfV(&out, fmt, va);
|
||||
va_end(va);
|
||||
return ostrAsStr(out);
|
||||
}
|
||||
|
||||
void strFree(str_t ctx) {
|
||||
free(ctx.buf);
|
||||
}
|
||||
|
||||
str_t strFromWCHAR(const wchar_t *src, usize len) {
|
||||
if(len == 0) len = wcslen(src);
|
||||
|
||||
#ifdef _WIN32
|
||||
// TODO CP_ACP should be CP_UTF8 but if i put CP_UTF8 it doesn't work??
|
||||
int result_len = WideCharToMultiByte(
|
||||
CP_ACP, 0,
|
||||
src, (int)len,
|
||||
NULL, 0,
|
||||
NULL, NULL
|
||||
);
|
||||
char *buf = (char *)malloc(result_len + 1);
|
||||
if(buf) {
|
||||
WideCharToMultiByte(
|
||||
CP_ACP, 0,
|
||||
src, (int)len,
|
||||
buf, result_len,
|
||||
NULL, NULL
|
||||
);
|
||||
buf[result_len] = '\0';
|
||||
}
|
||||
return (str_t) {
|
||||
.buf = buf,
|
||||
.len = result_len
|
||||
};
|
||||
#else
|
||||
usize actual_len = len * sizeof(wchar_t);
|
||||
|
||||
usize dest_len = len * 6;
|
||||
char *dest = (char *)malloc(dest_len);
|
||||
|
||||
iconv_t cd = iconv_open("UTF-8", "WCHAR_T");
|
||||
assert(cd);
|
||||
|
||||
usize dest_left = dest_len;
|
||||
char *dest_temp = dest;
|
||||
char *src_temp = (char*)src;
|
||||
usize lost = iconv(cd, &src_temp, &actual_len, &dest_temp, &dest_left);
|
||||
assert(lost != ((usize)-1));
|
||||
(void)lost;
|
||||
|
||||
dest_len -= dest_left;
|
||||
dest = (char *)realloc(dest, dest_len + 1);
|
||||
dest[dest_len] = '\0';
|
||||
|
||||
iconv_close(cd);
|
||||
|
||||
return (str_t){
|
||||
.buf = dest,
|
||||
.len = dest_len
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
wchar_t *strToWCHAR(str_t ctx) {
|
||||
#ifdef _WIN32
|
||||
UINT codepage = CP_ACP;
|
||||
int len = MultiByteToWideChar(
|
||||
codepage, 0,
|
||||
ctx.buf, (int)ctx.len,
|
||||
NULL, 0
|
||||
);
|
||||
wchar_t *str = (wchar_t *)malloc(sizeof(wchar_t) * (len + 1));
|
||||
if(!str) return NULL;
|
||||
len = MultiByteToWideChar(
|
||||
codepage, 0,
|
||||
ctx.buf, (int)ctx.len,
|
||||
str, len
|
||||
);
|
||||
str[len] = '\0';
|
||||
return str;
|
||||
#else
|
||||
usize dest_len = ctx.len * sizeof(wchar_t);
|
||||
char *dest = (char *)malloc(dest_len);
|
||||
|
||||
iconv_t cd = iconv_open("WCHAR_T", "UTF-8");
|
||||
assert(cd);
|
||||
|
||||
usize dest_left = dest_len;
|
||||
char *dest_temp = dest;
|
||||
char *src_temp = ctx.buf;
|
||||
usize lost = iconv(cd, &src_temp, &ctx.len, &dest_temp, &dest_left);
|
||||
assert(lost != ((usize)-1));
|
||||
(void)lost;
|
||||
|
||||
dest_len -= dest_left;
|
||||
dest = (char *)realloc(dest, dest_len + 1);
|
||||
dest[dest_len] = '\0';
|
||||
|
||||
iconv_close(cd);
|
||||
|
||||
return (wchar_t *)dest;
|
||||
#endif
|
||||
}
|
||||
|
||||
str_t strDup(str_t ctx) {
|
||||
return strFromBuf(ctx.buf, ctx.len);
|
||||
}
|
||||
|
||||
str_t strMove(str_t *ctx) {
|
||||
str_t out = *ctx;
|
||||
ctx->buf = NULL;
|
||||
ctx->len = 0;
|
||||
return out;
|
||||
}
|
||||
|
||||
strview_t strGetView(str_t ctx) {
|
||||
return (strview_t) {
|
||||
.buf = ctx.buf,
|
||||
.len = ctx.len
|
||||
};
|
||||
str_t strInitView(arena_t *arena, strview_t view) {
|
||||
return strInitLen(arena, view.buf, view.len);
|
||||
}
|
||||
|
||||
char *strBegin(str_t ctx) {
|
||||
return ctx.buf;
|
||||
str_t strFmt(arena_t *arena, const char *fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
str_t out = strFmtv(arena, fmt, args);
|
||||
va_end(args);
|
||||
return out;
|
||||
}
|
||||
|
||||
char *strEnd(str_t ctx) {
|
||||
return ctx.buf ? ctx.buf + ctx.len : NULL;
|
||||
str_t strFmtv(arena_t *arena, const char *fmt, va_list args) {
|
||||
va_list vcopy;
|
||||
va_copy(vcopy, args);
|
||||
int len = fmtBufferv(NULL, 0, fmt, vcopy);
|
||||
va_end(vcopy);
|
||||
|
||||
char *buffer = alloc(arena, char, len + 1);
|
||||
fmtBufferv(buffer, len + 1, fmt, args);
|
||||
|
||||
return (str_t){ .buf = buffer, .len = (usize)len };
|
||||
}
|
||||
|
||||
char strBack(str_t ctx) {
|
||||
return ctx.buf ? ctx.buf[ctx.len - 1] : '\0';
|
||||
str_t strFromWChar(arena_t *arena, const wchar_t *src, usize srclen) {
|
||||
if (!src) return (str_t){0};
|
||||
if (!srclen) srclen = wcslen(src);
|
||||
|
||||
str_t out = {0};
|
||||
|
||||
#if COLLA_WIN
|
||||
int outlen = WideCharToMultiByte(
|
||||
CP_UTF8, 0,
|
||||
src, (int)srclen,
|
||||
NULL, 0,
|
||||
NULL, NULL
|
||||
);
|
||||
|
||||
if (outlen == 0) {
|
||||
unsigned long error = GetLastError();
|
||||
if (error == ERROR_NO_UNICODE_TRANSLATION) {
|
||||
err("couldn't translate wide string (%S) to utf8, no unicode translation", src);
|
||||
}
|
||||
else {
|
||||
err("couldn't translate wide string (%S) to utf8, %u", error);
|
||||
}
|
||||
|
||||
return (str_t){0};
|
||||
}
|
||||
|
||||
out.buf = alloc(arena, char, outlen + 1);
|
||||
WideCharToMultiByte(
|
||||
CP_UTF8, 0,
|
||||
src, (int)srclen,
|
||||
out.buf, outlen,
|
||||
NULL, NULL
|
||||
);
|
||||
|
||||
#elif COLLA_LIN
|
||||
fatal("strFromWChar not implemented yet!");
|
||||
#endif
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
bool strEquals(str_t a, str_t b) {
|
||||
return strCompare(a, b) == 0;
|
||||
}
|
||||
|
||||
int strCompare(str_t a, str_t b) {
|
||||
return a.len == b.len ?
|
||||
memcmp(a.buf, b.buf, a.len) :
|
||||
a.len - (int)b.len;
|
||||
}
|
||||
|
||||
str_t strDup(arena_t *arena, str_t src) {
|
||||
return strInitLen(arena, src.buf, src.len);
|
||||
}
|
||||
|
||||
bool strIsEmpty(str_t ctx) {
|
||||
return ctx.len == 0;
|
||||
}
|
||||
|
||||
void strSwap(str_t *ctx, str_t *other) {
|
||||
char *buf = other->buf;
|
||||
usize len = other->len;
|
||||
other->buf = ctx->buf;
|
||||
other->len = ctx->len;
|
||||
ctx->buf = buf;
|
||||
ctx->len = len;
|
||||
return ctx.len == 0 || ctx.buf == NULL;
|
||||
}
|
||||
|
||||
void strReplace(str_t *ctx, char from, char to) {
|
||||
for(usize i = 0; i < ctx->len; ++i) {
|
||||
if(ctx->buf[i] == from) {
|
||||
ctx->buf[i] = to;
|
||||
}
|
||||
if (!ctx) return;
|
||||
char *buf = ctx->buf;
|
||||
for (usize i = 0; i < ctx->len; ++i) {
|
||||
buf[i] = buf[i] == from ? to : buf[i];
|
||||
}
|
||||
}
|
||||
|
||||
str_t strSubstr(str_t ctx, usize from, usize to) {
|
||||
if(strIsEmpty(ctx)) return strInit();
|
||||
strview_t strSub(str_t ctx, usize from, usize to) {
|
||||
if (to > ctx.len) to = ctx.len;
|
||||
if (from > to) from = to;
|
||||
return strFromBuf(ctx.buf + from, to - from);
|
||||
}
|
||||
|
||||
strview_t strSubview(str_t ctx, usize from, usize to) {
|
||||
if(strIsEmpty(ctx)) return strvInit(NULL);
|
||||
if (to > ctx.len) to = ctx.len;
|
||||
if (from > to) from = to;
|
||||
return strvInitLen(ctx.buf + from, to - from);
|
||||
if (from > to) from = to;
|
||||
return (strview_t){ ctx.buf + from, to - from };
|
||||
}
|
||||
|
||||
void strLower(str_t *ctx) {
|
||||
for(usize i = 0; i < ctx->len; ++i) {
|
||||
ctx->buf[i] = (char)tolower(ctx->buf[i]);
|
||||
char *buf = ctx->buf;
|
||||
for (usize i = 0; i < ctx->len; ++i) {
|
||||
buf[i] = (buf[i] >= 'A' && buf[i] <= 'Z') ?
|
||||
buf[i] += 'a' - 'A' :
|
||||
buf[i];
|
||||
}
|
||||
}
|
||||
|
||||
str_t strToLower(str_t ctx) {
|
||||
str_t str = strDup(ctx);
|
||||
strLower(&str);
|
||||
return str;
|
||||
}
|
||||
|
||||
void strUpper(str_t *ctx) {
|
||||
for(usize i = 0; i < ctx->len; ++i) {
|
||||
ctx->buf[i] = (char)toupper(ctx->buf[i]);
|
||||
char *buf = ctx->buf;
|
||||
for (usize i = 0; i < ctx->len; ++i) {
|
||||
buf[i] = (buf[i] >= 'a' && buf[i] <= 'z') ?
|
||||
buf[i] -= 'a' - 'A' :
|
||||
buf[i];
|
||||
}
|
||||
}
|
||||
|
||||
str_t strToUpper(str_t ctx) {
|
||||
str_t str = strDup(ctx);
|
||||
strUpper(&str);
|
||||
return str;
|
||||
str_t strToLower(arena_t *arena, str_t ctx) {
|
||||
strLower(&ctx);
|
||||
return strDup(arena, ctx);
|
||||
}
|
||||
|
||||
str_t strToUpper(arena_t *arena, str_t ctx) {
|
||||
strUpper(&ctx);
|
||||
return strDup(arena, ctx);
|
||||
}
|
||||
|
||||
|
||||
// == STRVIEW_T ====================================================
|
||||
|
||||
strview_t strvInit(const char *cstr) {
|
||||
return strvInitLen(cstr, cstr ? strlen(cstr) : 0);
|
||||
}
|
||||
|
||||
strview_t strvInitStr(str_t str) {
|
||||
return strvInitLen(str.buf, str.len);
|
||||
}
|
||||
|
||||
strview_t strvInitLen(const char *buf, usize size) {
|
||||
return (strview_t) {
|
||||
.buf = buf,
|
||||
.len = size
|
||||
return (strview_t){
|
||||
.buf = cstr,
|
||||
.len = cstr ? strlen(cstr) : 0,
|
||||
};
|
||||
}
|
||||
|
||||
char strvFront(strview_t ctx) {
|
||||
return ctx.buf[0];
|
||||
strview_t strvInitLen(const char *buf, usize size) {
|
||||
return (strview_t){
|
||||
.buf = buf,
|
||||
.len = size,
|
||||
};
|
||||
}
|
||||
|
||||
char strvBack(strview_t ctx) {
|
||||
return ctx.buf[ctx.len - 1];
|
||||
strview_t strvInitStr(str_t str) {
|
||||
return (strview_t){
|
||||
.buf = str.buf,
|
||||
.len = str.len
|
||||
};
|
||||
}
|
||||
|
||||
const char *strvBegin(strview_t ctx) {
|
||||
return ctx.buf;
|
||||
}
|
||||
|
||||
const char *strvEnd(strview_t ctx) {
|
||||
return ctx.buf + ctx.len;
|
||||
}
|
||||
|
||||
bool strvIsEmpty(strview_t ctx) {
|
||||
return ctx.len == 0;
|
||||
return ctx.len == 0 || !ctx.buf;
|
||||
}
|
||||
|
||||
bool strvEquals(strview_t a, strview_t b) {
|
||||
return strvCompare(a, b) == 0;
|
||||
}
|
||||
|
||||
int strvCompare(strview_t a, strview_t b) {
|
||||
return a.len == b.len ?
|
||||
memcmp(a.buf, b.buf, a.len) :
|
||||
a.len - (int)b.len;
|
||||
}
|
||||
|
||||
wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen) {
|
||||
wchar_t *out = NULL;
|
||||
int len = 0;
|
||||
|
||||
if (strvIsEmpty(ctx)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
#if COLLA_WIN
|
||||
len = MultiByteToWideChar(
|
||||
CP_UTF8, 0,
|
||||
ctx.buf, (int)ctx.len,
|
||||
NULL, 0
|
||||
);
|
||||
|
||||
if (len == 0) {
|
||||
unsigned long error = GetLastError();
|
||||
if (error == ERROR_NO_UNICODE_TRANSLATION) {
|
||||
err("couldn't translate string (%v) to a wide string, no unicode translation", ctx);
|
||||
}
|
||||
else {
|
||||
err("couldn't translate string (%v) to a wide string, %u", ctx, error);
|
||||
}
|
||||
|
||||
goto error;
|
||||
}
|
||||
|
||||
out = alloc(arena, wchar_t, len + 1);
|
||||
|
||||
MultiByteToWideChar(
|
||||
CP_UTF8, 0,
|
||||
ctx.buf, (int)ctx.len,
|
||||
out, len
|
||||
);
|
||||
|
||||
#elif COLLA_LIN
|
||||
fatal("strFromWChar not implemented yet!");
|
||||
#endif
|
||||
|
||||
error:
|
||||
if (outlen) {
|
||||
*outlen = (usize)len;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
TCHAR *strvToTChar(arena_t *arena, strview_t str) {
|
||||
#if UNICODE
|
||||
return strvToWChar(arena, str, NULL);
|
||||
#else
|
||||
char *cstr = alloc(arena, char, str.len + 1);
|
||||
memcpy(cstr, str.buf, str.len);
|
||||
return cstr;
|
||||
#endif
|
||||
}
|
||||
|
||||
strview_t strvRemovePrefix(strview_t ctx, usize n) {
|
||||
if (n > ctx.len) n = ctx.len;
|
||||
return (strview_t){
|
||||
.buf = ctx.buf + n,
|
||||
.len = ctx.len - n
|
||||
.len = ctx.len - n,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -293,7 +261,7 @@ strview_t strvRemoveSuffix(strview_t ctx, usize n) {
|
|||
if (n > ctx.len) n = ctx.len;
|
||||
return (strview_t){
|
||||
.buf = ctx.buf,
|
||||
.len = ctx.len - n
|
||||
.len = ctx.len - n,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -303,334 +271,115 @@ strview_t strvTrim(strview_t ctx) {
|
|||
|
||||
strview_t strvTrimLeft(strview_t ctx) {
|
||||
strview_t out = ctx;
|
||||
for (usize i = 0; i < ctx.len && isspace(ctx.buf[i]); ++i) {
|
||||
++out.buf;
|
||||
--out.len;
|
||||
for (usize i = 0; i < ctx.len; ++i) {
|
||||
char c = ctx.buf[i];
|
||||
if (c != ' ' || c < '\t' || c > '\r') {
|
||||
break;
|
||||
}
|
||||
out.buf++;
|
||||
out.len--;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
strview_t strvTrimRight(strview_t ctx) {
|
||||
strview_t out = ctx;
|
||||
for (isize i = ctx.len - 1; i >= 0 && isspace(ctx.buf[i]); --i) {
|
||||
--out.len;
|
||||
for (isize i = ctx.len - 1; i >= 0; --i) {
|
||||
char c = ctx.buf[i];
|
||||
if (c != ' ' || c < '\t' || c > '\r') {
|
||||
break;
|
||||
}
|
||||
out.len--;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
str_t strvCopy(strview_t ctx) {
|
||||
return strFromView(ctx);
|
||||
}
|
||||
|
||||
str_t strvCopyN(strview_t ctx, usize count, usize from) {
|
||||
usize sz = ctx.len + 1 - from;
|
||||
count = min(count, sz);
|
||||
return strFromBuf(ctx.buf + from, count);
|
||||
}
|
||||
|
||||
usize strvCopyBuf(strview_t ctx, char *buf, usize len, usize from) {
|
||||
usize sz = ctx.len + 1 - from;
|
||||
len = min(len, sz);
|
||||
memcpy(buf, ctx.buf + from, len);
|
||||
buf[len - 1] = '\0';
|
||||
return len - 1;
|
||||
}
|
||||
|
||||
strview_t strvSub(strview_t ctx, usize from, usize to) {
|
||||
if (to > ctx.len) to = ctx.len;
|
||||
if (from > to) from = to;
|
||||
return strvInitLen(ctx.buf + from, to - from);
|
||||
}
|
||||
|
||||
int strvCompare(strview_t ctx, strview_t other) {
|
||||
if(ctx.len < other.len) return -1;
|
||||
if(ctx.len > other.len) return 1;
|
||||
return memcmp(ctx.buf, other.buf, ctx.len);
|
||||
}
|
||||
|
||||
int strvICompare(strview_t ctx, strview_t other) {
|
||||
if(ctx.len < other.len) return -1;
|
||||
if(ctx.len > other.len) return 1;
|
||||
for(usize i = 0; i < ctx.len; ++i) {
|
||||
int a = tolower(ctx.buf[i]);
|
||||
int b = tolower(other.buf[i]);
|
||||
if(a != b) return a - b;
|
||||
}
|
||||
return 0;
|
||||
return (strview_t){ ctx.buf + from, to - from };
|
||||
}
|
||||
|
||||
bool strvStartsWith(strview_t ctx, char c) {
|
||||
return strvFront(ctx) == c;
|
||||
return ctx.len > 0 && ctx.buf[0] == c;
|
||||
}
|
||||
|
||||
bool strvStartsWithView(strview_t ctx, strview_t view) {
|
||||
if(ctx.len < view.len) return false;
|
||||
return memcmp(ctx.buf, view.buf, view.len) == 0;
|
||||
return ctx.len >= view.len && memcmp(ctx.buf, view.buf, view.len) == 0;
|
||||
}
|
||||
|
||||
bool strvEndsWith(strview_t ctx, char c) {
|
||||
return strvBack(ctx) == c;
|
||||
return ctx.len > 0 && ctx.buf[ctx.len - 1] == c;
|
||||
}
|
||||
|
||||
bool strvEndsWithView(strview_t ctx, strview_t view) {
|
||||
if(ctx.len < view.len) return false;
|
||||
return memcmp(ctx.buf + ctx.len - view.len, view.buf, view.len) == 0;
|
||||
return ctx.len >= view.len && memcmp(ctx.buf + ctx.len, view.buf, view.len) == 0;
|
||||
}
|
||||
|
||||
bool strvContains(strview_t ctx, char c) {
|
||||
for(usize i = 0; i < ctx.len; ++i) {
|
||||
if(ctx.buf[i] == c) return true;
|
||||
if(ctx.buf[i] == c) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool strvContainsView(strview_t ctx, strview_t view) {
|
||||
if(ctx.len < view.len) return false;
|
||||
if (ctx.len < view.len) return false;
|
||||
usize end = ctx.len - view.len;
|
||||
for(usize i = 0; i < end; ++i) {
|
||||
if(memcmp(ctx.buf + i, view.buf, view.len) == 0) return true;
|
||||
for (usize i = 0; i < end; ++i) {
|
||||
if (memcmp(ctx.buf + i, view.buf, view.len) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
usize strvFind(strview_t ctx, char c, usize from) {
|
||||
for(usize i = from; i < ctx.len; ++i) {
|
||||
if(ctx.buf[i] == c) return i;
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
usize strvFindView(strview_t ctx, strview_t view, usize from) {
|
||||
if(ctx.len < view.len) return SIZE_MAX;
|
||||
usize end = ctx.len - view.len;
|
||||
for(usize i = from; i < end; ++i) {
|
||||
if(memcmp(ctx.buf + i, view.buf, view.len) == 0) return i;
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
usize strvRFind(strview_t ctx, char c, usize from) {
|
||||
if(from >= ctx.len) {
|
||||
from = ctx.len;
|
||||
}
|
||||
|
||||
from = ctx.len - from;
|
||||
|
||||
const char *buf = ctx.buf + from;
|
||||
for(; buf >= ctx.buf; --buf) {
|
||||
if(*buf == c) return (buf - ctx.buf);
|
||||
}
|
||||
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
usize strvRFindView(strview_t ctx, strview_t view, usize from) {
|
||||
if(view.len > ctx.len) {
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
if(from > ctx.len) {
|
||||
from = ctx.len;
|
||||
}
|
||||
|
||||
from = ctx.len - from;
|
||||
from -= view.len;
|
||||
|
||||
const char *buf = ctx.buf + from;
|
||||
for(; buf >= ctx.buf; --buf) {
|
||||
if(memcmp(buf, view.buf, view.len) == 0) return (buf - ctx.buf);
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
usize strvFindFirstOf(strview_t ctx, strview_t view, usize from) {
|
||||
if(ctx.len < view.len) return SIZE_MAX;
|
||||
for(usize i = from; i < ctx.len; ++i) {
|
||||
for(usize j = 0; j < view.len; ++j) {
|
||||
if(ctx.buf[i] == view.buf[j]) return i;
|
||||
}
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
usize strvFindLastOf(strview_t ctx, strview_t view, usize from) {
|
||||
if(from >= ctx.len) {
|
||||
from = ctx.len - 1;
|
||||
}
|
||||
|
||||
const char *buf = ctx.buf + from;
|
||||
for(; buf >= ctx.buf; --buf) {
|
||||
for(usize j = 0; j < view.len; ++j) {
|
||||
if(*buf == view.buf[j]) return (buf - ctx.buf);
|
||||
}
|
||||
}
|
||||
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
usize strvFindFirstNot(strview_t ctx, char c, usize from) {
|
||||
usize end = ctx.len - 1;
|
||||
for(usize i = from; i < end; ++i) {
|
||||
if(ctx.buf[i] != c) return i;
|
||||
}
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
usize strvFindFirstNotOf(strview_t ctx, strview_t view, usize from) {
|
||||
for(usize i = from; i < ctx.len; ++i) {
|
||||
if(!strvContains(view, ctx.buf[i])) {
|
||||
for (usize i = from; i < ctx.len; ++i) {
|
||||
if (ctx.buf[i] == c) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return SIZE_MAX;
|
||||
return STR_NONE;
|
||||
}
|
||||
|
||||
usize strvFindLastNot(strview_t ctx, char c, usize from) {
|
||||
if(from >= ctx.len) {
|
||||
from = ctx.len - 1;
|
||||
}
|
||||
|
||||
const char *buf = ctx.buf + from;
|
||||
for(; buf >= ctx.buf; --buf) {
|
||||
if(*buf != c) {
|
||||
return buf - ctx.buf;
|
||||
usize strvFindView(strview_t ctx, strview_t view, usize from) {
|
||||
if (ctx.len < view.len) return STR_NONE;
|
||||
usize end = ctx.len - view.len;
|
||||
for (usize i = 0; i < end; ++i) {
|
||||
if (memcmp(ctx.buf + i, view.buf, view.len) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return SIZE_MAX;
|
||||
return STR_NONE;
|
||||
}
|
||||
|
||||
usize strvFindLastNotOf(strview_t ctx, strview_t view, usize from) {
|
||||
if(from >= ctx.len) {
|
||||
from = ctx.len - 1;
|
||||
}
|
||||
|
||||
const char *buf = ctx.buf + from;
|
||||
for(; buf >= ctx.buf; --buf) {
|
||||
if(!strvContains(view, *buf)) {
|
||||
return buf - ctx.buf;
|
||||
usize strvRFind(strview_t ctx, char c, usize from_right) {
|
||||
if (from_right > ctx.len) from_right = ctx.len;
|
||||
isize end = (isize)(ctx.len - from_right);
|
||||
for (isize i = end; i >= 0; --i) {
|
||||
if (ctx.buf[i] == c) {
|
||||
return (usize)i;
|
||||
}
|
||||
}
|
||||
|
||||
return SIZE_MAX;
|
||||
return STR_NONE;
|
||||
}
|
||||
|
||||
#ifdef STR_TESTING
|
||||
#include <stdio.h>
|
||||
#include "tracelog.h"
|
||||
|
||||
void strTest(void) {
|
||||
str_t s;
|
||||
debug("== testing init =================");
|
||||
{
|
||||
s = strInit();
|
||||
printf("%s %zu\n", s.buf, s.len);
|
||||
strFree(&s);
|
||||
s = strInitStr("hello world");
|
||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
||||
strFree(&s);
|
||||
uint8 buf[] = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' };
|
||||
s = strFromBuf((char *)buf, sizeof(buf));
|
||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
||||
strFree(&s);
|
||||
}
|
||||
debug("== testing view =================");
|
||||
{
|
||||
s = strInitStr("hello world");
|
||||
strview_t view = strGetView(&s);
|
||||
printf("\"%.*s\" %zu\n", (int)view.len, view.buf, view.len);
|
||||
strFree(&s);
|
||||
}
|
||||
debug("== testing begin/end ============");
|
||||
{
|
||||
s = strInitStr("hello world");
|
||||
char *beg = strBegin(&s);
|
||||
char *end = strEnd(&s);
|
||||
printf("[ ");
|
||||
for(; beg < end; ++beg) {
|
||||
printf("%c ", *beg);
|
||||
usize strvRFindView(strview_t ctx, strview_t view, usize from_right) {
|
||||
if (from_right > ctx.len) from_right = ctx.len;
|
||||
isize end = (isize)(ctx.len - from_right);
|
||||
if (end < view.len) return STR_NONE;
|
||||
for (isize i = end - view.len; i >= 0; --i) {
|
||||
if (memcmp(ctx.buf + i, view.buf, view.len) == 0) {
|
||||
return (usize)i;
|
||||
}
|
||||
printf("]\n");
|
||||
strFree(&s);
|
||||
}
|
||||
debug("== testing back/isempty =========");
|
||||
{
|
||||
s = strInitStr("hello world");
|
||||
printf("[ ");
|
||||
while(!strIsEmpty(&s)) {
|
||||
printf("%c ", strBack(&s));
|
||||
strPop(&s);
|
||||
}
|
||||
printf("]\n");
|
||||
strFree(&s);
|
||||
}
|
||||
debug("== testing append ===============");
|
||||
{
|
||||
s = strInitStr("hello ");
|
||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
||||
strAppend(&s, "world");
|
||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
||||
strAppendView(&s, strvInit(", how is it "));
|
||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
||||
uint8 buf[] = { 'g', 'o', 'i', 'n', 'g' };
|
||||
strAppendBuf(&s, (char*)buf, sizeof(buf));
|
||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
||||
strAppendChars(&s, '?', 2);
|
||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
||||
strFree(&s);
|
||||
}
|
||||
debug("== testing push/pop =============");
|
||||
{
|
||||
s = strInit();
|
||||
str_t s2 = strInitStr("hello world");
|
||||
|
||||
printf("%-14s %-14s\n", "s", "s2");
|
||||
printf("----------------------------\n");
|
||||
while(!strIsEmpty(&s2)) {
|
||||
printf("%-14s %-14s\n", s.buf, s2.buf);
|
||||
strPush(&s, strPop(&s2));
|
||||
}
|
||||
printf("%-14s %-14s\n", s.buf, s2.buf);
|
||||
|
||||
strFree(&s);
|
||||
strFree(&s2);
|
||||
}
|
||||
debug("== testing swap =================");
|
||||
{
|
||||
s = strInitStr("hello");
|
||||
str_t s2 = strInitStr("world");
|
||||
printf("%-8s %-8s\n", "s", "s2");
|
||||
printf("----------------\n");
|
||||
printf("%-8s %-8s\n", s.buf, s2.buf);
|
||||
strSwap(&s, &s2);
|
||||
printf("%-8s %-8s\n", s.buf, s2.buf);
|
||||
|
||||
strFree(&s);
|
||||
strFree(&s2);
|
||||
}
|
||||
debug("== testing substr ===============");
|
||||
{
|
||||
s = strInitStr("hello world");
|
||||
printf("s: %s\n", s.buf);
|
||||
|
||||
printf("-- string\n");
|
||||
str_t s2 = strSubstr(&s, 0, 5);
|
||||
printf("0..5: \"%s\"\n", s2.buf);
|
||||
strFree(&s2);
|
||||
|
||||
s2 = strSubstr(&s, 5, SIZE_MAX);
|
||||
printf("6..SIZE_MAX: \"%s\"\n", s2.buf);
|
||||
strFree(&s2);
|
||||
|
||||
printf("-- view\n");
|
||||
strview_t v = strSubview(&s, 0, 5);
|
||||
printf("0..5: \"%.*s\"\n", (int)v.len, v.buf);
|
||||
v = strSubview(&s, 5, SIZE_MAX);
|
||||
printf("6..SIZE_MAX: \"%.*s\"\n", (int)v.len, v.buf);
|
||||
|
||||
strFree(&s);
|
||||
}
|
||||
|
||||
strFree(&s);
|
||||
return STR_NONE;
|
||||
}
|
||||
#endif
|
||||
|
||||
#include "warnings/colla_warn_beg.h"
|
||||
|
||||
#undef CP_UTF8
|
||||
#undef ERROR_NO_UNICODE_TRANSLATION
|
||||
129
colla/str.h
129
colla/str.h
|
|
@ -1,19 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <limits.h>
|
||||
#include <wchar.h>
|
||||
#include <stdarg.h> // va_list
|
||||
#include <string.h> // strlen
|
||||
|
||||
#include "collatypes.h"
|
||||
|
||||
#define STRV_NOT_FOUND SIZE_MAX
|
||||
typedef struct arena_t arena_t;
|
||||
|
||||
typedef struct str_t {
|
||||
#define STR_NONE SIZE_MAX
|
||||
|
||||
typedef struct {
|
||||
char *buf;
|
||||
usize len;
|
||||
} str_t;
|
||||
|
|
@ -25,73 +21,77 @@ typedef struct {
|
|||
|
||||
// == STR_T ========================================================
|
||||
|
||||
str_t strInit(void);
|
||||
str_t strFromStr(const char *cstr);
|
||||
str_t strFromView(strview_t view);
|
||||
str_t strFromBuf(const char *buf, usize len);
|
||||
str_t strFromFmt(const char *fmt, ...);
|
||||
#define str__1(arena, x) \
|
||||
_Generic((x), \
|
||||
const char *: strInit, \
|
||||
char *: strInit, \
|
||||
strview_t: strInitView \
|
||||
)(arena, x)
|
||||
|
||||
str_t strFromWCHAR(const wchar_t *src, usize len);
|
||||
wchar_t *strToWCHAR(str_t ctx);
|
||||
#define str__2(arena, cstr, clen) strInitLen(arena, cstr, clen)
|
||||
#define str__impl(_1, _2, n, ...) str__##n
|
||||
|
||||
void strFree(str_t ctx);
|
||||
str_t strDup(str_t ctx);
|
||||
str_t strMove(str_t *ctx);
|
||||
// either:
|
||||
// arena_t arena, [const] char *cstr, [usize len]
|
||||
// arena_t arena, strview_t view
|
||||
#define str(arena, ...) str__impl(__VA_ARGS__, 2, 1, 0)(arena, __VA_ARGS__)
|
||||
|
||||
strview_t strGetView(str_t ctx);
|
||||
str_t strInit(arena_t *arena, const char *buf);
|
||||
str_t strInitLen(arena_t *arena, const char *buf, usize len);
|
||||
str_t strInitView(arena_t *arena, strview_t view);
|
||||
str_t strFmt(arena_t *arena, const char *fmt, ...);
|
||||
str_t strFmtv(arena_t *arena, const char *fmt, va_list args);
|
||||
|
||||
char *strBegin(str_t ctx);
|
||||
char *strEnd(str_t ctx);
|
||||
char strBack(str_t ctx);
|
||||
str_t strFromWChar(arena_t *arena, const wchar_t *src, usize srclen);
|
||||
|
||||
bool strEquals(str_t a, str_t b);
|
||||
int strCompare(str_t a, str_t b);
|
||||
|
||||
str_t strDup(arena_t *arena, str_t src);
|
||||
bool strIsEmpty(str_t ctx);
|
||||
void strReplace(str_t *ctx, char from, char to);
|
||||
|
||||
void strReplace(str_t *ctx, char from, char to);
|
||||
// if len == SIZE_MAX, copies until end
|
||||
str_t strSubstr(str_t ctx, usize from, usize to);
|
||||
// if len == SIZE_MAX, returns until end
|
||||
strview_t strSubview(str_t ctx, usize from, usize to);
|
||||
strview_t strSub(str_t ctx, usize from, usize to);
|
||||
|
||||
void strLower(str_t *ctx);
|
||||
str_t strToLower(str_t ctx);
|
||||
|
||||
void strUpper(str_t *ctx);
|
||||
str_t strToUpper(str_t ctx);
|
||||
|
||||
#ifdef STR_TESTING
|
||||
void strTest(void);
|
||||
#endif
|
||||
str_t strToLower(arena_t *arena, str_t ctx);
|
||||
str_t strToUpper(arena_t *arena, str_t ctx);
|
||||
|
||||
// == STRVIEW_T ====================================================
|
||||
|
||||
strview_t strvInit(const char *cstr);
|
||||
strview_t strvInitStr(str_t str);
|
||||
strview_t strvInitLen(const char *buf, usize size);
|
||||
#define strv__1(x) \
|
||||
_Generic((x), \
|
||||
char *: strvInit, \
|
||||
const char *: strvInit, \
|
||||
str_t: strvInitStr \
|
||||
)(x)
|
||||
|
||||
char strvFront(strview_t ctx);
|
||||
char strvBack(strview_t ctx);
|
||||
const char *strvBegin(strview_t ctx);
|
||||
const char *strvEnd(strview_t ctx);
|
||||
// move view forward by n characters
|
||||
strview_t strvRemovePrefix(strview_t ctx, usize n);
|
||||
// move view backwards by n characters
|
||||
strview_t strvRemoveSuffix(strview_t ctx, usize n);
|
||||
// removes whitespace from the beginning and the end
|
||||
strview_t strvTrim(strview_t ctx);
|
||||
// removes whitespace from the beginning
|
||||
strview_t strvTrimLeft(strview_t ctx);
|
||||
// removes whitespace from the end
|
||||
strview_t strvTrimRight(strview_t ctx);
|
||||
#define strv__2(cstr, clen) strvInitLen(cstr, clen)
|
||||
#define strv__impl(_1, _2, n, ...) strv__##n
|
||||
|
||||
#define strv(...) strv__impl(__VA_ARGS__, 2, 1, 0)(__VA_ARGS__)
|
||||
|
||||
strview_t strvInit(const char *cstr);
|
||||
strview_t strvInitLen(const char *buf, usize size);
|
||||
strview_t strvInitStr(str_t str);
|
||||
|
||||
bool strvIsEmpty(strview_t ctx);
|
||||
bool strvEquals(strview_t a, strview_t b);
|
||||
int strvCompare(strview_t a, strview_t b);
|
||||
|
||||
str_t strvCopy(strview_t ctx);
|
||||
str_t strvCopyN(strview_t ctx, usize count, usize from);
|
||||
usize strvCopyBuf(strview_t ctx, char *buf, usize len, usize from);
|
||||
wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen);
|
||||
TCHAR *strvToTChar(arena_t *arena, strview_t str);
|
||||
|
||||
strview_t strvRemovePrefix(strview_t ctx, usize n);
|
||||
strview_t strvRemoveSuffix(strview_t ctx, usize n);
|
||||
strview_t strvTrim(strview_t ctx);
|
||||
strview_t strvTrimLeft(strview_t ctx);
|
||||
strview_t strvTrimRight(strview_t ctx);
|
||||
|
||||
strview_t strvSub(strview_t ctx, usize from, usize to);
|
||||
int strvCompare(strview_t ctx, strview_t other);
|
||||
int strvICompare(strview_t ctx, strview_t other);
|
||||
|
||||
bool strvStartsWith(strview_t ctx, char c);
|
||||
bool strvStartsWithView(strview_t ctx, strview_t view);
|
||||
|
|
@ -105,18 +105,5 @@ bool strvContainsView(strview_t ctx, strview_t view);
|
|||
usize strvFind(strview_t ctx, char c, usize from);
|
||||
usize strvFindView(strview_t ctx, strview_t view, usize from);
|
||||
|
||||
usize strvRFind(strview_t ctx, char c, usize from);
|
||||
usize strvRFindView(strview_t ctx, strview_t view, usize from);
|
||||
|
||||
// Finds the first occurrence of any of the characters of 'view' in this view
|
||||
usize strvFindFirstOf(strview_t ctx, strview_t view, usize from);
|
||||
usize strvFindLastOf(strview_t ctx, strview_t view, usize from);
|
||||
|
||||
usize strvFindFirstNot(strview_t ctx, char c, usize from);
|
||||
usize strvFindFirstNotOf(strview_t ctx, strview_t view, usize from);
|
||||
usize strvFindLastNot(strview_t ctx, char c, usize from);
|
||||
usize strvFindLastNotOf(strview_t ctx, strview_t view, usize from);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
usize strvRFind(strview_t ctx, char c, usize from_right);
|
||||
usize strvRFindView(strview_t ctx, strview_t view, usize from_right);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#include "strstream.h"
|
||||
|
||||
#include "warnings/colla_warn_beg.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
|
|
@ -7,9 +9,11 @@
|
|||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <math.h> // HUGE_VALF
|
||||
#include "tracelog.h"
|
||||
|
||||
#if defined(_WIN32) && defined(__TINYC__)
|
||||
#include "tracelog.h"
|
||||
#include "arena.h"
|
||||
|
||||
#if COLLA_WIN && COLLA_TCC
|
||||
#define strtoull _strtoui64
|
||||
#define strtoll _strtoi64
|
||||
#define strtof strtod
|
||||
|
|
@ -17,39 +21,48 @@
|
|||
|
||||
/* == INPUT STREAM ============================================ */
|
||||
|
||||
str_istream_t istrInit(const char *str) {
|
||||
instream_t istrInit(const char *str) {
|
||||
return istrInitLen(str, strlen(str));
|
||||
}
|
||||
|
||||
str_istream_t istrInitLen(const char *str, usize len) {
|
||||
str_istream_t res;
|
||||
instream_t istrInitLen(const char *str, usize len) {
|
||||
instream_t res;
|
||||
res.start = res.cur = str;
|
||||
res.size = len;
|
||||
return res;
|
||||
}
|
||||
|
||||
char istrGet(str_istream_t *ctx) {
|
||||
char istrGet(instream_t *ctx) {
|
||||
return *ctx->cur++;
|
||||
}
|
||||
|
||||
void istrIgnore(str_istream_t *ctx, char delim) {
|
||||
usize position = ctx->cur - ctx->start;
|
||||
usize i;
|
||||
for(i = position;
|
||||
i < ctx->size && *ctx->cur != delim;
|
||||
++i, ++ctx->cur);
|
||||
void istrIgnore(instream_t *ctx, char delim) {
|
||||
for (; !istrIsFinished(*ctx) && *ctx->cur != delim; ++ctx->cur) {
|
||||
|
||||
}
|
||||
|
||||
//usize position = ctx->cur - ctx->start;
|
||||
//usize i;
|
||||
//for(i = position;
|
||||
// i < ctx->size && *ctx->cur != delim;
|
||||
// ++i, ++ctx->cur);
|
||||
}
|
||||
|
||||
void istrIgnoreAndSkip(str_istream_t *ctx, char delim) {
|
||||
void istrIgnoreAndSkip(instream_t *ctx, char delim) {
|
||||
istrIgnore(ctx, delim);
|
||||
istrSkip(ctx, 1);
|
||||
}
|
||||
|
||||
char istrPeek(str_istream_t *ctx) {
|
||||
char istrPeek(instream_t *ctx) {
|
||||
return *ctx->cur;
|
||||
}
|
||||
|
||||
void istrSkip(str_istream_t *ctx, usize n) {
|
||||
char istrPeekNext(instream_t *ctx) {
|
||||
usize offset = (ctx->cur - ctx->start) + 1;
|
||||
return offset > ctx->size ? '\0' : *(ctx->cur + 1);
|
||||
}
|
||||
|
||||
void istrSkip(instream_t *ctx, usize n) {
|
||||
usize remaining = ctx->size - (ctx->cur - ctx->start);
|
||||
if(n > remaining) {
|
||||
warn("skipping more then remaining: %zu -> %zu", n, remaining);
|
||||
|
|
@ -58,13 +71,13 @@ void istrSkip(str_istream_t *ctx, usize n) {
|
|||
ctx->cur += n;
|
||||
}
|
||||
|
||||
void istrSkipWhitespace(str_istream_t *ctx) {
|
||||
void istrSkipWhitespace(instream_t *ctx) {
|
||||
while (*ctx->cur && isspace(*ctx->cur)) {
|
||||
++ctx->cur;
|
||||
}
|
||||
}
|
||||
|
||||
void istrRead(str_istream_t *ctx, char *buf, usize len) {
|
||||
void istrRead(instream_t *ctx, char *buf, usize len) {
|
||||
usize remaining = ctx->size - (ctx->cur - ctx->start);
|
||||
if(len > remaining) {
|
||||
warn("istrRead: trying to read len %zu from remaining %zu", len, remaining);
|
||||
|
|
@ -74,7 +87,7 @@ void istrRead(str_istream_t *ctx, char *buf, usize len) {
|
|||
ctx->cur += len;
|
||||
}
|
||||
|
||||
usize istrReadMax(str_istream_t *ctx, char *buf, usize len) {
|
||||
usize istrReadMax(instream_t *ctx, char *buf, usize len) {
|
||||
usize remaining = ctx->size - (ctx->cur - ctx->start);
|
||||
len = remaining < len ? remaining : len;
|
||||
memcpy(buf, ctx->cur, len);
|
||||
|
|
@ -82,29 +95,29 @@ usize istrReadMax(str_istream_t *ctx, char *buf, usize len) {
|
|||
return len;
|
||||
}
|
||||
|
||||
void istrRewind(str_istream_t *ctx) {
|
||||
void istrRewind(instream_t *ctx) {
|
||||
ctx->cur = ctx->start;
|
||||
}
|
||||
|
||||
void istrRewindN(str_istream_t *ctx, usize amount) {
|
||||
void istrRewindN(instream_t *ctx, usize amount) {
|
||||
usize remaining = ctx->size - (ctx->cur - ctx->start);
|
||||
if (amount > remaining) amount = remaining;
|
||||
ctx->cur -= amount;
|
||||
}
|
||||
|
||||
usize istrTell(str_istream_t ctx) {
|
||||
usize istrTell(instream_t ctx) {
|
||||
return ctx.cur - ctx.start;
|
||||
}
|
||||
|
||||
usize istrRemaining(str_istream_t ctx) {
|
||||
usize istrRemaining(instream_t ctx) {
|
||||
return ctx.size - (ctx.cur - ctx.start);
|
||||
}
|
||||
|
||||
bool istrIsFinished(str_istream_t ctx) {
|
||||
bool istrIsFinished(instream_t ctx) {
|
||||
return (usize)(ctx.cur - ctx.start) >= ctx.size;
|
||||
}
|
||||
|
||||
bool istrGetbool(str_istream_t *ctx, bool *val) {
|
||||
bool istrGetBool(instream_t *ctx, bool *val) {
|
||||
usize remaining = ctx->size - (ctx->cur - ctx->start);
|
||||
if(strncmp(ctx->cur, "true", remaining) == 0) {
|
||||
*val = true;
|
||||
|
|
@ -117,16 +130,16 @@ bool istrGetbool(str_istream_t *ctx, bool *val) {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool istrGetu8(str_istream_t *ctx, uint8 *val) {
|
||||
bool istrGetU8(instream_t *ctx, uint8 *val) {
|
||||
char *end = NULL;
|
||||
*val = (uint8) strtoul(ctx->cur, &end, 0);
|
||||
|
||||
if(ctx->cur == end) {
|
||||
warn("istrGetu8: no valid conversion could be performed");
|
||||
warn("istrGetU8: no valid conversion could be performed");
|
||||
return false;
|
||||
}
|
||||
else if(*val == UINT8_MAX) {
|
||||
warn("istrGetu8: value read is out of the range of representable values");
|
||||
warn("istrGetU8: value read is out of the range of representable values");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -134,16 +147,16 @@ bool istrGetu8(str_istream_t *ctx, uint8 *val) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool istrGetu16(str_istream_t *ctx, uint16 *val) {
|
||||
bool istrGetU16(instream_t *ctx, uint16 *val) {
|
||||
char *end = NULL;
|
||||
*val = (uint16) strtoul(ctx->cur, &end, 0);
|
||||
|
||||
if(ctx->cur == end) {
|
||||
warn("istrGetu16: no valid conversion could be performed");
|
||||
warn("istrGetU16: no valid conversion could be performed");
|
||||
return false;
|
||||
}
|
||||
else if(*val == UINT16_MAX) {
|
||||
warn("istrGetu16: value read is out of the range of representable values");
|
||||
warn("istrGetU16: value read is out of the range of representable values");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -151,16 +164,16 @@ bool istrGetu16(str_istream_t *ctx, uint16 *val) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool istrGetu32(str_istream_t *ctx, uint32 *val) {
|
||||
bool istrGetU32(instream_t *ctx, uint32 *val) {
|
||||
char *end = NULL;
|
||||
*val = (uint32) strtoul(ctx->cur, &end, 0);
|
||||
|
||||
if(ctx->cur == end) {
|
||||
warn("istrGetu32: no valid conversion could be performed");
|
||||
warn("istrGetU32: no valid conversion could be performed");
|
||||
return false;
|
||||
}
|
||||
else if(*val == UINT32_MAX) {
|
||||
warn("istrGetu32: value read is out of the range of representable values");
|
||||
warn("istrGetU32: value read is out of the range of representable values");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -168,16 +181,16 @@ bool istrGetu32(str_istream_t *ctx, uint32 *val) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool istrGetu64(str_istream_t *ctx, uint64 *val) {
|
||||
bool istrGetU64(instream_t *ctx, uint64 *val) {
|
||||
char *end = NULL;
|
||||
*val = strtoull(ctx->cur, &end, 0);
|
||||
|
||||
if(ctx->cur == end) {
|
||||
warn("istrGetu64: no valid conversion could be performed");
|
||||
warn("istrGetU64: no valid conversion could be performed");
|
||||
return false;
|
||||
}
|
||||
else if(*val == ULLONG_MAX) {
|
||||
warn("istrGetu64: value read is out of the range of representable values");
|
||||
warn("istrGetU64: value read is out of the range of representable values");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -185,16 +198,16 @@ bool istrGetu64(str_istream_t *ctx, uint64 *val) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool istrGeti8(str_istream_t *ctx, int8 *val) {
|
||||
bool istrGetI8(instream_t *ctx, int8 *val) {
|
||||
char *end = NULL;
|
||||
*val = (int8) strtol(ctx->cur, &end, 0);
|
||||
|
||||
if(ctx->cur == end) {
|
||||
warn("istrGeti8: no valid conversion could be performed");
|
||||
warn("istrGetI8: no valid conversion could be performed");
|
||||
return false;
|
||||
}
|
||||
else if(*val == INT8_MAX || *val == INT8_MIN) {
|
||||
warn("istrGeti8: value read is out of the range of representable values");
|
||||
warn("istrGetI8: value read is out of the range of representable values");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -202,16 +215,16 @@ bool istrGeti8(str_istream_t *ctx, int8 *val) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool istrGeti16(str_istream_t *ctx, int16 *val) {
|
||||
bool istrGetI16(instream_t *ctx, int16 *val) {
|
||||
char *end = NULL;
|
||||
*val = (int16) strtol(ctx->cur, &end, 0);
|
||||
|
||||
if(ctx->cur == end) {
|
||||
warn("istrGeti16: no valid conversion could be performed");
|
||||
warn("istrGetI16: no valid conversion could be performed");
|
||||
return false;
|
||||
}
|
||||
else if(*val == INT16_MAX || *val == INT16_MIN) {
|
||||
warn("istrGeti16: value read is out of the range of representable values");
|
||||
warn("istrGetI16: value read is out of the range of representable values");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -219,16 +232,16 @@ bool istrGeti16(str_istream_t *ctx, int16 *val) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool istrGeti32(str_istream_t *ctx, int32 *val) {
|
||||
bool istrGetI32(instream_t *ctx, int32 *val) {
|
||||
char *end = NULL;
|
||||
*val = (int32) strtol(ctx->cur, &end, 0);
|
||||
|
||||
if(ctx->cur == end) {
|
||||
warn("istrGeti32: no valid conversion could be performed");
|
||||
warn("istrGetI32: no valid conversion could be performed");
|
||||
return false;
|
||||
}
|
||||
else if(*val == INT32_MAX || *val == INT32_MIN) {
|
||||
warn("istrGeti32: value read is out of the range of representable values");
|
||||
warn("istrGetI32: value read is out of the range of representable values");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -236,16 +249,16 @@ bool istrGeti32(str_istream_t *ctx, int32 *val) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool istrGeti64(str_istream_t *ctx, int64 *val) {
|
||||
bool istrGetI64(instream_t *ctx, int64 *val) {
|
||||
char *end = NULL;
|
||||
*val = strtoll(ctx->cur, &end, 0);
|
||||
|
||||
if(ctx->cur == end) {
|
||||
warn("istrGeti64: no valid conversion could be performed");
|
||||
warn("istrGetI64: no valid conversion could be performed");
|
||||
return false;
|
||||
}
|
||||
else if(*val == INT64_MAX || *val == INT64_MIN) {
|
||||
warn("istrGeti64: value read is out of the range of representable values");
|
||||
warn("istrGetI64: value read is out of the range of representable values");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -253,16 +266,16 @@ bool istrGeti64(str_istream_t *ctx, int64 *val) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool istrGetfloat(str_istream_t *ctx, float *val) {
|
||||
bool istrGetFloat(instream_t *ctx, float *val) {
|
||||
char *end = NULL;
|
||||
*val = strtof(ctx->cur, &end);
|
||||
|
||||
if(ctx->cur == end) {
|
||||
warn("istrGetfloat: no valid conversion could be performed");
|
||||
warn("istrGetFloat: no valid conversion could be performed");
|
||||
return false;
|
||||
}
|
||||
else if(*val == HUGE_VALF || *val == -HUGE_VALF) {
|
||||
warn("istrGetfloat: value read is out of the range of representable values");
|
||||
warn("istrGetFloat: value read is out of the range of representable values");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -270,16 +283,16 @@ bool istrGetfloat(str_istream_t *ctx, float *val) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool istrGetdouble(str_istream_t *ctx, double *val) {
|
||||
bool istrGetDouble(instream_t *ctx, double *val) {
|
||||
char *end = NULL;
|
||||
*val = strtod(ctx->cur, &end);
|
||||
|
||||
if(ctx->cur == end) {
|
||||
warn("istrGetdouble: no valid conversion could be performed");
|
||||
warn("istrGetDouble: no valid conversion could be performed (%.5s)", ctx->cur);
|
||||
return false;
|
||||
}
|
||||
else if(*val == HUGE_VAL || *val == -HUGE_VAL) {
|
||||
warn("istrGetdouble: value read is out of the range of representable values");
|
||||
warn("istrGetDouble: value read is out of the range of representable values");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -287,286 +300,127 @@ bool istrGetdouble(str_istream_t *ctx, double *val) {
|
|||
return true;
|
||||
}
|
||||
|
||||
usize istrGetstring(str_istream_t *ctx, char **val, char delim) {
|
||||
str_t istrGetStr(arena_t *arena, instream_t *ctx, char delim) {
|
||||
const char *from = ctx->cur;
|
||||
istrIgnore(ctx, delim);
|
||||
// if it didn't actually find it, it just reached the end of the string
|
||||
if(*ctx->cur != delim) {
|
||||
*val = NULL;
|
||||
return 0;
|
||||
return (str_t){0};
|
||||
}
|
||||
usize len = ctx->cur - from;
|
||||
*val = (char *)malloc(len + 1);
|
||||
memcpy(*val, from, len);
|
||||
(*val)[len] = '\0';
|
||||
return len;
|
||||
str_t out = {
|
||||
.buf = alloc(arena, char, len + 1),
|
||||
.len = len
|
||||
};
|
||||
memcpy(out.buf, from, len);
|
||||
return out;
|
||||
}
|
||||
|
||||
usize istrGetstringBuf(str_istream_t *ctx, char *val, usize len) {
|
||||
usize istrGetBuf(instream_t *ctx, char *buf, usize buflen) {
|
||||
usize remaining = ctx->size - (ctx->cur - ctx->start);
|
||||
len -= 1;
|
||||
len = remaining < len ? remaining : len;
|
||||
memcpy(val, ctx->cur, len);
|
||||
val[len] = '\0';
|
||||
ctx->cur += len;
|
||||
return len;
|
||||
buflen -= 1;
|
||||
buflen = remaining < buflen ? remaining : buflen;
|
||||
memcpy(buf, ctx->cur, buflen);
|
||||
buf[buflen] = '\0';
|
||||
ctx->cur += buflen;
|
||||
return buflen;
|
||||
}
|
||||
|
||||
strview_t istrGetview(str_istream_t *ctx, char delim) {
|
||||
strview_t istrGetView(instream_t *ctx, char delim) {
|
||||
const char *from = ctx->cur;
|
||||
istrIgnore(ctx, delim);
|
||||
usize len = ctx->cur - from;
|
||||
return strvInitLen(from, len);
|
||||
}
|
||||
|
||||
strview_t istrGetviewLen(str_istream_t *ctx, usize from, usize to) {
|
||||
usize len = ctx->size - (ctx->cur - ctx->start) - from;
|
||||
if (to > len) to = len;
|
||||
if (from > to) from = to;
|
||||
return strvInitLen(ctx->cur + from, to - from);
|
||||
strview_t istrGetViewLen(instream_t *ctx, usize len) {
|
||||
const char *from = ctx->cur;
|
||||
istrSkip(ctx, len);
|
||||
usize buflen = ctx->cur - from;
|
||||
return (strview_t){ from, buflen };
|
||||
}
|
||||
|
||||
/* == OUTPUT STREAM =========================================== */
|
||||
|
||||
static void _ostrRealloc(str_ostream_t *ctx, usize needed) {
|
||||
ctx->cap = (ctx->cap * 2) + needed;
|
||||
ctx->buf = (char *)realloc(ctx->buf, ctx->cap);
|
||||
void ostr__remove_null(outstream_t *o) {
|
||||
if (ostrTell(o)) {
|
||||
arenaPop(o->arena, 1);
|
||||
}
|
||||
}
|
||||
|
||||
str_ostream_t ostrInit() {
|
||||
return ostrInitLen(1);
|
||||
}
|
||||
|
||||
str_ostream_t ostrInitLen(usize initial_alloc) {
|
||||
str_ostream_t stream;
|
||||
stream.buf = (char *)calloc(initial_alloc, 1);
|
||||
stream.len = 0;
|
||||
stream.cap = initial_alloc;
|
||||
return stream;
|
||||
}
|
||||
|
||||
str_ostream_t ostrInitStr(const char *cstr, usize len) {
|
||||
str_ostream_t stream;
|
||||
stream.buf = (char *)malloc(len + 1);
|
||||
memcpy(stream.buf, cstr, len);
|
||||
stream.len = len;
|
||||
stream.cap = len + 1;
|
||||
return stream;
|
||||
}
|
||||
|
||||
void ostrFree(str_ostream_t ctx) {
|
||||
free(ctx.buf);
|
||||
}
|
||||
|
||||
void ostrClear(str_ostream_t *ctx) {
|
||||
ctx->len = 0;
|
||||
}
|
||||
|
||||
char ostrBack(str_ostream_t ctx) {
|
||||
if(ctx.len == 0) return '\0';
|
||||
return ctx.buf[ctx.len - 1];
|
||||
}
|
||||
|
||||
str_t ostrAsStr(str_ostream_t ctx) {
|
||||
return (str_t) {
|
||||
.buf = ctx.buf,
|
||||
.len = ctx.len
|
||||
outstream_t ostrInit(arena_t *arena) {
|
||||
return (outstream_t){
|
||||
.beg = (char *)(arena ? arena->current : NULL),
|
||||
.arena = arena,
|
||||
};
|
||||
}
|
||||
|
||||
strview_t ostrAsView(str_ostream_t ctx) {
|
||||
return (strview_t) {
|
||||
.buf = ctx.buf,
|
||||
.len = ctx.len
|
||||
void ostrClear(outstream_t *ctx) {
|
||||
arenaPop(ctx->arena, ostrTell(ctx));
|
||||
}
|
||||
|
||||
usize ostrTell(outstream_t *ctx) {
|
||||
return ctx->arena ? (char *)ctx->arena->current - ctx->beg : 0;
|
||||
}
|
||||
|
||||
char ostrBack(outstream_t *ctx) {
|
||||
return arenaTell(ctx->arena) ? *ctx->arena->current : '\0';
|
||||
}
|
||||
|
||||
str_t ostrAsStr(outstream_t *ctx) {
|
||||
return (str_t){
|
||||
.buf = ctx->beg,
|
||||
.len = ostrTell(ctx)
|
||||
};
|
||||
}
|
||||
|
||||
void ostrReplace(str_ostream_t *ctx, char from, char to) {
|
||||
for(usize i = 0; i < ctx->len; ++i) {
|
||||
if(ctx->buf[i] == from) {
|
||||
ctx->buf[i] = to;
|
||||
}
|
||||
}
|
||||
strview_t ostrAsView(outstream_t *ctx) {
|
||||
return (strview_t){
|
||||
.buf = ctx->beg,
|
||||
.len = ostrTell(ctx)
|
||||
};
|
||||
}
|
||||
|
||||
void ostrPrintf(str_ostream_t *ctx, const char *fmt, ...) {
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
ostrPrintfV(ctx, fmt, va);
|
||||
va_end(va);
|
||||
void ostrPrintf(outstream_t *ctx, const char *fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
ostrPrintfV(ctx, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void ostrPrintfV(str_ostream_t *ctx, const char *fmt, va_list args) {
|
||||
va_list vtemp;
|
||||
int len;
|
||||
usize remaining;
|
||||
|
||||
// vsnprintf returns the length of the formatted string, even if truncated
|
||||
// we use this to get the actual length of the formatted string
|
||||
va_copy(vtemp, args);
|
||||
len = vsnprintf(NULL, 0, fmt, vtemp);
|
||||
va_end(vtemp);
|
||||
if(len < 0) {
|
||||
err("couldn't format string \"%s\"", fmt);
|
||||
goto error;
|
||||
}
|
||||
|
||||
remaining = ctx->cap - ctx->len;
|
||||
if(remaining < (usize)len) {
|
||||
_ostrRealloc(ctx, len + 1);
|
||||
remaining = ctx->cap - ctx->len;
|
||||
}
|
||||
|
||||
// actual formatting here
|
||||
va_copy(vtemp, args);
|
||||
len = vsnprintf(ctx->buf + ctx->len, remaining, fmt, vtemp);
|
||||
va_end(vtemp);
|
||||
if(len < 0) {
|
||||
err("couldn't format stringh \"%s\"", fmt);
|
||||
goto error;
|
||||
}
|
||||
ctx->len += len;
|
||||
|
||||
error:
|
||||
return;
|
||||
void ostrPrintfV(outstream_t *ctx, const char *fmt, va_list args) {
|
||||
if (!ctx->arena) return;
|
||||
ostr__remove_null(ctx);
|
||||
strFmtv(ctx->arena, fmt, args);
|
||||
}
|
||||
|
||||
|
||||
#define APPEND_BUF_LEN 20
|
||||
|
||||
void ostrPutc(str_ostream_t *ctx, char c) {
|
||||
ostrAppendchar(ctx, c);
|
||||
void ostrPutc(outstream_t *ctx, char c) {
|
||||
if (!ctx->arena) return;
|
||||
ostr__remove_null(ctx);
|
||||
char *newc = alloc(ctx->arena, char);
|
||||
*newc = c;
|
||||
}
|
||||
|
||||
void ostrPuts(str_ostream_t *ctx, const char *str) {
|
||||
ostrAppendview(ctx, strvInit(str));
|
||||
void ostrPuts(outstream_t *ctx, strview_t v) {
|
||||
if (strvIsEmpty(v)) return;
|
||||
ostr__remove_null(ctx);
|
||||
str(ctx->arena, v);
|
||||
}
|
||||
|
||||
void ostrAppendbool(str_ostream_t *ctx, bool val) {
|
||||
ostrAppendview(ctx, strvInit(val ? "true" : "false"));
|
||||
void ostrAppendBool(outstream_t *ctx, bool val) {
|
||||
ostrPuts(ctx, val ? strv("true") : strv("false"));
|
||||
}
|
||||
|
||||
void ostrAppendchar(str_ostream_t *ctx, char val) {
|
||||
if(ctx->len >= ctx->cap) {
|
||||
_ostrRealloc(ctx, 1);
|
||||
}
|
||||
ctx->buf[ctx->len++] = val;
|
||||
ctx->buf[ctx->len] = '\0';
|
||||
void ostrAppendUInt(outstream_t *ctx, uint64 val) {
|
||||
ostrPrintf(ctx, "%I64u", val);
|
||||
}
|
||||
|
||||
void ostrAppendu8(str_ostream_t *ctx, uint8 val) {
|
||||
char buf[APPEND_BUF_LEN];
|
||||
int len = snprintf(buf, sizeof(buf), "%hhu", val);
|
||||
if(len <= 0) {
|
||||
err("ostrAppendu8: couldn't write %hhu", val);
|
||||
return;
|
||||
}
|
||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
||||
void ostrAppendInt(outstream_t *ctx, int64 val) {
|
||||
ostrPrintf(ctx, "%I64d", val);
|
||||
}
|
||||
|
||||
void ostrAppendu16(str_ostream_t *ctx, uint16 val) {
|
||||
char buf[APPEND_BUF_LEN];
|
||||
int len = snprintf(buf, sizeof(buf), "%hu", val);
|
||||
if(len <= 0) {
|
||||
err("ostrAppendu16: couldn't write %hu", val);
|
||||
return;
|
||||
}
|
||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
||||
void ostrAppendNum(outstream_t *ctx, double val) {
|
||||
ostrPrintf(ctx, "%g", val);
|
||||
}
|
||||
|
||||
void ostrAppendu32(str_ostream_t *ctx, uint32 val) {
|
||||
char buf[APPEND_BUF_LEN];
|
||||
int len = snprintf(buf, sizeof(buf), "%u", val);
|
||||
if(len <= 0) {
|
||||
err("ostrAppendu32: couldn't write %u", val);
|
||||
return;
|
||||
}
|
||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
||||
}
|
||||
|
||||
void ostrAppendu64(str_ostream_t *ctx, uint64 val) {
|
||||
char buf[APPEND_BUF_LEN];
|
||||
#if _WIN32
|
||||
int len = snprintf(buf, sizeof(buf), "%llu", val);
|
||||
#else
|
||||
int len = snprintf(buf, sizeof(buf), "%lu", val);
|
||||
#endif
|
||||
if(len <= 0) {
|
||||
err("ostrAppendu64: couldn't write %lu", val);
|
||||
return;
|
||||
}
|
||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
||||
}
|
||||
|
||||
void ostrAppendi8(str_ostream_t *ctx, int8 val) {
|
||||
char buf[APPEND_BUF_LEN];
|
||||
int len = snprintf(buf, sizeof(buf), "%hhi", val);
|
||||
if(len <= 0) {
|
||||
err("ostrAppendi8: couldn't write %hhi", val);
|
||||
return;
|
||||
}
|
||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
||||
}
|
||||
|
||||
void ostrAppendi16(str_ostream_t *ctx, int16 val) {
|
||||
char buf[APPEND_BUF_LEN];
|
||||
int len = snprintf(buf, sizeof(buf), "%hi", val);
|
||||
if(len <= 0) {
|
||||
err("ostrAppendi16: couldn't write %hi", val);
|
||||
return;
|
||||
}
|
||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
||||
}
|
||||
|
||||
void ostrAppendi32(str_ostream_t *ctx, int32 val) {
|
||||
char buf[APPEND_BUF_LEN];
|
||||
int len = snprintf(buf, sizeof(buf), "%i", val);
|
||||
if(len <= 0) {
|
||||
err("ostrAppendi32: couldn't write %i", val);
|
||||
return;
|
||||
}
|
||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
||||
}
|
||||
|
||||
void ostrAppendi64(str_ostream_t *ctx, int64 val) {
|
||||
char buf[APPEND_BUF_LEN];
|
||||
#if _WIN32
|
||||
int len = snprintf(buf, sizeof(buf), "%lli", val);
|
||||
#else
|
||||
int len = snprintf(buf, sizeof(buf), "%li", val);
|
||||
#endif
|
||||
if(len <= 0) {
|
||||
err("ostrAppendi64: couldn't write %li", val);
|
||||
return;
|
||||
}
|
||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
||||
}
|
||||
|
||||
void ostrAppendfloat(str_ostream_t *ctx, float val) {
|
||||
char buf[APPEND_BUF_LEN * 3];
|
||||
int len = snprintf(buf, sizeof(buf), "%g", (double)val);
|
||||
if(len <= 0) {
|
||||
err("ostrAppendfloat: couldn't write %g", (double)val);
|
||||
return;
|
||||
}
|
||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
||||
}
|
||||
|
||||
void ostrAppenddouble(str_ostream_t *ctx, double val) {
|
||||
char buf[APPEND_BUF_LEN * 3];
|
||||
int len = snprintf(buf, sizeof(buf), "%g", val);
|
||||
if(len <= 0) {
|
||||
err("ostrAppenddouble: couldn't write %g", val);
|
||||
return;
|
||||
}
|
||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
||||
}
|
||||
|
||||
void ostrAppendview(str_ostream_t *ctx, strview_t view) {
|
||||
if((ctx->cap - ctx->len) <= view.len) {
|
||||
_ostrRealloc(ctx, view.len + 1);
|
||||
}
|
||||
memcpy(ctx->buf + ctx->len, view.buf, view.len);
|
||||
ctx->len += view.len;
|
||||
ctx->buf[ctx->len] = '\0';
|
||||
}
|
||||
#include "warnings/colla_warn_end.h"
|
||||
|
|
@ -4,110 +4,97 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "collatypes.h"
|
||||
#include "str.h"
|
||||
|
||||
typedef struct arena_t arena_t;
|
||||
|
||||
/* == INPUT STREAM ============================================ */
|
||||
|
||||
typedef struct {
|
||||
const char *start;
|
||||
const char *cur;
|
||||
usize size;
|
||||
} str_istream_t;
|
||||
} instream_t;
|
||||
|
||||
// initialize with null-terminated string
|
||||
str_istream_t istrInit(const char *str);
|
||||
str_istream_t istrInitLen(const char *str, usize len);
|
||||
instream_t istrInit(const char *str);
|
||||
instream_t istrInitLen(const char *str, usize len);
|
||||
|
||||
// get the current character and advance
|
||||
char istrGet(str_istream_t *ctx);
|
||||
char istrGet(instream_t *ctx);
|
||||
// get the current character but don't advance
|
||||
char istrPeek(str_istream_t *ctx);
|
||||
char istrPeek(instream_t *ctx);
|
||||
// get the next character but don't advance
|
||||
char istrPeekNext(instream_t *ctx);
|
||||
// ignore characters until the delimiter
|
||||
void istrIgnore(str_istream_t *ctx, char delim);
|
||||
void istrIgnore(instream_t *ctx, char delim);
|
||||
// ignore characters until the delimiter and skip it
|
||||
void istrIgnoreAndSkip(str_istream_t *ctx, char delim);
|
||||
void istrIgnoreAndSkip(instream_t *ctx, char delim);
|
||||
// skip n characters
|
||||
void istrSkip(str_istream_t *ctx, usize n);
|
||||
void istrSkip(instream_t *ctx, usize n);
|
||||
// skips whitespace (' ', '\n', '\t', '\r')
|
||||
void istrSkipWhitespace(str_istream_t *ctx);
|
||||
void istrSkipWhitespace(instream_t *ctx);
|
||||
// read len bytes into buffer, the buffer will not be null terminated
|
||||
void istrRead(str_istream_t *ctx, char *buf, usize len);
|
||||
void istrRead(instream_t *ctx, char *buf, usize len);
|
||||
// read a maximum of len bytes into buffer, the buffer will not be null terminated
|
||||
// returns the number of bytes read
|
||||
usize istrReadMax(str_istream_t *ctx, char *buf, usize len);
|
||||
usize istrReadMax(instream_t *ctx, char *buf, usize len);
|
||||
// returns to the beginning of the stream
|
||||
void istrRewind(str_istream_t *ctx);
|
||||
void istrRewind(instream_t *ctx);
|
||||
// returns back <amount> characters
|
||||
void istrRewindN(str_istream_t *ctx, usize amount);
|
||||
void istrRewindN(instream_t *ctx, usize amount);
|
||||
// returns the number of bytes read from beginning of stream
|
||||
usize istrTell(str_istream_t ctx);
|
||||
usize istrTell(instream_t ctx);
|
||||
// returns the number of bytes left to read in the stream
|
||||
usize istrRemaining(str_istream_t ctx);
|
||||
usize istrRemaining(instream_t ctx);
|
||||
// return true if the stream doesn't have any new bytes to read
|
||||
bool istrIsFinished(str_istream_t ctx);
|
||||
bool istrIsFinished(instream_t ctx);
|
||||
|
||||
bool istrGetbool(str_istream_t *ctx, bool *val);
|
||||
bool istrGetu8(str_istream_t *ctx, uint8 *val);
|
||||
bool istrGetu16(str_istream_t *ctx, uint16 *val);
|
||||
bool istrGetu32(str_istream_t *ctx, uint32 *val);
|
||||
bool istrGetu64(str_istream_t *ctx, uint64 *val);
|
||||
bool istrGeti8(str_istream_t *ctx, int8 *val);
|
||||
bool istrGeti16(str_istream_t *ctx, int16 *val);
|
||||
bool istrGeti32(str_istream_t *ctx, int32 *val);
|
||||
bool istrGeti64(str_istream_t *ctx, int64 *val);
|
||||
bool istrGetfloat(str_istream_t *ctx, float *val);
|
||||
bool istrGetdouble(str_istream_t *ctx, double *val);
|
||||
// get a string until a delimiter, the string is allocated by the function and should be freed
|
||||
usize istrGetstring(str_istream_t *ctx, char **val, char delim);
|
||||
bool istrGetBool(instream_t *ctx, bool *val);
|
||||
bool istrGetU8(instream_t *ctx, uint8 *val);
|
||||
bool istrGetU16(instream_t *ctx, uint16 *val);
|
||||
bool istrGetU32(instream_t *ctx, uint32 *val);
|
||||
bool istrGetU64(instream_t *ctx, uint64 *val);
|
||||
bool istrGetI8(instream_t *ctx, int8 *val);
|
||||
bool istrGetI16(instream_t *ctx, int16 *val);
|
||||
bool istrGetI32(instream_t *ctx, int32 *val);
|
||||
bool istrGetI64(instream_t *ctx, int64 *val);
|
||||
bool istrGetFloat(instream_t *ctx, float *val);
|
||||
bool istrGetDouble(instream_t *ctx, double *val);
|
||||
str_t istrGetStr(arena_t *arena, instream_t *ctx, char delim);
|
||||
// get a string of maximum size len, the string is not allocated by the function and will be null terminated
|
||||
usize istrGetstringBuf(str_istream_t *ctx, char *val, usize len);
|
||||
strview_t istrGetview(str_istream_t *ctx, char delim);
|
||||
strview_t istrGetviewLen(str_istream_t *ctx, usize from, usize to);
|
||||
usize istrGetBuf(instream_t *ctx, char *buf, usize buflen);
|
||||
strview_t istrGetView(instream_t *ctx, char delim);
|
||||
strview_t istrGetViewLen(instream_t *ctx, usize len);
|
||||
|
||||
/* == OUTPUT STREAM =========================================== */
|
||||
|
||||
typedef struct {
|
||||
char *buf;
|
||||
usize len;
|
||||
usize cap;
|
||||
} str_ostream_t;
|
||||
char *beg;
|
||||
arena_t *arena;
|
||||
} outstream_t;
|
||||
|
||||
str_ostream_t ostrInit(void);
|
||||
str_ostream_t ostrInitLen(usize initial_alloc);
|
||||
str_ostream_t ostrInitStr(const char *buf, usize len);
|
||||
outstream_t ostrInit(arena_t *exclusive_arena);
|
||||
void ostrClear(outstream_t *ctx);
|
||||
|
||||
void ostrFree(str_ostream_t ctx);
|
||||
void ostrClear(str_ostream_t *ctx);
|
||||
usize ostrTell(outstream_t *ctx);
|
||||
|
||||
char ostrBack(str_ostream_t ctx);
|
||||
str_t ostrAsStr(str_ostream_t ctx);
|
||||
strview_t ostrAsView(str_ostream_t ctx);
|
||||
char ostrBack(outstream_t *ctx);
|
||||
str_t ostrAsStr(outstream_t *ctx);
|
||||
strview_t ostrAsView(outstream_t *ctx);
|
||||
|
||||
void ostrReplace(str_ostream_t *ctx, char from, char to);
|
||||
void ostrPrintf(outstream_t *ctx, const char *fmt, ...);
|
||||
void ostrPrintfV(outstream_t *ctx, const char *fmt, va_list args);
|
||||
void ostrPutc(outstream_t *ctx, char c);
|
||||
void ostrPuts(outstream_t *ctx, strview_t v);
|
||||
|
||||
void ostrPrintf(str_ostream_t *ctx, const char *fmt, ...);
|
||||
void ostrPrintfV(str_ostream_t *ctx, const char *fmt, va_list args);
|
||||
void ostrPutc(str_ostream_t *ctx, char c);
|
||||
void ostrPuts(str_ostream_t *ctx, const char *str);
|
||||
|
||||
void ostrAppendbool(str_ostream_t *ctx, bool val);
|
||||
void ostrAppendchar(str_ostream_t *ctx, char val);
|
||||
void ostrAppendu8(str_ostream_t *ctx, uint8 val);
|
||||
void ostrAppendu16(str_ostream_t *ctx, uint16 val);
|
||||
void ostrAppendu32(str_ostream_t *ctx, uint32 val);
|
||||
void ostrAppendu64(str_ostream_t *ctx, uint64 val);
|
||||
void ostrAppendi8(str_ostream_t *ctx, int8 val);
|
||||
void ostrAppendi16(str_ostream_t *ctx, int16 val);
|
||||
void ostrAppendi32(str_ostream_t *ctx, int32 val);
|
||||
void ostrAppendi64(str_ostream_t *ctx, int64 val);
|
||||
void ostrAppendfloat(str_ostream_t *ctx, float val);
|
||||
void ostrAppenddouble(str_ostream_t *ctx, double val);
|
||||
void ostrAppendview(str_ostream_t *ctx, strview_t view);
|
||||
void ostrAppendBool(outstream_t *ctx, bool val);
|
||||
void ostrAppendUInt(outstream_t *ctx, uint64 val);
|
||||
void ostrAppendInt(outstream_t *ctx, int64 val);
|
||||
void ostrAppendNum(outstream_t *ctx, double val);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
|
|
|
|||
|
|
@ -4,9 +4,32 @@
|
|||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS.
|
||||
#include "win32_slim.h"
|
||||
#include "format.h"
|
||||
|
||||
#if COLLA_WIN
|
||||
#if COLLA_MSVC
|
||||
#pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS.
|
||||
#endif
|
||||
|
||||
#include <handleapi.h>
|
||||
|
||||
// avoid including windows.h
|
||||
|
||||
#ifndef STD_OUTPUT_HANDLE
|
||||
#define STD_OUTPUT_HANDLE ((DWORD)-11)
|
||||
#endif
|
||||
#ifndef CP_UTF8
|
||||
#define CP_UTF8 65001
|
||||
#endif
|
||||
|
||||
typedef unsigned short WORD;
|
||||
typedef unsigned long DWORD;
|
||||
typedef unsigned int UINT;
|
||||
typedef int BOOL;
|
||||
WINBASEAPI HANDLE WINAPI GetStdHandle(DWORD nStdHandle);
|
||||
WINBASEAPI BOOL WINAPI SetConsoleTextAttribute(HANDLE hConsoleOutput, WORD wAttributes);
|
||||
WINBASEAPI BOOL WINAPI SetConsoleOutputCP(UINT wCodePageID);
|
||||
|
||||
#ifndef TLOG_VS
|
||||
#define TLOG_WIN32_NO_VS
|
||||
#ifndef TLOG_NO_COLOURS
|
||||
|
|
@ -16,33 +39,33 @@
|
|||
#endif
|
||||
|
||||
#ifdef TLOG_VS
|
||||
#ifndef _WIN32
|
||||
#if COLLA_WIN
|
||||
#error "can't use TLOG_VS if not on windows"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef TLOG_NO_COLOURS
|
||||
#define BLACK ""
|
||||
#define RED ""
|
||||
#define GREEN ""
|
||||
#define YELLOW ""
|
||||
#define BLUE ""
|
||||
#define MAGENTA ""
|
||||
#define CYAN ""
|
||||
#define WHITE ""
|
||||
#define RESET ""
|
||||
#define BOLD ""
|
||||
#define COLOUR_BLACK ""
|
||||
#define COLOUR_RED ""
|
||||
#define COLOUR_GREEN ""
|
||||
#define COLOUR_YELLOW ""
|
||||
#define COLOUR_BLUE ""
|
||||
#define COLOUR_MAGENTA ""
|
||||
#define COLOUR_CYAN ""
|
||||
#define COLOUR_WHITE ""
|
||||
#define COLOUR_RESET ""
|
||||
#define COLOUR_BOLD ""
|
||||
#else
|
||||
#define BLACK "\033[30m"
|
||||
#define RED "\033[31m"
|
||||
#define GREEN "\033[32m"
|
||||
#define YELLOW "\033[33m"
|
||||
#define BLUE "\033[22;34m"
|
||||
#define MAGENTA "\033[35m"
|
||||
#define CYAN "\033[36m"
|
||||
#define WHITE "\033[37m"
|
||||
#define RESET "\033[0m"
|
||||
#define BOLD "\033[1m"
|
||||
#define COLOUR_BLACK "\033[30m"
|
||||
#define COLOUR_RED "\033[31m"
|
||||
#define COLOUR_GREEN "\033[32m"
|
||||
#define COLOUR_YELLOW "\033[33m"
|
||||
#define COLOUR_BLUE "\033[22;34m"
|
||||
#define COLOUR_MAGENTA "\033[35m"
|
||||
#define COLOUR_CYAN "\033[36m"
|
||||
#define COLOUR_WHITE "\033[37m"
|
||||
#define COLOUR_RESET "\033[0m"
|
||||
#define COLOUR_BOLD "\033[1m"
|
||||
#endif
|
||||
|
||||
#define MAX_TRACELOG_MSG_LENGTH 1024
|
||||
|
|
@ -67,8 +90,7 @@ static void setLevelColour(int level) {
|
|||
|
||||
void traceLog(int level, const char *fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
traceLogVaList(level, fmt, args);
|
||||
va_start(args, fmt); traceLogVaList(level, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
|
|
@ -88,12 +110,12 @@ void traceLogVaList(int level, const char *fmt, va_list args) {
|
|||
|
||||
const char *beg;
|
||||
switch (level) {
|
||||
case LogTrace: beg = BOLD WHITE "[TRACE]: " RESET; break;
|
||||
case LogDebug: beg = BOLD BLUE "[DEBUG]: " RESET; break;
|
||||
case LogInfo: beg = BOLD GREEN "[INFO]: " RESET; break;
|
||||
case LogWarning: beg = BOLD YELLOW "[WARNING]: " RESET; break;
|
||||
case LogError: beg = BOLD RED "[ERROR]: " RESET; break;
|
||||
case LogFatal: beg = BOLD RED "[FATAL]: " RESET; break;
|
||||
case LogTrace: beg = COLOUR_BOLD COLOUR_WHITE "[TRACE]: " COLOUR_RESET; break;
|
||||
case LogDebug: beg = COLOUR_BOLD COLOUR_BLUE "[DEBUG]: " COLOUR_RESET; break;
|
||||
case LogInfo: beg = COLOUR_BOLD COLOUR_GREEN "[INFO]: " COLOUR_RESET; break;
|
||||
case LogWarning: beg = COLOUR_BOLD COLOUR_YELLOW "[WARNING]: " COLOUR_RESET; break;
|
||||
case LogError: beg = COLOUR_BOLD COLOUR_RED "[ERROR]: " COLOUR_RESET; break;
|
||||
case LogFatal: beg = COLOUR_BOLD COLOUR_RED "[FATAL]: " COLOUR_RESET; break;
|
||||
default: beg = ""; break;
|
||||
}
|
||||
|
||||
|
|
@ -104,7 +126,7 @@ void traceLogVaList(int level, const char *fmt, va_list args) {
|
|||
strncpy(buffer, beg, sizeof(buffer));
|
||||
#endif
|
||||
|
||||
vsnprintf(buffer + offset, sizeof(buffer) - offset, fmt, args);
|
||||
fmtBufferv(buffer + offset, sizeof(buffer) - offset, fmt, args);
|
||||
|
||||
#if defined(TLOG_VS)
|
||||
OutputDebugStringA(buffer);
|
||||
|
|
@ -123,7 +145,10 @@ void traceLogVaList(int level, const char *fmt, va_list args) {
|
|||
#endif
|
||||
|
||||
#ifndef TLOG_DONT_EXIT_ON_FATAL
|
||||
if (level == LogFatal) exit(1);
|
||||
if (level == LogFatal) {
|
||||
abort();
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef TLOG_MUTEX
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ extern "C" {
|
|||
* -> TLOG_MUTEX: use a mutex on every traceLog call
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "collatypes.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
enum {
|
||||
|
|
|
|||
133
colla/vmem.c
Normal file
133
colla/vmem.c
Normal 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
12
colla/vmem.h
Normal 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
|
||||
7
colla/warnings/colla_warn_beg.h
Normal file
7
colla/warnings/colla_warn_beg.h
Normal 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
|
||||
5
colla/warnings/colla_warn_end.h
Normal file
5
colla/warnings/colla_warn_end.h
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#if COLLA_CLANG
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
#endif
|
||||
|
|
@ -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
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
include = colla/
|
||||
files = colla/*.c
|
||||
|
||||
[windows]
|
||||
link = ws2_32.lib
|
||||
|
||||
[linux]
|
||||
link = pthread
|
||||
|
|
@ -4,8 +4,8 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h> // bool
|
||||
#include <string.h> // memset
|
||||
#include "collatypes.h"
|
||||
#include "tracelog.h" // fatal
|
||||
|
||||
// heavily inspired by https://gist.github.com/Enichan/5f01140530ff0133fde19c9549a2a973
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#include "dir.h"
|
||||
#include "tracelog.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#if COLLA_WIN
|
||||
#include "win32_slim.h"
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
|
@ -19,7 +19,7 @@ static dir_entry_t _fillDirEntry(WIN32_FIND_DATAW *data) {
|
|||
.type =
|
||||
data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ?
|
||||
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) {
|
||||
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);
|
||||
assert(n > 0);
|
||||
out.len += n;
|
||||
|
|
@ -196,7 +196,7 @@ bool dirRemove(const char *path) {
|
|||
}
|
||||
}
|
||||
dirClose(dir);
|
||||
#ifdef _WIN32
|
||||
#if COLLA_WIN
|
||||
return RemoveDirectoryA(path);
|
||||
#else
|
||||
return rmdir(path) == 0;
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
#include <assert.h>
|
||||
#include "tracelog.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#if COLLA_WIN
|
||||
#include "win32_slim.h"
|
||||
#include "str.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ extern "C" {
|
|||
* }
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "collatypes.h"
|
||||
#include "cthreads.h"
|
||||
|
||||
enum {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
#include "tracelog.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#if COLLA_WIN
|
||||
#include "win32_slim.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
|
|
|||
|
|
@ -4,8 +4,7 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "collatypes.h"
|
||||
|
||||
#include "file.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#include "jobpool.h"
|
||||
|
||||
#include <vec.h>
|
||||
#include "vec.h"
|
||||
|
||||
typedef struct {
|
||||
cthread_func_t func;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <collatypes.h>
|
||||
#include <cthreads.h>
|
||||
#include "collatypes.h"
|
||||
#include "cthreads.h"
|
||||
|
||||
typedef void *jobpool_t;
|
||||
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#if COLLA_WIN
|
||||
#define _BUFSZ 128
|
||||
|
||||
#include <lmcons.h>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ extern "C" {
|
|||
#include "str.h"
|
||||
#include "collatypes.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#if COLLA_WIN
|
||||
#include <stdio.h>
|
||||
#include "win32_slim.h"
|
||||
isize getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp);
|
||||
|
|
|
|||
104
gen.lua
104
gen.lua
|
|
@ -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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue