moving stuff to custom git instance
This commit is contained in:
parent
a66e58193f
commit
ccb992a44a
42 changed files with 4947 additions and 10390 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -1 +1,4 @@
|
||||||
.vscode/
|
colla/
|
||||||
|
build/
|
||||||
|
*.pdb
|
||||||
|
test.c
|
||||||
|
|
|
||||||
0
README.md
Normal file
0
README.md
Normal file
332
arena.c
332
arena.c
|
|
@ -1,332 +0,0 @@
|
||||||
#include "arena.h"
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#if COLLA_DEBUG
|
|
||||||
#include <stdlib.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "os.h"
|
|
||||||
|
|
||||||
static uptr arena__align(uptr 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(u8 *buf, usize len);
|
|
||||||
|
|
||||||
static void *arena__alloc_common(const arena_alloc_desc_t *desc);
|
|
||||||
static void *arena__alloc_malloc_always(const arena_alloc_desc_t *desc);
|
|
||||||
|
|
||||||
static void arena__free_virtual(arena_t *arena);
|
|
||||||
static void arena__free_malloc(arena_t *arena);
|
|
||||||
|
|
||||||
arena_t malloc_arena = {
|
|
||||||
.type = ARENA_MALLOC_ALWAYS,
|
|
||||||
};
|
|
||||||
|
|
||||||
arena_t arena_init(const arena_desc_t *desc) {
|
|
||||||
arena_t out = {0};
|
|
||||||
|
|
||||||
#if COLLA_DEBUG
|
|
||||||
out.file = desc->file;
|
|
||||||
out.line = desc->line;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (desc) {
|
|
||||||
switch (desc->type) {
|
|
||||||
case ARENA_VIRTUAL: out = arena__make_virtual(desc->size); break;
|
|
||||||
case ARENA_MALLOC: out = arena__make_malloc(desc->size); break;
|
|
||||||
case ARENA_STATIC: out = arena__make_static(desc->static_buffer, desc->size); break;
|
|
||||||
case ARENA_MALLOC_ALWAYS: out = malloc_arena; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
void arena_cleanup(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
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(arena, 0, sizeof(arena_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
arena_t arena_scratch(arena_t *arena, usize size) {
|
|
||||||
u8 *buffer = alloc(arena, u8, size, ALLOC_SOFT_FAIL | ALLOC_NOZERO);
|
|
||||||
return arena__make_static(buffer, buffer ? size : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void *arena_alloc(const arena_alloc_desc_t *desc) {
|
|
||||||
if (!desc || !desc->arena || desc->arena->type == ARENA_TYPE_NONE) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
arena_t *arena = desc->arena;
|
|
||||||
|
|
||||||
u8 *ptr = NULL;
|
|
||||||
|
|
||||||
switch (arena->type) {
|
|
||||||
case ARENA_MALLOC_ALWAYS:
|
|
||||||
ptr = arena__alloc_malloc_always(desc);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
ptr = arena__alloc_common(desc);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ptr && desc->flags & ALLOC_SOFT_FAIL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize total = desc->size * desc->count;
|
|
||||||
|
|
||||||
return desc->flags & ALLOC_NOZERO ? ptr : memset(ptr, 0, total);
|
|
||||||
}
|
|
||||||
|
|
||||||
usize arena_tell(arena_t *arena) {
|
|
||||||
return arena ? arena->cur - arena->beg : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize arena_remaining(arena_t *arena) {
|
|
||||||
return arena && (arena->cur < arena->end) ? arena->end - arena->cur : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize arena_capacity(arena_t *arena) {
|
|
||||||
return arena ? arena->end - arena->beg : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void arena_rewind(arena_t *arena, usize from_start) {
|
|
||||||
if (!arena) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
colla_assert(arena_tell(arena) >= from_start);
|
|
||||||
|
|
||||||
arena->cur = arena->beg + from_start;
|
|
||||||
}
|
|
||||||
|
|
||||||
void arena_pop(arena_t *arena, usize amount) {
|
|
||||||
if (!arena) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
usize position = arena_tell(arena);
|
|
||||||
if (!position) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
arena_rewind(arena, position - amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
// == VIRTUAL ARENA ====================================================================================================
|
|
||||||
|
|
||||||
static arena_t arena__make_virtual(usize size) {
|
|
||||||
usize alloc_size = 0;
|
|
||||||
u8 *ptr = os_reserve(size, &alloc_size);
|
|
||||||
if (!os_commit(ptr, 1)) {
|
|
||||||
os_release(ptr, alloc_size);
|
|
||||||
ptr = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (arena_t){
|
|
||||||
.beg = ptr,
|
|
||||||
.cur = ptr,
|
|
||||||
.end = ptr ? ptr + alloc_size : NULL,
|
|
||||||
.type = ARENA_VIRTUAL,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static void arena__free_virtual(arena_t *arena) {
|
|
||||||
if (!arena->beg) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
os_release(arena->beg, arena_capacity(arena));
|
|
||||||
}
|
|
||||||
// == MALLOC ARENA =====================================================================================================
|
|
||||||
|
|
||||||
static arena_t arena__make_malloc(usize size) {
|
|
||||||
u8 *ptr = os_alloc(size);
|
|
||||||
colla_assert(ptr);
|
|
||||||
return (arena_t) {
|
|
||||||
.beg = ptr,
|
|
||||||
.cur = ptr,
|
|
||||||
.end = ptr ? ptr + size : NULL,
|
|
||||||
.type = ARENA_MALLOC,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static void arena__free_malloc(arena_t *arena) {
|
|
||||||
os_free(arena->beg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// == ARENA ALLOC ======================================================================================================
|
|
||||||
|
|
||||||
static void *arena__alloc_common(const arena_alloc_desc_t *desc) {
|
|
||||||
usize total = desc->size * desc->count;
|
|
||||||
arena_t *arena = desc->arena;
|
|
||||||
|
|
||||||
arena->cur = (u8 *)arena__align((uptr)arena->cur, desc->align);
|
|
||||||
bool soft_fail = desc->flags & ALLOC_SOFT_FAIL;
|
|
||||||
|
|
||||||
if (total > arena_remaining(arena)) {
|
|
||||||
if (!soft_fail) {
|
|
||||||
#if COLLA_DEBUG
|
|
||||||
arena__print_crash(arena);
|
|
||||||
#endif
|
|
||||||
fatal("finished space in arena, tried to allocate %_$$$dB out of %_$$$dB (total: %_$$$dB)\n", total, arena_remaining(arena), (usize)(arena->end - arena->beg));
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arena->type == ARENA_VIRTUAL) {
|
|
||||||
usize allocated = arena_tell(arena);
|
|
||||||
usize page_end = os_pad_to_page(allocated);
|
|
||||||
usize new_cur = allocated + total;
|
|
||||||
|
|
||||||
if (new_cur > page_end) {
|
|
||||||
usize extra_mem = os_pad_to_page(new_cur - page_end);
|
|
||||||
usize page_size = os_get_system_info().page_size;
|
|
||||||
// TODO is this really correct?
|
|
||||||
usize num_of_pages = (extra_mem / page_size) + 1;
|
|
||||||
|
|
||||||
colla_assert(num_of_pages > 0);
|
|
||||||
|
|
||||||
if (!os_commit(arena->cur, num_of_pages + 1)) {
|
|
||||||
if (!soft_fail) {
|
|
||||||
#if COLLA_DEBUG
|
|
||||||
arena__print_crash(arena);
|
|
||||||
#endif
|
|
||||||
fatal("failed to commit memory for virtual arena, tried to commit %zu pages\n", num_of_pages);
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
u8 *ptr = arena->cur;
|
|
||||||
arena->cur += total;
|
|
||||||
|
|
||||||
#if COLLA_DEBUG
|
|
||||||
alloc_header_t *header = calloc(1, sizeof(alloc_header_t));
|
|
||||||
header->size = desc->size;
|
|
||||||
header->count = desc->count;
|
|
||||||
memcpy(header->type_name, desc->type_name.buf, MIN(desc->type_name.len, sizeof(header->type_name)));
|
|
||||||
list_push(arena->head, header);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void *arena__alloc_malloc_always(const arena_alloc_desc_t *desc) {
|
|
||||||
usize total = desc->size * desc->count;
|
|
||||||
|
|
||||||
// TODO: alignment?
|
|
||||||
u8 *ptr = os_alloc(total);
|
|
||||||
if (!ptr && !(desc->flags & ALLOC_SOFT_FAIL)) {
|
|
||||||
fatal("alloc call failed for %_$$$dB", total);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// == STATIC ARENA =====================================================================================================
|
|
||||||
|
|
||||||
static arena_t arena__make_static(u8 *buf, usize len) {
|
|
||||||
return (arena_t) {
|
|
||||||
.beg = buf,
|
|
||||||
.cur = buf,
|
|
||||||
.end = buf ? buf + len : NULL,
|
|
||||||
.type = ARENA_STATIC,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// == DEBUG ============================================================================================================
|
|
||||||
#if COLLA_DEBUG
|
|
||||||
|
|
||||||
#define ARENA_HMAP_SIZE (1024)
|
|
||||||
|
|
||||||
typedef struct arena_hnode_t arena_hnode_t;
|
|
||||||
struct arena_hnode_t {
|
|
||||||
u32 size;
|
|
||||||
u32 count;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct arena_hmap_t arena_hmap_t;
|
|
||||||
struct arena_hmap_t {
|
|
||||||
u64 hashes[ARENA_HMAP_SIZE];
|
|
||||||
char keys[ARENA_HMAP_SIZE][16];
|
|
||||||
arena_hnode_t values[ARENA_HMAP_SIZE];
|
|
||||||
};
|
|
||||||
|
|
||||||
u64 arena_hmap_hash(char key[16]) {
|
|
||||||
const u8 *data = (const u8 *)key;
|
|
||||||
u64 hash = 0;
|
|
||||||
|
|
||||||
for (usize i = 0; i < 16; ++i) {
|
|
||||||
hash = data[i] + (hash << 6) + (hash << 16) - hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
arena_hnode_t *arena_hmap_find_or_add(arena_hmap_t *map, char key[16]) {
|
|
||||||
u64 hash = arena_hmap_hash(key);
|
|
||||||
usize index = hash & (ARENA_HMAP_SIZE - 1);
|
|
||||||
|
|
||||||
usize beg = index;
|
|
||||||
usize end = ARENA_HMAP_SIZE;
|
|
||||||
|
|
||||||
for (usize k = 0; k < 2; ++k) {
|
|
||||||
for (usize i = index; i < ARENA_HMAP_SIZE; ++i) {
|
|
||||||
if (map->hashes[i] == 0){
|
|
||||||
arena_hnode_t *node = &map->values[i];
|
|
||||||
map->hashes[i] = hash;
|
|
||||||
memcpy(map->keys[i], key, 16);
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (map->hashes[i] == hash && memcmp(map->keys[i], key, 16) == 0) {
|
|
||||||
return &map->values[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
beg = 0;
|
|
||||||
end = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void arena__print_crash(arena_t *arena) {
|
|
||||||
arena_hmap_t hmap = {0};
|
|
||||||
|
|
||||||
debug("arena %v:%d", arena->file, arena->line);
|
|
||||||
|
|
||||||
for_each (header, arena->head) {
|
|
||||||
arena_hnode_t *node = arena_hmap_find_or_add(&hmap, header->type_name);
|
|
||||||
colla_assert(node->size == 0 || node->size == header->size);
|
|
||||||
node->size = header->size;
|
|
||||||
node->count += header->count;
|
|
||||||
}
|
|
||||||
|
|
||||||
print("type name | size\t| count\t| total\n");
|
|
||||||
for (usize i = 0; i < ARENA_HMAP_SIZE; ++i) {
|
|
||||||
if (hmap.hashes[i] == 0) continue;
|
|
||||||
arena_hnode_t n = hmap.values[i];
|
|
||||||
print("%16s| %_$$$dB\t| %d\t| %_$$$dB\n", hmap.keys[i], n.size, n.count, n.size * n.count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
108
arena.h
108
arena.h
|
|
@ -1,108 +0,0 @@
|
||||||
#ifndef COLLA_ARENA_H
|
|
||||||
#define COLLA_ARENA_H
|
|
||||||
|
|
||||||
#include "core.h"
|
|
||||||
|
|
||||||
#if COLLA_DEBUG
|
|
||||||
#include "str.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if COLLA_WIN && !COLLA_TCC
|
|
||||||
#define alignof __alignof
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef enum arena_type_e {
|
|
||||||
ARENA_TYPE_NONE, // only here so that a 0 initialised arena is valid
|
|
||||||
ARENA_VIRTUAL,
|
|
||||||
ARENA_MALLOC,
|
|
||||||
ARENA_MALLOC_ALWAYS,
|
|
||||||
ARENA_STATIC,
|
|
||||||
} arena_type_e;
|
|
||||||
|
|
||||||
typedef enum alloc_flags_e {
|
|
||||||
ALLOC_FLAGS_NONE = 0,
|
|
||||||
ALLOC_NOZERO = 1 << 0,
|
|
||||||
ALLOC_SOFT_FAIL = 1 << 1,
|
|
||||||
} alloc_flags_e;
|
|
||||||
|
|
||||||
#if COLLA_DEBUG
|
|
||||||
typedef struct alloc_header_t alloc_header_t;
|
|
||||||
struct alloc_header_t {
|
|
||||||
char type_name[16];
|
|
||||||
u32 size;
|
|
||||||
u32 count;
|
|
||||||
alloc_header_t *next;
|
|
||||||
};
|
|
||||||
void arena__print_crash(arena_t *arena);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef struct arena_t arena_t;
|
|
||||||
struct arena_t {
|
|
||||||
u8 *beg;
|
|
||||||
u8 *cur;
|
|
||||||
u8 *end;
|
|
||||||
arena_type_e type;
|
|
||||||
#if COLLA_DEBUG
|
|
||||||
strview_t file;
|
|
||||||
int line;
|
|
||||||
alloc_header_t *head;
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct arena_desc_t arena_desc_t;
|
|
||||||
struct arena_desc_t {
|
|
||||||
#if COLLA_DEBUG
|
|
||||||
strview_t file;
|
|
||||||
int line;
|
|
||||||
#endif
|
|
||||||
arena_type_e type;
|
|
||||||
usize size;
|
|
||||||
u8 *static_buffer;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct arena_alloc_desc_t arena_alloc_desc_t;
|
|
||||||
struct arena_alloc_desc_t {
|
|
||||||
#if COLLA_DEBUG
|
|
||||||
strview_t type_name;
|
|
||||||
#endif
|
|
||||||
arena_t *arena;
|
|
||||||
usize count;
|
|
||||||
alloc_flags_e flags;
|
|
||||||
usize align;
|
|
||||||
usize size;
|
|
||||||
};
|
|
||||||
|
|
||||||
// arena_type_e type, usize allocation, [ byte *static_buffer ]
|
|
||||||
#if !COLLA_DEBUG
|
|
||||||
#define arena_make(...) arena_init(&(arena_desc_t){ __VA_ARGS__ })
|
|
||||||
#else
|
|
||||||
#define arena_make(...) arena_init(&(arena_desc_t){ strv(__FILE__), __LINE__, __VA_ARGS__ })
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// arena_t *arena, T type, [ usize count, alloc_flags_e flags, usize align, usize size ]
|
|
||||||
#if !COLLA_DEBUG
|
|
||||||
#define alloc(arenaptr, type, ...) arena_alloc(&(arena_alloc_desc_t){ .size = sizeof(type), .count = 1, .align = alignof(type), .arena = arenaptr, __VA_ARGS__ })
|
|
||||||
#else
|
|
||||||
#define alloc(arenaptr, type, ...) arena_alloc(&(arena_alloc_desc_t){ .type_name = strv(#type), .size = sizeof(type), .count = 1, .align = alignof(type), .arena = arenaptr, __VA_ARGS__ })
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// simple arena that always calls malloc internally, this is useful if you need
|
|
||||||
// malloc for some reason but want to still use the arena interface
|
|
||||||
// WARN: most arena functions outside of alloc/scratch won't work!
|
|
||||||
// you also need to each allocation afterwards! this is still
|
|
||||||
// malloc
|
|
||||||
extern arena_t malloc_arena;
|
|
||||||
|
|
||||||
arena_t arena_init(const arena_desc_t *desc);
|
|
||||||
void arena_cleanup(arena_t *arena);
|
|
||||||
|
|
||||||
arena_t arena_scratch(arena_t *arena, usize size);
|
|
||||||
|
|
||||||
void *arena_alloc(const arena_alloc_desc_t *desc);
|
|
||||||
usize arena_tell(arena_t *arena);
|
|
||||||
usize arena_remaining(arena_t *arena);
|
|
||||||
usize arena_capacity(arena_t *arena);
|
|
||||||
void arena_rewind(arena_t *arena, usize from_start);
|
|
||||||
void arena_pop(arena_t *arena, usize amount);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
17
build.c
17
build.c
|
|
@ -1,17 +0,0 @@
|
||||||
#include "core.h"
|
|
||||||
|
|
||||||
#if COLLA_TCC
|
|
||||||
#define COLLA_NO_CONDITION_VARIABLE 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "core.c"
|
|
||||||
#include "os.c"
|
|
||||||
#include "arena.c"
|
|
||||||
#include "str.c"
|
|
||||||
#include "parsers.c"
|
|
||||||
#include "pretty_print.c"
|
|
||||||
#include "darr.h"
|
|
||||||
|
|
||||||
#if !COLLA_NO_NET
|
|
||||||
#include "net.c"
|
|
||||||
#endif
|
|
||||||
0
colla_lin.c
Normal file
0
colla_lin.c
Normal file
|
|
@ -318,4 +318,5 @@ extern BOOL __stdcall InternetCloseHandle(HINTERNET hInternet);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -1,17 +1,93 @@
|
||||||
|
#include "colla.h"
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#if COLLA_DEBUG
|
|
||||||
#include <stdlib.h> // abort
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "../os.h"
|
|
||||||
#include "../net.h"
|
|
||||||
|
|
||||||
#if COLLA_TCC
|
#if COLLA_TCC
|
||||||
#include "../tcc/colla_tcc.h"
|
#include "colla_tcc.h"
|
||||||
|
#elif !COLLA_NO_NET
|
||||||
|
#include <wininet.h>
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
|
||||||
|
#if COLLA_CMT_LIB
|
||||||
|
#pragma comment(lib, "Wininet")
|
||||||
|
#pragma comment(lib, "Ws2_32")
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
str_t str_os_from_str16(arena_t *arena, str16_t src) {
|
||||||
|
str_t out = {0};
|
||||||
|
|
||||||
|
int outlen = WideCharToMultiByte(
|
||||||
|
CP_UTF8, 0,
|
||||||
|
src.buf, (int)src.len,
|
||||||
|
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.buf);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
err("couldn't translate wide string (%S) to utf8, %v", src.buf, os_get_error_string(os_get_last_error()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return STR_EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.buf = alloc(arena, char, outlen + 1);
|
||||||
|
WideCharToMultiByte(
|
||||||
|
CP_UTF8, 0,
|
||||||
|
src.buf, (int)src.len,
|
||||||
|
out.buf, outlen,
|
||||||
|
NULL, NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
out.len = outlen;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
str16_t strv_os_to_str16(arena_t *arena, strview_t src) {
|
||||||
|
str16_t out = {0};
|
||||||
|
|
||||||
|
if (strv_is_empty(src)) {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
int len = MultiByteToWideChar(
|
||||||
|
CP_UTF8, 0,
|
||||||
|
src.buf, (int)src.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", src);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
err("couldn't translate string (%v) to a wide string, %v", src, os_get_error_string(os_get_last_error()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.buf = alloc(arena, wchar_t, len + 1);
|
||||||
|
|
||||||
|
MultiByteToWideChar(
|
||||||
|
CP_UTF8, 0,
|
||||||
|
src.buf, (int)src.len,
|
||||||
|
out.buf, len
|
||||||
|
);
|
||||||
|
|
||||||
|
out.len = len;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
typedef enum os_entity_kind_e {
|
typedef enum os_entity_kind_e {
|
||||||
OS_KIND_NULL,
|
OS_KIND_NULL,
|
||||||
OS_KIND_THREAD,
|
OS_KIND_THREAD,
|
||||||
|
|
@ -502,12 +578,42 @@ os_env_t *os_get_env(arena_t *arena) {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
oshandle_t os_run_cmd_async(arena_t scratch, os_cmd_t *cmd, os_env_t *optional_env) {
|
oshandle_t os_run_cmd_async(arena_t scratch, os_cmd_t *cmd, os_cmd_options_t *options) {
|
||||||
|
HANDLE hstdout_read = NULL;
|
||||||
|
HANDLE hstderr_read = NULL;
|
||||||
|
HANDLE hstdin_write = NULL;
|
||||||
|
HANDLE hstdout_write = NULL;
|
||||||
|
HANDLE hstderr_write = NULL;
|
||||||
|
HANDLE hstdin_read = NULL;
|
||||||
|
|
||||||
|
SECURITY_ATTRIBUTES sa_attr = {
|
||||||
|
.nLength = sizeof(SECURITY_ATTRIBUTES),
|
||||||
|
.bInheritHandle = TRUE,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options && options->out) {
|
||||||
|
CreatePipe(&hstdout_read, &hstdout_write, &sa_attr, 0);
|
||||||
|
options->out->data = (uptr)hstdout_read;
|
||||||
|
SetHandleInformation(hstdout_read, HANDLE_FLAG_INHERIT, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options && options->error) {
|
||||||
|
CreatePipe(&hstderr_read, &hstderr_write, &sa_attr, 0);
|
||||||
|
options->error->data = (uptr)hstderr_read;
|
||||||
|
SetHandleInformation(hstderr_read, HANDLE_FLAG_INHERIT, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options && options->in) {
|
||||||
|
CreatePipe(&hstdin_read, &hstdin_write, &sa_attr, 0);
|
||||||
|
options->in->data = (uptr)hstdin_read;
|
||||||
|
SetHandleInformation(hstdin_write, HANDLE_FLAG_INHERIT, 0);
|
||||||
|
}
|
||||||
|
|
||||||
STARTUPINFOW start_info = {
|
STARTUPINFOW start_info = {
|
||||||
.cb = sizeof(STARTUPINFO),
|
.cb = sizeof(STARTUPINFO),
|
||||||
.hStdError = GetStdHandle(STD_ERROR_HANDLE),
|
.hStdError = hstderr_write ? hstderr_write : GetStdHandle(STD_ERROR_HANDLE),
|
||||||
.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE),
|
.hStdOutput = hstdout_write ? hstdout_write : GetStdHandle(STD_OUTPUT_HANDLE),
|
||||||
.hStdInput = GetStdHandle(STD_INPUT_HANDLE),
|
.hStdInput = hstdin_read ? hstdin_read : GetStdHandle(STD_INPUT_HANDLE),
|
||||||
.dwFlags = STARTF_USESTDHANDLES,
|
.dwFlags = STARTF_USESTDHANDLES,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -534,7 +640,7 @@ oshandle_t os_run_cmd_async(arena_t scratch, os_cmd_t *cmd, os_env_t *optional_e
|
||||||
strview_t cmd_view = ostr_as_view(&cmdline);
|
strview_t cmd_view = ostr_as_view(&cmdline);
|
||||||
str16_t command = strv_to_str16(&scratch, cmd_view);
|
str16_t command = strv_to_str16(&scratch, cmd_view);
|
||||||
|
|
||||||
WCHAR *env = optional_env ? optional_env->data : NULL;
|
WCHAR* env = (options && options->env) ? options->env->data : NULL;
|
||||||
|
|
||||||
BOOL success = CreateProcessW(
|
BOOL success = CreateProcessW(
|
||||||
NULL,
|
NULL,
|
||||||
|
|
@ -549,9 +655,21 @@ oshandle_t os_run_cmd_async(arena_t scratch, os_cmd_t *cmd, os_env_t *optional_e
|
||||||
&proc_info
|
&proc_info
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (hstdout_write) {
|
||||||
|
CloseHandle(hstdout_write);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hstderr_write) {
|
||||||
|
CloseHandle(hstderr_write);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hstdin_read) {
|
||||||
|
CloseHandle(hstdin_read);
|
||||||
|
}
|
||||||
|
|
||||||
if (env) {
|
if (env) {
|
||||||
FreeEnvironmentStringsW(env);
|
FreeEnvironmentStringsW(env);
|
||||||
optional_env->data = NULL;
|
options->env->data = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
|
@ -749,3 +867,342 @@ void os_cond_wait(oshandle_t cond, oshandle_t mutex, int milliseconds) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if !COLLA_NO_NET
|
||||||
|
|
||||||
|
struct {
|
||||||
|
HINTERNET internet;
|
||||||
|
} http_win = {0};
|
||||||
|
|
||||||
|
void net_init(void) {
|
||||||
|
if (http_win.internet) return;
|
||||||
|
http_win.internet = InternetOpen(
|
||||||
|
TEXT("COLLA_WININET"),
|
||||||
|
INTERNET_OPEN_TYPE_PRECONFIG,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
WSADATA wsdata = {0};
|
||||||
|
if (WSAStartup(0x0202, &wsdata)) {
|
||||||
|
fatal("couldn't startup sockets: %v", os_get_error_string(WSAGetLastError()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void net_cleanup(void) {
|
||||||
|
if (!http_win.internet) return;
|
||||||
|
InternetCloseHandle(http_win.internet);
|
||||||
|
http_win.internet = NULL;
|
||||||
|
WSACleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
iptr net_get_last_error(void) {
|
||||||
|
return WSAGetLastError();
|
||||||
|
}
|
||||||
|
|
||||||
|
http_res_t http_request(http_request_desc_t *req) {
|
||||||
|
return http_request_cb(req, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
http_res_t http_request_cb(http_request_desc_t *req, http_request_callback_fn callback, void *userdata) {
|
||||||
|
HINTERNET connection = NULL;
|
||||||
|
HINTERNET request = NULL;
|
||||||
|
BOOL result = FALSE;
|
||||||
|
bool success = false;
|
||||||
|
http_res_t res = {0};
|
||||||
|
arena_t arena_before = *req->arena;
|
||||||
|
|
||||||
|
if (!http_win.internet) {
|
||||||
|
err("net_init has not been called");
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
http_url_t split = http_split_url(req->url);
|
||||||
|
strview_t server = split.host;
|
||||||
|
strview_t page = split.uri;
|
||||||
|
|
||||||
|
if (strv_starts_with_view(server, strv("http://"))) {
|
||||||
|
server = strv_remove_prefix(server, 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strv_starts_with_view(server, strv("https://"))) {
|
||||||
|
server = strv_remove_prefix(server, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
arena_t scratch = *req->arena;
|
||||||
|
|
||||||
|
if (req->version.major == 0) req->version.major = 1;
|
||||||
|
if (req->version.minor == 0) req->version.minor = 1;
|
||||||
|
|
||||||
|
const TCHAR *accepted_types[] = { TEXT("*/*"), NULL };
|
||||||
|
const char *method = http_get_method_string(req->request_type);
|
||||||
|
str_t http_ver = str_fmt(&scratch, "HTTP/%u.%u", req->version.major, req->version.minor);
|
||||||
|
|
||||||
|
tstr_t tserver = strv_to_tstr(&scratch, server);
|
||||||
|
tstr_t tpage = strv_to_tstr(&scratch, page);
|
||||||
|
tstr_t tmethod = strv_to_tstr(&scratch, strv(method));
|
||||||
|
tstr_t thttp_ver = strv_to_tstr(&scratch, strv(http_ver));
|
||||||
|
|
||||||
|
connection = InternetConnect(
|
||||||
|
http_win.internet,
|
||||||
|
tserver.buf,
|
||||||
|
INTERNET_DEFAULT_HTTPS_PORT,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
INTERNET_SERVICE_HTTP,
|
||||||
|
0,
|
||||||
|
(DWORD_PTR)NULL // userdata
|
||||||
|
);
|
||||||
|
if (!connection) {
|
||||||
|
err("call to InternetConnect failed: %u", os_get_error_string(os_get_last_error()));
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
request = HttpOpenRequest(
|
||||||
|
connection,
|
||||||
|
tmethod.buf,
|
||||||
|
tpage.buf,
|
||||||
|
thttp_ver.buf,
|
||||||
|
NULL,
|
||||||
|
accepted_types,
|
||||||
|
INTERNET_FLAG_SECURE,
|
||||||
|
(DWORD_PTR)NULL // userdata
|
||||||
|
);
|
||||||
|
if (!request) {
|
||||||
|
err("call to HttpOpenRequest failed: %v", os_get_error_string(os_get_last_error()));
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < req->header_count; ++i) {
|
||||||
|
http_header_t *h = &req->headers[i];
|
||||||
|
arena_t scratch = *req->arena;
|
||||||
|
|
||||||
|
str_t header = str_fmt(&scratch, "%v: %v\r\n", h->key, h->value);
|
||||||
|
tstr_t theader = strv_to_tstr(&scratch, strv(header));
|
||||||
|
HttpAddRequestHeaders(request, theader.buf, (DWORD)theader.len, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = HttpSendRequest(
|
||||||
|
request,
|
||||||
|
NULL,
|
||||||
|
0,
|
||||||
|
(void *)req->body.buf,
|
||||||
|
(DWORD)req->body.len
|
||||||
|
);
|
||||||
|
if (!result) {
|
||||||
|
iptr error = os_get_last_error();
|
||||||
|
if (error == ERROR_INTERNET_NAME_NOT_RESOLVED) {
|
||||||
|
err("invalid url: %v", req->url);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
err("call to HttpSendRequest failed: %lld", error);
|
||||||
|
// os_get_error_string(error));
|
||||||
|
}
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 smallbuf[KB(5)];
|
||||||
|
DWORD bufsize = sizeof(smallbuf);
|
||||||
|
|
||||||
|
u8 *buffer = smallbuf;
|
||||||
|
|
||||||
|
// try and read it into a static buffer
|
||||||
|
result = HttpQueryInfo(request, HTTP_QUERY_RAW_HEADERS_CRLF, smallbuf, &bufsize, NULL);
|
||||||
|
|
||||||
|
// buffer is not big enough, allocate one with the arena instead
|
||||||
|
if (!result && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
|
||||||
|
info("buffer is too small");
|
||||||
|
buffer = alloc(req->arena, u8, bufsize + 1);
|
||||||
|
result = HttpQueryInfo(request, HTTP_QUERY_RAW_HEADERS_CRLF, buffer, &bufsize, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
err("couldn't get headers");
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
tstr_t theaders = { (TCHAR *)buffer, bufsize };
|
||||||
|
str_t headers = str_from_tstr(req->arena, theaders);
|
||||||
|
|
||||||
|
res.headers = http_parse_headers(req->arena, strv(headers));
|
||||||
|
res.version = req->version;
|
||||||
|
|
||||||
|
DWORD status_code = 0;
|
||||||
|
DWORD status_code_len = sizeof(status_code);
|
||||||
|
result = HttpQueryInfo(request, HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_STATUS_CODE, &status_code, &status_code_len, 0);
|
||||||
|
if (!result) {
|
||||||
|
err("couldn't get status code");
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status_code = status_code;
|
||||||
|
|
||||||
|
outstream_t body = ostr_init(req->arena);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
DWORD read = 0;
|
||||||
|
char read_buffer[4096];
|
||||||
|
BOOL read_success = InternetReadFile(
|
||||||
|
request, read_buffer, sizeof(read_buffer), &read
|
||||||
|
);
|
||||||
|
if (!read_success || read == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
strview_t chunk = strv(read_buffer, read);
|
||||||
|
if (callback) {
|
||||||
|
callback(chunk, userdata);
|
||||||
|
}
|
||||||
|
ostr_puts(&body, chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.body = strv(ostr_to_str(&body));
|
||||||
|
|
||||||
|
success = true;
|
||||||
|
|
||||||
|
failed:
|
||||||
|
if (request) InternetCloseHandle(request);
|
||||||
|
if (connection) InternetCloseHandle(connection);
|
||||||
|
if (!success) *req->arena = arena_before;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SOCKETS //////////////////////////
|
||||||
|
|
||||||
|
SOCKADDR_IN sk__addrin_in(const char *ip, u16 port) {
|
||||||
|
SOCKADDR_IN sk_addr = {
|
||||||
|
.sin_family = AF_INET,
|
||||||
|
.sin_port = htons(port),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!inet_pton(AF_INET, ip, &sk_addr.sin_addr)) {
|
||||||
|
err("inet_pton failed: %v", os_get_error_string(net_get_last_error()));
|
||||||
|
return (SOCKADDR_IN){0};
|
||||||
|
}
|
||||||
|
|
||||||
|
return sk_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket_t sk_open(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return socket(AF_INET, sock_type, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
socket_t sk_open_protocol(const char *protocol) {
|
||||||
|
struct protoent *proto = getprotobyname(protocol);
|
||||||
|
if(!proto) {
|
||||||
|
return INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
return socket(AF_INET, SOCK_STREAM, proto->p_proto);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sk_is_valid(socket_t sock) {
|
||||||
|
return sock != INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sk_close(socket_t sock) {
|
||||||
|
return closesocket(sock) != SOCKET_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sk_bind(socket_t sock, const char *ip, u16 port) {
|
||||||
|
SOCKADDR_IN sk_addr = sk__addrin_in(ip, port);
|
||||||
|
if (sk_addr.sin_family == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return bind(sock, (SOCKADDR*)&sk_addr, sizeof(sk_addr)) != SOCKET_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sk_listen(socket_t sock, int backlog) {
|
||||||
|
return listen(sock, backlog) != SOCKET_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket_t sk_accept(socket_t sock) {
|
||||||
|
SOCKADDR_IN addr = {0};
|
||||||
|
int addr_size = sizeof(addr);
|
||||||
|
return accept(sock, (SOCKADDR*)&addr, &addr_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sk_connect(socket_t sock, const char *server, u16 server_port) {
|
||||||
|
u8 tmpbuf[1024] = {0};
|
||||||
|
arena_t scratch = arena_make(ARENA_STATIC, sizeof(tmpbuf), tmpbuf);
|
||||||
|
|
||||||
|
str16_t wserver = strv_to_str16(&scratch, strv(server));
|
||||||
|
|
||||||
|
ADDRINFOW *addrinfo = NULL;
|
||||||
|
int result = GetAddrInfoW(wserver.buf, NULL, NULL, &addrinfo);
|
||||||
|
if (result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char ip_str[1024] = {0};
|
||||||
|
inet_ntop(addrinfo->ai_family, addrinfo->ai_addr, ip_str, sizeof(ip_str));
|
||||||
|
|
||||||
|
SOCKADDR_IN sk_addr = sk__addrin_in(ip_str, server_port);
|
||||||
|
if (sk_addr.sin_family == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return connect(sock, (SOCKADDR*)&sk_addr, sizeof(sk_addr)) != SOCKET_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sk_send(socket_t sock, const void *buf, int len) {
|
||||||
|
return send(sock, (const char *)buf, len, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int sk_recv(socket_t sock, void *buf, int len) {
|
||||||
|
return recv(sock, (char *)buf, len, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int sk_poll(skpoll_t *to_poll, int num_to_poll, int timeout) {
|
||||||
|
return WSAPoll((WSAPOLLFD*)to_poll, (ULONG)num_to_poll, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
oshandle_t sk_bind_event(socket_t sock, skevent_e event) {
|
||||||
|
if (event == SOCK_EVENT_NONE) {
|
||||||
|
return os_handle_zero();
|
||||||
|
}
|
||||||
|
|
||||||
|
HANDLE handle = WSACreateEvent();
|
||||||
|
if (handle == WSA_INVALID_EVENT) {
|
||||||
|
return os_handle_zero();
|
||||||
|
}
|
||||||
|
|
||||||
|
int wsa_event = 0;
|
||||||
|
if (event & SOCK_EVENT_READ) wsa_event |= FD_READ;
|
||||||
|
if (event & SOCK_EVENT_WRITE) wsa_event |= FD_WRITE;
|
||||||
|
if (event & SOCK_EVENT_ACCEPT) wsa_event |= FD_ACCEPT;
|
||||||
|
if (event & SOCK_EVENT_CONNECT) wsa_event |= FD_CONNECT;
|
||||||
|
if (event & SOCK_EVENT_CLOSE) wsa_event |= FD_CLOSE;
|
||||||
|
|
||||||
|
if (WSAEventSelect(sock, handle, wsa_event) != 0) {
|
||||||
|
WSACloseEvent(handle);
|
||||||
|
return os_handle_zero();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (oshandle_t){ .data = (uptr)handle };
|
||||||
|
}
|
||||||
|
|
||||||
|
void sk_reset_event(oshandle_t handle) {
|
||||||
|
if (!os_handle_valid(handle)) {
|
||||||
|
warn("invalid handle");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
WSAResetEvent((HANDLE)handle.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sk_destroy_event(oshandle_t handle) {
|
||||||
|
if (!os_handle_valid(handle)) return;
|
||||||
|
WSACloseEvent((HANDLE)handle.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
81
core.c
81
core.c
|
|
@ -1,81 +0,0 @@
|
||||||
#include "core.h"
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#if COLLA_CLANG
|
|
||||||
#pragma clang diagnostic push
|
|
||||||
#pragma clang diagnostic ignored "-Weverything"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define STB_SPRINTF_DECORATE(name) colla_stb_##name
|
|
||||||
#define STB_SPRINTF_NOUNALIGNED
|
|
||||||
#define STB_SPRINTF_IMPLEMENTATION
|
|
||||||
#include "stb/stb_sprintf.h"
|
|
||||||
|
|
||||||
#if COLLA_CLANG
|
|
||||||
#pragma clang diagnostic pop
|
|
||||||
#endif
|
|
||||||
|
|
||||||
colla_modules_e colla__initialised_modules = 0;
|
|
||||||
|
|
||||||
extern void os_init(void);
|
|
||||||
extern void os_cleanup(void);
|
|
||||||
#if !COLLA_NO_NET
|
|
||||||
extern void net_init(void);
|
|
||||||
extern void net_cleanup(void);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static char *colla_fmt__stb_callback(const char *buf, void *ud, int len) {
|
|
||||||
fflush(stdout);
|
|
||||||
fwrite(buf, 1, len, stdout);
|
|
||||||
return (char *)ud;
|
|
||||||
}
|
|
||||||
|
|
||||||
void colla_init(colla_modules_e modules) {
|
|
||||||
colla__initialised_modules = modules;
|
|
||||||
if (modules & COLLA_OS) {
|
|
||||||
os_init();
|
|
||||||
}
|
|
||||||
#if !COLLA_NO_NET
|
|
||||||
if (modules & COLLA_NET) {
|
|
||||||
net_init();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void colla_cleanup(void) {
|
|
||||||
colla_modules_e modules = colla__initialised_modules;
|
|
||||||
if (modules & COLLA_OS) {
|
|
||||||
os_cleanup();
|
|
||||||
}
|
|
||||||
#if !COLLA_NO_NET
|
|
||||||
if (modules & COLLA_NET) {
|
|
||||||
net_cleanup();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
int fmt_print(const char *fmt, ...) {
|
|
||||||
va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
int out = fmt_printv(fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
int fmt_printv(const char *fmt, va_list args) {
|
|
||||||
char buffer[STB_SPRINTF_MIN] = {0};
|
|
||||||
return colla_stb_vsprintfcb(colla_fmt__stb_callback, buffer, buffer, fmt, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
int fmt_buffer(char *buf, usize len, const char *fmt, ...) {
|
|
||||||
va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
int out = fmt_bufferv(buf, len, fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
int fmt_bufferv(char *buf, usize len, const char *fmt, va_list args) {
|
|
||||||
return colla_stb_vsnprintf(buf, (int)len, fmt, args);
|
|
||||||
}
|
|
||||||
222
core.h
222
core.h
|
|
@ -1,222 +0,0 @@
|
||||||
#ifndef COLLA_CORE_H
|
|
||||||
#define COLLA_CORE_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
// CORE MODULES /////////////////////////////////
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
COLLA_CORE = 0,
|
|
||||||
COLLA_OS = 1 << 0,
|
|
||||||
COLLA_NET = 1 << 1,
|
|
||||||
COLLA_ALL = 0xff,
|
|
||||||
} colla_modules_e;
|
|
||||||
|
|
||||||
void colla_init(colla_modules_e modules);
|
|
||||||
void colla_cleanup(void);
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// LINKED LISTS /////////////////////////////////
|
|
||||||
|
|
||||||
// singly linked list
|
|
||||||
#define list_push(list, item) ((item)->next=(list), (list)=(item))
|
|
||||||
#define list_pop(list) ((list) = (list) ? (list)->next : NULL)
|
|
||||||
|
|
||||||
// double linked list
|
|
||||||
#define dlist_push(list, item) do { \
|
|
||||||
if (item) (item)->next = (list); \
|
|
||||||
if (list) (list)->prev = (item); \
|
|
||||||
(list) = (item); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#define dlist_pop(list, item) do { \
|
|
||||||
if (!(item)) break; \
|
|
||||||
if ((item)->prev) (item)->prev->next = (item)->next; \
|
|
||||||
if ((item)->next) (item)->next->prev = (item)->prev; \
|
|
||||||
if ((item) == (list)) (list) = (item)->next; \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
// ordered linked list
|
|
||||||
|
|
||||||
#define olist_push(head, tail, item) do { \
|
|
||||||
if (tail) { \
|
|
||||||
(tail)->next = (item); \
|
|
||||||
(tail) = (item); \
|
|
||||||
} \
|
|
||||||
else { \
|
|
||||||
(head) = (tail) = (item); \
|
|
||||||
} \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#define for_each(it, list) for (typeof(list) it = list; it; it = it->next)
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// OS AND COMPILER MACROS ///////////////////////
|
|
||||||
|
|
||||||
#if defined(_DEBUG)
|
|
||||||
#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
|
|
||||||
#define COLLA_EMC 0
|
|
||||||
#elif defined(__EMSCRIPTEN__)
|
|
||||||
#define COLLA_WIN 0
|
|
||||||
#define COLLA_OSX 0
|
|
||||||
#define COLLA_LIN 0
|
|
||||||
#define COLLA_EMC 1
|
|
||||||
#elif defined(__linux__)
|
|
||||||
#define COLLA_WIN 0
|
|
||||||
#define COLLA_OSX 0
|
|
||||||
#define COLLA_LIN 1
|
|
||||||
#define COLLA_EMC 0
|
|
||||||
#elif defined(__APPLE__)
|
|
||||||
#define COLLA_WIN 0
|
|
||||||
#define COLLA_OSX 1
|
|
||||||
#define COLLA_LIN 0
|
|
||||||
#define COLLA_EMC 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(__COSMOPOLITAN__)
|
|
||||||
#define COLLA_COSMO 1
|
|
||||||
#else
|
|
||||||
#define COLLA_COSMO 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define COLLA_POSIX (COLLA_OSX || COLLA_LIN || COLLA_COSMO)
|
|
||||||
|
|
||||||
#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
|
|
||||||
|
|
||||||
#if COLLA_TCC
|
|
||||||
#define alignof __alignof__
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if COLLA_WIN
|
|
||||||
#undef NOMINMAX
|
|
||||||
#undef WIN32_LEAN_AND_MEAN
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
|
||||||
#define NOMINMAX
|
|
||||||
|
|
||||||
#ifdef UNICODE
|
|
||||||
#define COLLA_UNICODE 1
|
|
||||||
#else
|
|
||||||
#define COLLA_UNICODE 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// USEFUL MACROS ////////////////////////////////
|
|
||||||
|
|
||||||
#define arrlen(a) (sizeof(a) / sizeof((a)[0]))
|
|
||||||
#define COLLA_UNUSED(v) (void)(v)
|
|
||||||
|
|
||||||
#define COLLA__STRINGIFY(x) #x
|
|
||||||
#define COLLA_STRINGIFY(x) COLLA__STRINGIFY(x)
|
|
||||||
|
|
||||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
|
||||||
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
|
||||||
|
|
||||||
#define KB(n) (((u64)n) << 10)
|
|
||||||
#define MB(n) (((u64)n) << 20)
|
|
||||||
#define GB(n) (((u64)n) << 30)
|
|
||||||
#define TB(n) (((u64)n) << 40)
|
|
||||||
|
|
||||||
#if COLLA_DEBUG
|
|
||||||
#define colla__assert(file, line, cond, ...) do { if (!(cond)) fatal(file ":" line " ASSERT FAILED: (" COLLA__STRINGIFY(cond) ") " __VA_ARGS__); } while (0)
|
|
||||||
#define colla_assert(...) colla__assert(__FILE__, COLLA__STRINGIFY(__LINE__), __VA_ARGS__)
|
|
||||||
#else
|
|
||||||
#define colla_assert(...) (void)0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// BASIC TYPES //////////////////////////////////
|
|
||||||
|
|
||||||
#if COLLA_WIN && COLLA_UNICODE
|
|
||||||
typedef wchar_t TCHAR;
|
|
||||||
#else
|
|
||||||
typedef char TCHAR;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef unsigned char uchar;
|
|
||||||
typedef unsigned short ushort;
|
|
||||||
typedef unsigned int uint;
|
|
||||||
|
|
||||||
typedef uint8_t u8;
|
|
||||||
typedef uint16_t u16;
|
|
||||||
typedef uint32_t u32;
|
|
||||||
typedef uint64_t u64;
|
|
||||||
|
|
||||||
typedef int8_t i8;
|
|
||||||
typedef int16_t i16;
|
|
||||||
typedef int32_t i32;
|
|
||||||
typedef int64_t i64;
|
|
||||||
|
|
||||||
typedef size_t usize;
|
|
||||||
typedef ptrdiff_t isize;
|
|
||||||
|
|
||||||
typedef uintptr_t uptr;
|
|
||||||
typedef intptr_t iptr;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
u8 *data;
|
|
||||||
usize len;
|
|
||||||
} buffer_t;
|
|
||||||
|
|
||||||
typedef struct arena_t arena_t;
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// FORMATTING ///////////////////////////////////
|
|
||||||
|
|
||||||
int fmt_print(const char *fmt, ...);
|
|
||||||
int fmt_printv(const char *fmt, va_list args);
|
|
||||||
int fmt_buffer(char *buf, usize len, const char *fmt, ...);
|
|
||||||
int fmt_bufferv(char *buf, usize len, const char *fmt, va_list args);
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////
|
|
||||||
|
|
||||||
#endif
|
|
||||||
82
darr.h
82
darr.h
|
|
@ -1,82 +0,0 @@
|
||||||
#ifndef COLLA_DARR_HEADER
|
|
||||||
#define COLLA_DARR_HEADER
|
|
||||||
|
|
||||||
/*
|
|
||||||
dynamic chunked array which uses an arena to allocate,
|
|
||||||
the structure needs to follow this exact format, if you want
|
|
||||||
you can use the macro darr_define(struct_name, item_type) instead:
|
|
||||||
|
|
||||||
////////////////////////////////////
|
|
||||||
|
|
||||||
typedef struct arr_t arr_t;
|
|
||||||
struct arr_t {
|
|
||||||
int *items;
|
|
||||||
usize block_size;
|
|
||||||
usize count;
|
|
||||||
arr_t *next;
|
|
||||||
arr_t *head;
|
|
||||||
};
|
|
||||||
// equivalent to
|
|
||||||
|
|
||||||
darr_define(arr_t, int);
|
|
||||||
|
|
||||||
////////////////////////////////////
|
|
||||||
|
|
||||||
by default a chunk is 64 items long, you can change this default
|
|
||||||
by modifying the arr.block_size value before adding to the array,
|
|
||||||
or by defining DARRAY_DEFAULT_BLOCK_SIZE
|
|
||||||
|
|
||||||
usage example:
|
|
||||||
|
|
||||||
////////////////////////////////////
|
|
||||||
|
|
||||||
darr_define(arr_t, int);
|
|
||||||
|
|
||||||
arr_t *arr = NULL;
|
|
||||||
|
|
||||||
for (int i = 0; i < 100; ++i) {
|
|
||||||
darr_push(&arena, arr, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
for_each (chunk, arr) {
|
|
||||||
for (int i = 0; i < chunk->count; ++i) {
|
|
||||||
info("%d -> %d", i, chunk->items[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define DARRAY_DEFAULT_BLOCK_SIZE (64)
|
|
||||||
|
|
||||||
#define darr_define(struct_name, item_type) typedef struct struct_name struct_name; \
|
|
||||||
struct struct_name { \
|
|
||||||
item_type *items; \
|
|
||||||
usize block_size; \
|
|
||||||
usize count; \
|
|
||||||
struct_name *next; \
|
|
||||||
struct_name *head; \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define darr__alloc_first(arena, arr) do { \
|
|
||||||
(arr) = (arr) ? (arr) : alloc(arena, typeof(*arr)); \
|
|
||||||
(arr)->head = (arr)->head ? (arr)->head : (arr); \
|
|
||||||
(arr)->block_size = (arr)->block_size ? (arr)->block_size : DARRAY_DEFAULT_BLOCK_SIZE; \
|
|
||||||
(arr)->items = alloc(arena, typeof(*arr->items), arr->block_size); \
|
|
||||||
assert((arr)->count == 0); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#define darr__alloc_block(arena, arr) do { \
|
|
||||||
typeof(arr) newarr = alloc(arena, typeof(*arr)); \
|
|
||||||
newarr->block_size = arr->block_size; \
|
|
||||||
newarr->items = alloc(arena, typeof(*arr->items), arr->block_size); \
|
|
||||||
newarr->head = arr->head; \
|
|
||||||
arr->next = newarr; \
|
|
||||||
arr = newarr; \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#define darr_push(arena, arr, item) do { \
|
|
||||||
if (!(arr) || (arr)->items == NULL) darr__alloc_first(arena, arr); \
|
|
||||||
if ((arr)->count >= (arr)->block_size) darr__alloc_block(arena, arr); \
|
|
||||||
(arr)->items[(arr)->count++] = (item); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#endif
|
|
||||||
9
example.c
Normal file
9
example.c
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
#include "colla.c"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
colla_init(COLLA_ALL);
|
||||||
|
|
||||||
|
info("hello world!");
|
||||||
|
|
||||||
|
colla_cleanup();
|
||||||
|
}
|
||||||
629
highlight.c
629
highlight.c
|
|
@ -1,629 +0,0 @@
|
||||||
#include "highlight.h"
|
|
||||||
|
|
||||||
// based on https://github.com/Theldus/kat
|
|
||||||
|
|
||||||
#include "arena.h"
|
|
||||||
#include "str.h"
|
|
||||||
#include "os.h"
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
HL_STATE_DEFAULT,
|
|
||||||
HL_STATE_KEYWORD,
|
|
||||||
HL_STATE_NUMBER,
|
|
||||||
HL_STATE_CHAR,
|
|
||||||
HL_STATE_STRING,
|
|
||||||
HL_STATE_COMMENT_MULTI,
|
|
||||||
HL_STATE_PREPROCESSOR,
|
|
||||||
HL_STATE_PREPROCESSOR_INCLUDE,
|
|
||||||
HL_STATE_PREPROCESSOR_INCLUDE_STRING,
|
|
||||||
} hl_state_e;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
HL_HTABLE_FAILED,
|
|
||||||
HL_HTABLE_REPLACED,
|
|
||||||
HL_HTABLE_ADDED,
|
|
||||||
} hl_htable_result_e;
|
|
||||||
|
|
||||||
typedef struct hl_node_t {
|
|
||||||
strview_t key;
|
|
||||||
hl_color_e value;
|
|
||||||
struct hl_node_t *next;
|
|
||||||
} hl_node_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
hl_node_t **buckets;
|
|
||||||
uint count;
|
|
||||||
uint used;
|
|
||||||
uint collisions;
|
|
||||||
} hl_hashtable_t;
|
|
||||||
|
|
||||||
static hl_hashtable_t hl_htable_init(arena_t *arena, uint pow2_exp);
|
|
||||||
static hl_htable_result_e hl_htable_add(arena_t *arena, hl_hashtable_t *table, strview_t key, hl_color_e value);
|
|
||||||
static hl_node_t *hl_htable_get(hl_hashtable_t *table, strview_t key);
|
|
||||||
static u64 hl_htable_hash(const void *bytes, usize count);
|
|
||||||
|
|
||||||
typedef struct hl_ctx_t {
|
|
||||||
hl_state_e state;
|
|
||||||
hl_flags_e flags;
|
|
||||||
usize kw_beg;
|
|
||||||
strview_t colors[HL_COLOR__COUNT]; // todo: maybe should be str_t?
|
|
||||||
outstream_t ostr;
|
|
||||||
hl_hashtable_t kw_htable;
|
|
||||||
bool symbol_table[256];
|
|
||||||
} hl_ctx_t;
|
|
||||||
|
|
||||||
#define KW(str, col) { { str, sizeof(str)-1 }, HL_COLOR_##col }
|
|
||||||
|
|
||||||
static hl_keyword_t hl_c_cpp_kwrds[] = {
|
|
||||||
/* C Types. */
|
|
||||||
KW("double", TYPES),
|
|
||||||
KW("int", TYPES),
|
|
||||||
KW("long", TYPES),
|
|
||||||
KW("char", TYPES),
|
|
||||||
KW("float", TYPES),
|
|
||||||
KW("short", TYPES),
|
|
||||||
KW("unsigned", TYPES),
|
|
||||||
KW("signed", TYPES),
|
|
||||||
KW("bool", TYPES),
|
|
||||||
|
|
||||||
/* Common typedefs. */
|
|
||||||
KW("int8_t", TYPES), KW("uint8_t", TYPES),
|
|
||||||
KW("int16_t", TYPES), KW("uint16_t", TYPES),
|
|
||||||
KW("int32_t", TYPES), KW("uint32_t", TYPES),
|
|
||||||
KW("int64_t", TYPES), KW("uint64_t", TYPES),
|
|
||||||
KW("int8", TYPES), KW("uint8", TYPES),
|
|
||||||
KW("int16", TYPES), KW("uint16", TYPES),
|
|
||||||
KW("int32", TYPES), KW("uint32", TYPES),
|
|
||||||
KW("int64", TYPES), KW("uint64", TYPES),
|
|
||||||
KW("i8", TYPES), KW("u8", TYPES),
|
|
||||||
KW("i16", TYPES), KW("u16", TYPES),
|
|
||||||
KW("i32", TYPES), KW("u32", TYPES),
|
|
||||||
KW("i64", TYPES), KW("u64", TYPES),
|
|
||||||
|
|
||||||
|
|
||||||
/* Colla keywords */
|
|
||||||
KW("uchar", TYPES),
|
|
||||||
KW("ushort", TYPES),
|
|
||||||
KW("uint", TYPES),
|
|
||||||
KW("usize", TYPES),
|
|
||||||
KW("isize", TYPES),
|
|
||||||
KW("byte", TYPES),
|
|
||||||
|
|
||||||
/* Other keywords. */
|
|
||||||
KW("auto", KEYWORDS), KW("struct", KEYWORDS), KW("break", KEYWORDS),
|
|
||||||
KW("else", KEYWORDS), KW("switch", KEYWORDS), KW("case", KEYWORDS),
|
|
||||||
KW("enum", KEYWORDS), KW("register", KEYWORDS), KW("typedef", KEYWORDS),
|
|
||||||
KW("extern", KEYWORDS), KW("return", KEYWORDS), KW("union", KEYWORDS),
|
|
||||||
KW("const", KEYWORDS), KW("continue", KEYWORDS), KW("for", KEYWORDS),
|
|
||||||
KW("void", KEYWORDS), KW("default", KEYWORDS), KW("goto", KEYWORDS),
|
|
||||||
KW("sizeof", KEYWORDS), KW("volatile", KEYWORDS), KW("do", KEYWORDS),
|
|
||||||
KW("if", KEYWORDS), KW("static", KEYWORDS), KW("inline", KEYWORDS),
|
|
||||||
KW("while", KEYWORDS),
|
|
||||||
};
|
|
||||||
|
|
||||||
#undef KW
|
|
||||||
|
|
||||||
static bool hl_default_symbols_table[256] = {
|
|
||||||
['['] = true, [']'] = true, ['('] = true,
|
|
||||||
[')'] = true, ['{'] = true, ['}'] = true,
|
|
||||||
['*'] = true, [':'] = true, ['='] = true,
|
|
||||||
[';'] = true, ['-'] = true, ['>'] = true,
|
|
||||||
['&'] = true, ['+'] = true, ['~'] = true,
|
|
||||||
['!'] = true, ['/'] = true, ['%'] = true,
|
|
||||||
['<'] = true, ['^'] = true, ['|'] = true,
|
|
||||||
['?'] = true, ['#'] = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void hl_write_char(hl_ctx_t *ctx, char c);
|
|
||||||
static void hl_write(hl_ctx_t *ctx, strview_t v);
|
|
||||||
static bool hl_is_char_keyword(char c);
|
|
||||||
static bool hl_highlight_symbol(hl_ctx_t *ctx, char c);
|
|
||||||
static hl_color_e hl_get_keyword_color(hl_ctx_t *ctx, strview_t keyword);
|
|
||||||
static bool hl_is_capitalised(strview_t string);
|
|
||||||
static strview_t hl_finish_keyword(hl_ctx_t *ctx, usize beg, instream_t *in);
|
|
||||||
static void hl_print_keyword(hl_ctx_t *ctx, strview_t keyword, hl_color_e color);
|
|
||||||
|
|
||||||
hl_ctx_t *hl_init(arena_t *arena, hl_config_t *config) {
|
|
||||||
if (!config) {
|
|
||||||
err("<config> cannot be null");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
hl_ctx_t *out = alloc(arena, hl_ctx_t);
|
|
||||||
|
|
||||||
out->flags = config->flags;
|
|
||||||
|
|
||||||
memcpy(out->symbol_table, hl_default_symbols_table, sizeof(hl_default_symbols_table));
|
|
||||||
memcpy(out->colors, config->colors, sizeof(config->colors));
|
|
||||||
|
|
||||||
int kw_count = arrlen(hl_c_cpp_kwrds);
|
|
||||||
|
|
||||||
out->kw_htable = hl_htable_init(arena, 8);
|
|
||||||
|
|
||||||
for (int i = 0; i < kw_count; ++i) {
|
|
||||||
hl_keyword_t *kw = &hl_c_cpp_kwrds[i];
|
|
||||||
hl_htable_add(arena, &out->kw_htable, kw->keyword, kw->color);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < config->kwrds_count; ++i) {
|
|
||||||
hl_keyword_t *kw = &config->extra_kwrds[i];
|
|
||||||
hl_htable_add(arena, &out->kw_htable, kw->keyword, kw->color);
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
void hl_next_char(hl_ctx_t *ctx, instream_t *in) {
|
|
||||||
char cur = istr_get(in);
|
|
||||||
bool is_last = istr_is_finished(in);
|
|
||||||
|
|
||||||
switch (ctx->state) {
|
|
||||||
case HL_STATE_DEFAULT:
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* If potential keyword.
|
|
||||||
*
|
|
||||||
* A valid C keyword may contain numbers, but *not*
|
|
||||||
* as a suffix.
|
|
||||||
*/
|
|
||||||
if (hl_is_char_keyword(cur) && !char_is_num(cur)) {
|
|
||||||
ctx->kw_beg = istr_tell(in);
|
|
||||||
ctx->state = HL_STATE_KEYWORD;
|
|
||||||
}
|
|
||||||
|
|
||||||
// potential number
|
|
||||||
else if (char_is_num(cur)) {
|
|
||||||
ctx->kw_beg = istr_tell(in);
|
|
||||||
ctx->state = HL_STATE_NUMBER;
|
|
||||||
}
|
|
||||||
|
|
||||||
// potential char
|
|
||||||
else if (cur == '\'') {
|
|
||||||
ctx->kw_beg = istr_tell(in);
|
|
||||||
ctx->state = HL_STATE_CHAR;
|
|
||||||
}
|
|
||||||
|
|
||||||
// potential string
|
|
||||||
else if (cur == '"') {
|
|
||||||
ctx->kw_beg = istr_tell(in);
|
|
||||||
ctx->state = HL_STATE_STRING;
|
|
||||||
}
|
|
||||||
|
|
||||||
// line or multiline comment
|
|
||||||
else if (cur == '/') {
|
|
||||||
// single line comment
|
|
||||||
if (istr_peek(in) == '/') {
|
|
||||||
// rewind before comment begins
|
|
||||||
istr_rewind_n(in, 1);
|
|
||||||
|
|
||||||
// comment until the end of line
|
|
||||||
hl_print_keyword(ctx, istr_get_line(in), HL_COLOR_COMMENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
// multiline comment
|
|
||||||
else if (istr_peek(in) == '*') {
|
|
||||||
ctx->state = HL_STATE_COMMENT_MULTI;
|
|
||||||
ctx->kw_beg = istr_tell(in);
|
|
||||||
istr_skip(in, 1); // skip *
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
// maybe a symbol?
|
|
||||||
hl_highlight_symbol(ctx, cur);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// preprocessor
|
|
||||||
else if (cur == '#') {
|
|
||||||
// print the # as a symbol
|
|
||||||
hl_highlight_symbol(ctx, cur);
|
|
||||||
ctx->kw_beg = istr_tell(in);
|
|
||||||
ctx->state = HL_STATE_PREPROCESSOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
// other suppored symbols
|
|
||||||
else if (hl_highlight_symbol(ctx, cur)) {
|
|
||||||
// noop
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
hl_write_char(ctx, cur);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case HL_STATE_KEYWORD:
|
|
||||||
{
|
|
||||||
// end of keyword, check if it really is a valid keyword
|
|
||||||
if (!hl_is_char_keyword(cur)) {
|
|
||||||
strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in);
|
|
||||||
hl_color_e kw_color = hl_get_keyword_color(ctx, keyword);
|
|
||||||
|
|
||||||
if (kw_color != HL_COLOR__COUNT) {
|
|
||||||
hl_print_keyword(ctx, keyword, kw_color);
|
|
||||||
|
|
||||||
// maybe we should highlight this remaining char.
|
|
||||||
if (!hl_highlight_symbol(ctx, cur)) {
|
|
||||||
hl_write_char(ctx, cur);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If not keyword, maybe its a function call.
|
|
||||||
*
|
|
||||||
* Important to note that this is hacky and will only work
|
|
||||||
* if there is no space between keyword and '('.
|
|
||||||
*/
|
|
||||||
else if (cur == '(') {
|
|
||||||
hl_print_keyword(ctx, keyword, HL_COLOR_FUNC);
|
|
||||||
|
|
||||||
// Opening parenthesis will always be highlighted
|
|
||||||
hl_highlight_symbol(ctx, cur);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (hl_is_capitalised(keyword)) {
|
|
||||||
hl_print_keyword(ctx, keyword, HL_COLOR_MACRO);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
hl_write(ctx, keyword);
|
|
||||||
}
|
|
||||||
if (!hl_highlight_symbol(ctx, cur)) {
|
|
||||||
hl_write_char(ctx, cur);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case HL_STATE_NUMBER:
|
|
||||||
{
|
|
||||||
char c = char_lower(cur);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Should we end the state?.
|
|
||||||
*
|
|
||||||
* Very important observation:
|
|
||||||
* Although the number highlight works fine for most (if not all)
|
|
||||||
* of the possible cases, it also assumes that the code is written
|
|
||||||
* correctly and the source is able to compile, meaning that:
|
|
||||||
*
|
|
||||||
* Numbers like: 123, 0xABC123, 12.3e4f, 123ULL....
|
|
||||||
* will be correctly identified and highlighted
|
|
||||||
*
|
|
||||||
* But, 'numbers' like: 123ABC, 0xxxxABCxx123, 123UUUUU....
|
|
||||||
* will also be highlighted.
|
|
||||||
*
|
|
||||||
* It also assumes that no keyword will start with a number
|
|
||||||
* and everything starting with a number (except inside strings or
|
|
||||||
* comments) will be a number.
|
|
||||||
*/
|
|
||||||
if (!char_is_num(c) &&
|
|
||||||
(c < 'a' || c > 'f') &&
|
|
||||||
c != 'b' && c != 'x' &&
|
|
||||||
c != 'u' && c != 'l' &&
|
|
||||||
c != '.'
|
|
||||||
) {
|
|
||||||
strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in);
|
|
||||||
|
|
||||||
// if not a valid char keyword: valid number
|
|
||||||
if (!hl_is_char_keyword(cur)) {
|
|
||||||
hl_print_keyword(ctx, keyword, HL_COLOR_NUMBER);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
hl_write(ctx, keyword);
|
|
||||||
}
|
|
||||||
|
|
||||||
// maybe we should highlight this remaining char.
|
|
||||||
if (!hl_highlight_symbol(ctx, cur)) {
|
|
||||||
hl_write_char(ctx, cur);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case HL_STATE_CHAR:
|
|
||||||
{
|
|
||||||
if (is_last || (cur == '\'' && istr_peek(in) != '\'')) {
|
|
||||||
strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in);
|
|
||||||
keyword.len++;
|
|
||||||
|
|
||||||
hl_print_keyword(ctx, keyword, HL_COLOR_STRING);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case HL_STATE_STRING:
|
|
||||||
{
|
|
||||||
if (is_last || (cur == '"' && istr_prev_prev(in) != '\\')) {
|
|
||||||
strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in);
|
|
||||||
keyword.len++;
|
|
||||||
|
|
||||||
hl_print_keyword(ctx, keyword, HL_COLOR_STRING);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case HL_STATE_COMMENT_MULTI:
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* If we are at the end of line _or_ have identified
|
|
||||||
* an end of comment...
|
|
||||||
*/
|
|
||||||
if (is_last || (cur == '*' && istr_peek(in) == '/')) {
|
|
||||||
strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in);
|
|
||||||
|
|
||||||
hl_print_keyword(ctx, keyword, HL_COLOR_COMMENT);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case HL_STATE_PREPROCESSOR:
|
|
||||||
{
|
|
||||||
|
|
||||||
if (!hl_is_char_keyword(cur)) {
|
|
||||||
hl_write_char(ctx, cur);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define hl_check(str, new_state) \
|
|
||||||
if (cur == str[0]) { \
|
|
||||||
instream_t temp = *in; \
|
|
||||||
strview_t a = { &(str[1]), sizeof(str) - 2 }; \
|
|
||||||
strview_t b = istr_get_view_len(&temp, a.len); \
|
|
||||||
if (strv_equals(a, b)) { \
|
|
||||||
*in = temp; \
|
|
||||||
hl_print_keyword(ctx, (strview_t){ str, sizeof(str) - 1 }, HL_COLOR_PREPROC); \
|
|
||||||
ctx->state = new_state; \
|
|
||||||
break; \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
if (is_last) {
|
|
||||||
strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in);
|
|
||||||
hl_print_keyword(ctx, keyword, HL_COLOR_PREPROC);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
hl_check("include", HL_STATE_PREPROCESSOR_INCLUDE)
|
|
||||||
hl_check("define", HL_STATE_DEFAULT)
|
|
||||||
hl_check("undef", HL_STATE_DEFAULT)
|
|
||||||
hl_check("ifdef", HL_STATE_DEFAULT)
|
|
||||||
hl_check("ifndef", HL_STATE_DEFAULT)
|
|
||||||
hl_check("if", HL_STATE_DEFAULT)
|
|
||||||
hl_check("endif", HL_STATE_DEFAULT)
|
|
||||||
hl_check("pragma", HL_STATE_DEFAULT)
|
|
||||||
|
|
||||||
#undef hl_check
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Preprocessor/Preprocessor include
|
|
||||||
*
|
|
||||||
* This is a 'dumb' preprocessor highlighter:
|
|
||||||
* it highlights everything with the same color
|
|
||||||
* and if and only if an '#include' is detected
|
|
||||||
* the included header will be handled as string
|
|
||||||
* and thus, will have the same color as the string.
|
|
||||||
*
|
|
||||||
* In fact, it is somehow similar to what GtkSourceView
|
|
||||||
* does (Mousepad, Gedit...) but with one silly difference:
|
|
||||||
* single-line/multi-line comments will not be handled
|
|
||||||
* while inside the preprocessor state, meaning that
|
|
||||||
* comments will also have the same color as the remaining
|
|
||||||
* of the line, yeah, ugly.
|
|
||||||
*/
|
|
||||||
case HL_STATE_PREPROCESSOR_INCLUDE:
|
|
||||||
{
|
|
||||||
if (cur == '<' || cur == '"' || is_last) {
|
|
||||||
ctx->kw_beg = istr_tell(in);
|
|
||||||
ctx->state = HL_STATE_PREPROCESSOR_INCLUDE_STRING;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
hl_write_char(ctx, cur);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case HL_STATE_PREPROCESSOR_INCLUDE_STRING:
|
|
||||||
{
|
|
||||||
if (cur == '>' || cur == '"' || is_last) {
|
|
||||||
strview_t keyword = hl_finish_keyword(ctx, ctx->kw_beg, in);
|
|
||||||
keyword.len += 1;
|
|
||||||
hl_print_keyword(ctx, keyword, HL_COLOR_STRING);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t hl_highlight(arena_t *arena, hl_ctx_t *ctx, strview_t data) {
|
|
||||||
ctx->ostr = ostr_init(arena);
|
|
||||||
|
|
||||||
ctx->state = HL_STATE_DEFAULT;
|
|
||||||
ctx->kw_beg = 0;
|
|
||||||
|
|
||||||
instream_t in = istr_init(data);
|
|
||||||
|
|
||||||
while (!istr_is_finished(&in)) {
|
|
||||||
hl_next_char(ctx, &in);
|
|
||||||
}
|
|
||||||
|
|
||||||
hl_next_char(ctx, &in);
|
|
||||||
|
|
||||||
return ostr_to_str(&ctx->ostr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void hl_set_symbol_in_table(hl_ctx_t *ctx, char symbol, bool value) {
|
|
||||||
if (!ctx) return;
|
|
||||||
ctx->symbol_table[(unsigned char)symbol] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void hl_add_keyword(arena_t *arena, hl_ctx_t *ctx, hl_keyword_t *keyword) {
|
|
||||||
hl_htable_add(arena, &ctx->kw_htable, keyword->keyword, keyword->color);
|
|
||||||
}
|
|
||||||
|
|
||||||
//// HASH TABLE ///////////////////////////////////////////////////
|
|
||||||
|
|
||||||
static hl_hashtable_t hl_htable_init(arena_t *arena, uint pow2_exp) {
|
|
||||||
uint count = 1 << pow2_exp;
|
|
||||||
return (hl_hashtable_t) {
|
|
||||||
.count = count,
|
|
||||||
.buckets = alloc(arena, hl_node_t*, count),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static hl_htable_result_e hl_htable_add(arena_t *arena, hl_hashtable_t *table, strview_t key, hl_color_e value) {
|
|
||||||
if (!table) {
|
|
||||||
return HL_HTABLE_FAILED;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((float)table->used >= table->count * 0.6f) {
|
|
||||||
warn("more than 60%% of the arena is being used: %d/%d", table->used, table->count);
|
|
||||||
}
|
|
||||||
|
|
||||||
u64 hash = hl_htable_hash(key.buf, key.len);
|
|
||||||
usize index = hash & (table->count - 1);
|
|
||||||
hl_node_t *bucket = table->buckets[index];
|
|
||||||
if (bucket) table->collisions++;
|
|
||||||
while (bucket) {
|
|
||||||
// already exists
|
|
||||||
if (strv_equals(bucket->key, key)) {
|
|
||||||
bucket->value = value;
|
|
||||||
return HL_HTABLE_REPLACED;
|
|
||||||
}
|
|
||||||
bucket = bucket->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
bucket = alloc(arena, hl_node_t);
|
|
||||||
|
|
||||||
bucket->key = key;
|
|
||||||
bucket->value = value;
|
|
||||||
bucket->next = table->buckets[index];
|
|
||||||
|
|
||||||
table->buckets[index] = bucket;
|
|
||||||
table->used++;
|
|
||||||
|
|
||||||
return HL_HTABLE_ADDED;
|
|
||||||
}
|
|
||||||
|
|
||||||
static hl_node_t *hl_htable_get(hl_hashtable_t *table, strview_t key) {
|
|
||||||
if (!table || table->count == 0) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
u64 hash = hl_htable_hash(key.buf, key.len);
|
|
||||||
usize index = hash & (table->count - 1);
|
|
||||||
hl_node_t *bucket = table->buckets[index];
|
|
||||||
while (bucket) {
|
|
||||||
if (strv_equals(bucket->key, key)) {
|
|
||||||
return bucket;
|
|
||||||
}
|
|
||||||
bucket = bucket->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// uses the sdbm algorithm
|
|
||||||
static u64 hl_htable_hash(const void *bytes, usize count) {
|
|
||||||
const u8 *data = bytes;
|
|
||||||
u64 hash = 0;
|
|
||||||
|
|
||||||
for (usize i = 0; i < count; ++i) {
|
|
||||||
hash = data[i] + (hash << 6) + (hash << 16) - hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
//// STATIC FUNCTIONS /////////////////////////////////////////////
|
|
||||||
|
|
||||||
static inline void hl_escape_html(outstream_t *out, char c) {
|
|
||||||
switch (c) {
|
|
||||||
case '&':
|
|
||||||
ostr_puts(out, strv("&"));
|
|
||||||
break;
|
|
||||||
case '<':
|
|
||||||
ostr_puts(out, strv("<"));
|
|
||||||
break;
|
|
||||||
case '>':
|
|
||||||
ostr_puts(out, strv(">"));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
ostr_putc(out, c);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void hl_write_char(hl_ctx_t *ctx, char c) {
|
|
||||||
if (ctx->flags & HL_FLAG_HTML) {
|
|
||||||
hl_escape_html(&ctx->ostr, c);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ostr_putc(&ctx->ostr, c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void hl_write(hl_ctx_t *ctx, strview_t v) {
|
|
||||||
if (ctx->flags & HL_FLAG_HTML) {
|
|
||||||
for (usize i = 0; i < v.len; ++i) {
|
|
||||||
hl_escape_html(&ctx->ostr, v.buf[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ostr_puts(&ctx->ostr, v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool hl_is_char_keyword(char c) {
|
|
||||||
return char_is_alpha(c) || char_is_num(c) || c == '_';
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool hl_highlight_symbol(hl_ctx_t *ctx, char c) {
|
|
||||||
if (!ctx->symbol_table[(unsigned char)c]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ostr_puts(&ctx->ostr, ctx->colors[HL_COLOR_SYMBOL]);
|
|
||||||
hl_write_char(ctx, c);
|
|
||||||
ostr_puts(&ctx->ostr, ctx->colors[HL_COLOR_NORMAL]);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static hl_color_e hl_get_keyword_color(hl_ctx_t *ctx, strview_t keyword) {
|
|
||||||
// todo: make this an option?
|
|
||||||
if (strv_ends_with_view(keyword, strv("_t"))) {
|
|
||||||
return HL_COLOR_CUSTOM_TYPES;
|
|
||||||
}
|
|
||||||
|
|
||||||
hl_node_t *node = hl_htable_get(&ctx->kw_htable, keyword);
|
|
||||||
return node ? node->value : HL_COLOR__COUNT;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool hl_is_capitalised(strview_t string) {
|
|
||||||
for (usize i = 0; i < string.len; ++i) {
|
|
||||||
char c = string.buf[i];
|
|
||||||
if (!char_is_num(c) && c != '_' && (c < 'A' || c > 'Z')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static strview_t hl_finish_keyword(hl_ctx_t *ctx, usize beg, instream_t *in) {
|
|
||||||
ctx->state = HL_STATE_DEFAULT;
|
|
||||||
beg -= 1;
|
|
||||||
usize end = istr_tell(in) - 1;
|
|
||||||
|
|
||||||
return strv(in->beg + beg, end - beg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void hl_print_keyword(hl_ctx_t *ctx, strview_t keyword, hl_color_e color) {
|
|
||||||
ostr_puts(&ctx->ostr, ctx->colors[color]);
|
|
||||||
hl_write(ctx, keyword);
|
|
||||||
ostr_puts(&ctx->ostr, ctx->colors[HL_COLOR_NORMAL]);
|
|
||||||
}
|
|
||||||
|
|
||||||
49
highlight.h
49
highlight.h
|
|
@ -1,49 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "str.h"
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
HL_COLOR_NORMAL,
|
|
||||||
HL_COLOR_PREPROC,
|
|
||||||
HL_COLOR_TYPES,
|
|
||||||
HL_COLOR_CUSTOM_TYPES,
|
|
||||||
HL_COLOR_KEYWORDS,
|
|
||||||
HL_COLOR_NUMBER,
|
|
||||||
HL_COLOR_STRING,
|
|
||||||
HL_COLOR_COMMENT,
|
|
||||||
HL_COLOR_FUNC,
|
|
||||||
HL_COLOR_SYMBOL,
|
|
||||||
HL_COLOR_MACRO,
|
|
||||||
|
|
||||||
HL_COLOR__COUNT,
|
|
||||||
} hl_color_e;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
HL_FLAG_NONE = 0,
|
|
||||||
HL_FLAG_HTML = 1 << 0,
|
|
||||||
} hl_flags_e;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
strview_t keyword;
|
|
||||||
hl_color_e color;
|
|
||||||
} hl_keyword_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
usize idx;
|
|
||||||
usize size;
|
|
||||||
} hl_line_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
strview_t colors[HL_COLOR__COUNT];
|
|
||||||
hl_keyword_t *extra_kwrds;
|
|
||||||
int kwrds_count;
|
|
||||||
hl_flags_e flags;
|
|
||||||
} hl_config_t;
|
|
||||||
|
|
||||||
typedef struct hl_ctx_t hl_ctx_t;
|
|
||||||
|
|
||||||
hl_ctx_t *hl_init(arena_t *arena, hl_config_t *config);
|
|
||||||
str_t hl_highlight(arena_t *arena, hl_ctx_t *ctx, strview_t str);
|
|
||||||
|
|
||||||
void hl_set_symbol_in_table(hl_ctx_t *ctx, char symbol, bool value);
|
|
||||||
void hl_add_keyword(arena_t *arena, hl_ctx_t *ctx, hl_keyword_t *keyword);
|
|
||||||
649
net.c
649
net.c
|
|
@ -1,649 +0,0 @@
|
||||||
#include "net.h"
|
|
||||||
#include "arena.h"
|
|
||||||
|
|
||||||
#include <stdio.h> // sscanf
|
|
||||||
|
|
||||||
#if COLLA_WIN
|
|
||||||
#include "win/net_win32.c"
|
|
||||||
#else
|
|
||||||
#error "platform not supported"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const char *http_get_method_string(http_method_e method) {
|
|
||||||
switch (method) {
|
|
||||||
case HTTP_GET: return "GET";
|
|
||||||
case HTTP_POST: return "POST";
|
|
||||||
case HTTP_HEAD: return "HEAD";
|
|
||||||
case HTTP_PUT: return "PUT";
|
|
||||||
case HTTP_DELETE: return "DELETE";
|
|
||||||
}
|
|
||||||
return "GET";
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *http_get_status_string(int status) {
|
|
||||||
switch (status) {
|
|
||||||
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_header_t *http__parse_headers_instream(arena_t *arena, instream_t *in) {
|
|
||||||
http_header_t *head = NULL;
|
|
||||||
|
|
||||||
while (!istr_is_finished(in)) {
|
|
||||||
strview_t line = istr_get_line(in);
|
|
||||||
|
|
||||||
// end of headers
|
|
||||||
if (strv_is_empty(line)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize pos = strv_find(line, ':', 0);
|
|
||||||
if (pos != STR_NONE) {
|
|
||||||
http_header_t *new_head = alloc(arena, http_header_t);
|
|
||||||
|
|
||||||
new_head->key = strv_sub(line, 0, pos);
|
|
||||||
new_head->value = strv_sub(line, pos + 2, SIZE_MAX);
|
|
||||||
|
|
||||||
list_push(head, new_head);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return head;
|
|
||||||
}
|
|
||||||
|
|
||||||
http_header_t *http_parse_headers(arena_t *arena, strview_t header_string) {
|
|
||||||
instream_t in = istr_init(header_string);
|
|
||||||
return http__parse_headers_instream(arena, &in);
|
|
||||||
}
|
|
||||||
|
|
||||||
http_req_t http_parse_req(arena_t *arena, strview_t request) {
|
|
||||||
http_req_t req = {0};
|
|
||||||
instream_t in = istr_init(request);
|
|
||||||
|
|
||||||
strview_t method = strv_trim(istr_get_view(&in, '/'));
|
|
||||||
istr_skip(&in, 1); // skip /
|
|
||||||
req.url = strv_trim(istr_get_view(&in, ' '));
|
|
||||||
strview_t http = strv_trim(istr_get_view(&in, '\n'));
|
|
||||||
|
|
||||||
istr_skip(&in, 1); // skip \n
|
|
||||||
|
|
||||||
req.headers = http__parse_headers_instream(arena, &in);
|
|
||||||
|
|
||||||
req.body = strv_trim(istr_get_view_len(&in, SIZE_MAX));
|
|
||||||
|
|
||||||
strview_t methods[5] = { strv("GET"), strv("POST"), strv("HEAD"), strv("PUT"), strv("DELETE") };
|
|
||||||
usize methods_count = arrlen(methods);
|
|
||||||
|
|
||||||
for (usize i = 0; i < methods_count; ++i) {
|
|
||||||
if (strv_equals(method, methods[i])) {
|
|
||||||
req.method = (http_method_e)i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
in = istr_init(http);
|
|
||||||
istr_ignore_and_skip(&in, '/'); // skip HTTP/
|
|
||||||
istr_get_u8(&in, &req.version.major);
|
|
||||||
istr_skip(&in, 1); // skip .
|
|
||||||
istr_get_u8(&in, &req.version.minor);
|
|
||||||
|
|
||||||
return req;
|
|
||||||
}
|
|
||||||
|
|
||||||
http_res_t http_parse_res(arena_t *arena, strview_t response) {
|
|
||||||
http_res_t res = {0};
|
|
||||||
instream_t in = istr_init(response);
|
|
||||||
|
|
||||||
strview_t http = istr_get_view_len(&in, 4);
|
|
||||||
if (!strv_equals(http, strv("HTTP"))) {
|
|
||||||
err("response doesn't start with 'HTTP', instead with %v", http);
|
|
||||||
return (http_res_t){0};
|
|
||||||
}
|
|
||||||
istr_skip(&in, 1); // skip /
|
|
||||||
istr_get_u8(&in, &res.version.major);
|
|
||||||
istr_skip(&in, 1); // skip .
|
|
||||||
istr_get_u8(&in, &res.version.minor);
|
|
||||||
istr_get_i32(&in, (i32*)&res.status_code);
|
|
||||||
|
|
||||||
istr_ignore(&in, '\n');
|
|
||||||
istr_skip(&in, 1); // skip \n
|
|
||||||
|
|
||||||
res.headers = http__parse_headers_instream(arena, &in);
|
|
||||||
|
|
||||||
strview_t encoding = http_get_header(res.headers, strv("transfer-encoding"));
|
|
||||||
if (!strv_equals(encoding, strv("chunked"))) {
|
|
||||||
res.body = istr_get_view_len(&in, SIZE_MAX);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
err("chunked encoding not implemented yet! body ignored");
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t http_req_to_str(arena_t *arena, http_req_t *req) {
|
|
||||||
outstream_t out = ostr_init(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_EMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
ostr_print(
|
|
||||||
&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) {
|
|
||||||
ostr_print(&out, "%v: %v\r\n", h->key, h->value);
|
|
||||||
h = h->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
ostr_puts(&out, strv("\r\n"));
|
|
||||||
ostr_puts(&out, req->body);
|
|
||||||
|
|
||||||
return ostr_to_str(&out);
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t http_res_to_str(arena_t *arena, http_res_t *res) {
|
|
||||||
outstream_t out = ostr_init(arena);
|
|
||||||
|
|
||||||
ostr_print(
|
|
||||||
&out,
|
|
||||||
"HTTP/%hhu.%hhu %d %s\r\n",
|
|
||||||
res->version.major,
|
|
||||||
res->version.minor,
|
|
||||||
res->status_code,
|
|
||||||
http_get_status_string(res->status_code)
|
|
||||||
);
|
|
||||||
ostr_puts(&out, strv("\r\n"));
|
|
||||||
ostr_puts(&out, res->body);
|
|
||||||
|
|
||||||
return ostr_to_str(&out);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool http_has_header(http_header_t *headers, strview_t key) {
|
|
||||||
for_each(h, headers) {
|
|
||||||
if (strv_equals(h->key, key)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void http_set_header(http_header_t *headers, strview_t key, strview_t value) {
|
|
||||||
http_header_t *h = headers;
|
|
||||||
while (h) {
|
|
||||||
if (strv_equals(h->key, key)) {
|
|
||||||
h->value = value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
h = h->next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
strview_t http_get_header(http_header_t *headers, strview_t key) {
|
|
||||||
http_header_t *h = headers;
|
|
||||||
while (h) {
|
|
||||||
if (strv_equals(h->key, key)) {
|
|
||||||
return h->value;
|
|
||||||
}
|
|
||||||
h = h->next;
|
|
||||||
}
|
|
||||||
return STRV_EMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t http_make_url_safe(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 (strv_contains(chars, string.buf[i])) {
|
|
||||||
final_len += 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (strv_contains(chars, string.buf[i])) {
|
|
||||||
fmt_buffer(out.buf + cur, 4, "%%%X", string.buf[i]);
|
|
||||||
cur += 3;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
out.buf[cur++] = string.buf[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t http_decode_url_safe(arena_t *arena, strview_t string) {
|
|
||||||
usize final_len = string.len;
|
|
||||||
|
|
||||||
for (usize i = 0; i < string.len; ++i) {
|
|
||||||
if (string.buf[i] == '%') {
|
|
||||||
final_len -= 2;
|
|
||||||
i += 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
colla_assert(final_len <= string.len);
|
|
||||||
|
|
||||||
str_t out = {
|
|
||||||
.buf = alloc(arena, char, final_len + 1),
|
|
||||||
.len = final_len
|
|
||||||
};
|
|
||||||
|
|
||||||
usize k = 0;
|
|
||||||
|
|
||||||
for (usize i = 0; i < string.len; ++i) {
|
|
||||||
if (string.buf[i] == '%') {
|
|
||||||
// skip %
|
|
||||||
++i;
|
|
||||||
|
|
||||||
unsigned int ch = 0;
|
|
||||||
int result = sscanf(string.buf + i, "%02X", &ch);
|
|
||||||
if (result != 1 || ch > UINT8_MAX) {
|
|
||||||
err("malformed url at %zu (%s)", i, string.buf + i);
|
|
||||||
return STR_EMPTY;
|
|
||||||
}
|
|
||||||
out.buf[k++] = (char)ch;
|
|
||||||
|
|
||||||
// skip first char of hex
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
out.buf[k++] = string.buf[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
http_url_t http_split_url(strview_t url) {
|
|
||||||
http_url_t out = {0};
|
|
||||||
|
|
||||||
if (strv_starts_with_view(url, strv("https://"))) {
|
|
||||||
url = strv_remove_prefix(url, 8);
|
|
||||||
}
|
|
||||||
else if (strv_starts_with_view(url, strv("http://"))) {
|
|
||||||
url = strv_remove_prefix(url, 7);
|
|
||||||
}
|
|
||||||
|
|
||||||
out.host = strv_sub(url, 0, strv_find(url, '/', 0));
|
|
||||||
out.uri = strv_sub(url, out.host.len, SIZE_MAX);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// WEBSOCKETS ///////////////////////
|
|
||||||
|
|
||||||
#define WEBSOCKET_MAGIC "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
|
||||||
#define WEBSOCKET_HTTP_KEY "Sec-WebSocket-Key"
|
|
||||||
|
|
||||||
bool websocket_init(arena_t scratch, socket_t socket, strview_t key) {
|
|
||||||
str_t full_key = str_fmt(&scratch, "%v" WEBSOCKET_MAGIC, key);
|
|
||||||
|
|
||||||
sha1_t sha1_ctx = sha1_init();
|
|
||||||
sha1_result_t sha1_data = sha1(&sha1_ctx, full_key.buf, full_key.len);
|
|
||||||
|
|
||||||
// convert to big endian for network communication
|
|
||||||
for (int i = 0; i < 5; ++i) {
|
|
||||||
sha1_data.digest[i] = htonl(sha1_data.digest[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer_t encoded_key = base64_encode(&scratch, (buffer_t){ (u8 *)sha1_data.digest, sizeof(sha1_data.digest) });
|
|
||||||
|
|
||||||
str_t response = str_fmt(
|
|
||||||
&scratch,
|
|
||||||
"HTTP/1.1 101 Switching Protocols\r\n"
|
|
||||||
"Connection: Upgrade\r\n"
|
|
||||||
"Upgrade: websocket\r\n"
|
|
||||||
"Sec-WebSocket-Accept: %v\r\n"
|
|
||||||
"\r\n",
|
|
||||||
encoded_key
|
|
||||||
);
|
|
||||||
|
|
||||||
int result = sk_send(socket, response.buf, (int)response.len);
|
|
||||||
return result != SOCKET_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer_t websocket_encode(arena_t *arena, strview_t message) {
|
|
||||||
int extra = 6;
|
|
||||||
if (message.len > UINT16_MAX) extra += sizeof(u64);
|
|
||||||
else if (message.len > UINT8_MAX) extra += sizeof(u16);
|
|
||||||
u8 *bytes = alloc(arena, u8, message.len + extra);
|
|
||||||
bytes[0] = 0b10000001;
|
|
||||||
bytes[1] = 0b10000000;
|
|
||||||
int offset = 2;
|
|
||||||
if (message.len > UINT16_MAX) {
|
|
||||||
bytes[1] |= 0b01111111;
|
|
||||||
u64 len = htonll(message.len);
|
|
||||||
memmove(bytes + 2, &len, sizeof(len));
|
|
||||||
offset += sizeof(u64);
|
|
||||||
}
|
|
||||||
else if (message.len > UINT8_MAX) {
|
|
||||||
bytes[1] |= 0b01111110;
|
|
||||||
u16 len = htons((u16)message.len);
|
|
||||||
memmove(bytes + 2, &len, sizeof(len));
|
|
||||||
offset += sizeof(u16);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
bytes[1] |= (u8)message.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 mask = 0;
|
|
||||||
memmove(bytes + offset, &mask, sizeof(mask));
|
|
||||||
offset += sizeof(mask);
|
|
||||||
memmove(bytes + offset, message.buf, message.len);
|
|
||||||
|
|
||||||
return (buffer_t){ bytes, message.len + extra };
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t websocket_decode(arena_t *arena, buffer_t message) {
|
|
||||||
str_t out = STR_EMPTY;
|
|
||||||
u8 *bytes = message.data;
|
|
||||||
|
|
||||||
bool mask = bytes[1] & 0b10000000;
|
|
||||||
int offset = 2;
|
|
||||||
u64 msglen = bytes[1] & 0b01111111;
|
|
||||||
|
|
||||||
// 16bit msg len
|
|
||||||
if (msglen == 126) {
|
|
||||||
u16 be_len = 0;
|
|
||||||
memmove(&be_len, bytes + 2, sizeof(be_len));
|
|
||||||
msglen = ntohs(be_len);
|
|
||||||
offset += sizeof(u16);
|
|
||||||
}
|
|
||||||
// 64bit msg len
|
|
||||||
else if (msglen == 127) {
|
|
||||||
u64 be_len = 0;
|
|
||||||
memmove(&be_len, bytes + 2, sizeof(be_len));
|
|
||||||
msglen = ntohll(be_len);
|
|
||||||
offset += sizeof(u64);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msglen == 0) {
|
|
||||||
warn("message length = 0");
|
|
||||||
}
|
|
||||||
else if (mask) {
|
|
||||||
u8 *decoded = alloc(arena, u8, msglen + 1);
|
|
||||||
u8 masks[4] = {0};
|
|
||||||
memmove(masks, bytes + offset, sizeof(masks));
|
|
||||||
offset += 4;
|
|
||||||
|
|
||||||
for (u64 i = 0; i < msglen; ++i) {
|
|
||||||
decoded[i] = bytes[offset + i] ^ masks[i % 4];
|
|
||||||
}
|
|
||||||
|
|
||||||
out = (str_t){ (char *)decoded, msglen };
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
warn("mask bit not set!");
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// SHA 1 ////////////////////////////
|
|
||||||
|
|
||||||
sha1_t sha1_init(void) {
|
|
||||||
return (sha1_t) {
|
|
||||||
.digest = {
|
|
||||||
0x67452301,
|
|
||||||
0xEFCDAB89,
|
|
||||||
0x98BADCFE,
|
|
||||||
0x10325476,
|
|
||||||
0xC3D2E1F0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 sha1__left_rotate(u32 value, u32 count) {
|
|
||||||
return (value << count) ^ (value >> (32 - count));
|
|
||||||
}
|
|
||||||
|
|
||||||
void sha1__process_block(sha1_t *ctx) {
|
|
||||||
u32 w [80];
|
|
||||||
for (usize i = 0; i < 16; ++i) {
|
|
||||||
w[i] = ctx->block[i * 4 + 0] << 24;
|
|
||||||
w[i] |= ctx->block[i * 4 + 1] << 16;
|
|
||||||
w[i] |= ctx->block[i * 4 + 2] << 8;
|
|
||||||
w[i] |= ctx->block[i * 4 + 3] << 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (usize i = 16; i < 80; ++i) {
|
|
||||||
w[i] = sha1__left_rotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 a = ctx->digest[0];
|
|
||||||
u32 b = ctx->digest[1];
|
|
||||||
u32 c = ctx->digest[2];
|
|
||||||
u32 d = ctx->digest[3];
|
|
||||||
u32 e = ctx->digest[4];
|
|
||||||
|
|
||||||
for (usize i = 0; i < 80; ++i) {
|
|
||||||
u32 f = 0;
|
|
||||||
u32 k = 0;
|
|
||||||
|
|
||||||
if (i<20) {
|
|
||||||
f = (b & c) | (~b & d);
|
|
||||||
k = 0x5A827999;
|
|
||||||
} else if (i<40) {
|
|
||||||
f = b ^ c ^ d;
|
|
||||||
k = 0x6ED9EBA1;
|
|
||||||
} else if (i<60) {
|
|
||||||
f = (b & c) | (b & d) | (c & d);
|
|
||||||
k = 0x8F1BBCDC;
|
|
||||||
} else {
|
|
||||||
f = b ^ c ^ d;
|
|
||||||
k = 0xCA62C1D6;
|
|
||||||
}
|
|
||||||
u32 temp = sha1__left_rotate(a, 5) + f + e + k + w[i];
|
|
||||||
e = d;
|
|
||||||
d = c;
|
|
||||||
c = sha1__left_rotate(b, 30);
|
|
||||||
b = a;
|
|
||||||
a = temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx->digest[0] += a;
|
|
||||||
ctx->digest[1] += b;
|
|
||||||
ctx->digest[2] += c;
|
|
||||||
ctx->digest[3] += d;
|
|
||||||
ctx->digest[4] += e;
|
|
||||||
}
|
|
||||||
|
|
||||||
void sha1__process_byte(sha1_t *ctx, u8 b) {
|
|
||||||
ctx->block[ctx->block_index++] = b;
|
|
||||||
++ctx->byte_count;
|
|
||||||
if (ctx->block_index == 64) {
|
|
||||||
ctx->block_index = 0;
|
|
||||||
sha1__process_block(ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sha1_result_t sha1(sha1_t *ctx, const void *buf, usize len) {
|
|
||||||
const u8 *block = buf;
|
|
||||||
|
|
||||||
for (usize i = 0; i < len; ++i) {
|
|
||||||
sha1__process_byte(ctx, block[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
usize bitcount = ctx->byte_count * 8;
|
|
||||||
sha1__process_byte(ctx, 0x80);
|
|
||||||
|
|
||||||
if (ctx->block_index > 56) {
|
|
||||||
while (ctx->block_index != 0) {
|
|
||||||
sha1__process_byte(ctx, 0);
|
|
||||||
}
|
|
||||||
while (ctx->block_index < 56) {
|
|
||||||
sha1__process_byte(ctx, 0);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
while (ctx->block_index < 56) {
|
|
||||||
sha1__process_byte(ctx, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sha1__process_byte(ctx, 0);
|
|
||||||
sha1__process_byte(ctx, 0);
|
|
||||||
sha1__process_byte(ctx, 0);
|
|
||||||
sha1__process_byte(ctx, 0);
|
|
||||||
sha1__process_byte(ctx, (uchar)((bitcount >> 24) & 0xFF));
|
|
||||||
sha1__process_byte(ctx, (uchar)((bitcount >> 16) & 0xFF));
|
|
||||||
sha1__process_byte(ctx, (uchar)((bitcount >> 8 ) & 0xFF));
|
|
||||||
sha1__process_byte(ctx, (uchar)((bitcount >> 0 ) & 0xFF));
|
|
||||||
|
|
||||||
sha1_result_t result = {0};
|
|
||||||
memcpy(result.digest, ctx->digest, sizeof(result.digest));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t sha1_str(arena_t *arena, sha1_t *ctx, const void *buf, usize len) {
|
|
||||||
sha1_result_t result = sha1(ctx, buf, len);
|
|
||||||
return str_fmt(arena, "%08x%08x%08x%08x%08x", result.digest[0], result.digest[1], result.digest[2], result.digest[3], result.digest[4]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// BASE 64 //////////////////////////
|
|
||||||
|
|
||||||
unsigned char b64__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', '+', '/'
|
|
||||||
};
|
|
||||||
|
|
||||||
u8 b64__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 base64_encode(arena_t *arena, buffer_t buffer) {
|
|
||||||
usize outlen = ((buffer.len + 2) / 3) * 4;
|
|
||||||
u8 *out = alloc(arena, u8, outlen);
|
|
||||||
|
|
||||||
for (usize i = 0, j = 0; i < buffer.len;) {
|
|
||||||
u32 a = i < buffer.len ? buffer.data[i++] : 0;
|
|
||||||
u32 b = i < buffer.len ? buffer.data[i++] : 0;
|
|
||||||
u32 c = i < buffer.len ? buffer.data[i++] : 0;
|
|
||||||
|
|
||||||
u32 triple = (a << 16) | (b << 8) | c;
|
|
||||||
|
|
||||||
out[j++] = b64__encoding_table[(triple >> 18) & 0x3F];
|
|
||||||
out[j++] = b64__encoding_table[(triple >> 12) & 0x3F];
|
|
||||||
out[j++] = b64__encoding_table[(triple >> 6) & 0x3F];
|
|
||||||
out[j++] = b64__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 base64_decode(arena_t *arena, buffer_t buffer) {
|
|
||||||
u8 *out = arena->cur;
|
|
||||||
usize start = arena_tell(arena);
|
|
||||||
|
|
||||||
for (usize i = 0; i < buffer.len; i += 4) {
|
|
||||||
u8 a = b64__decoding_table[buffer.data[i + 0]];
|
|
||||||
u8 b = b64__decoding_table[buffer.data[i + 1]];
|
|
||||||
u8 c = b64__decoding_table[buffer.data[i + 2]];
|
|
||||||
u8 d = b64__decoding_table[buffer.data[i + 3]];
|
|
||||||
|
|
||||||
u32 triple =
|
|
||||||
((u32)a << 18) |
|
|
||||||
((u32)b << 12) |
|
|
||||||
((u32)c << 6) |
|
|
||||||
((u32)d);
|
|
||||||
|
|
||||||
u8 *bytes = alloc(arena, u8, 3);
|
|
||||||
|
|
||||||
bytes[0] = (triple >> 16) & 0xFF;
|
|
||||||
bytes[1] = (triple >> 8) & 0xFF;
|
|
||||||
bytes[2] = (triple >> 0) & 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize spaces_count = 0;
|
|
||||||
for (isize i = buffer.len - 1; i >= 0; --i) {
|
|
||||||
if (buffer.data[i] == '=') {
|
|
||||||
spaces_count++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
usize outlen = arena_tell(arena) - start;
|
|
||||||
|
|
||||||
return (buffer_t){
|
|
||||||
.data = out,
|
|
||||||
.len = outlen - spaces_count,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
194
net.h
194
net.h
|
|
@ -1,194 +0,0 @@
|
||||||
#ifndef COLLA_NET_HEADER
|
|
||||||
#define COLLA_NET_HEADER
|
|
||||||
|
|
||||||
#include "core.h"
|
|
||||||
#include "str.h"
|
|
||||||
#include "os.h"
|
|
||||||
|
|
||||||
void net_init(void);
|
|
||||||
void net_cleanup(void);
|
|
||||||
iptr net_get_last_error(void);
|
|
||||||
|
|
||||||
typedef enum http_method_e {
|
|
||||||
HTTP_GET,
|
|
||||||
HTTP_POST,
|
|
||||||
HTTP_HEAD,
|
|
||||||
HTTP_PUT,
|
|
||||||
HTTP_DELETE,
|
|
||||||
HTTP_METHOD__COUNT,
|
|
||||||
} http_method_e;
|
|
||||||
|
|
||||||
const char *http_get_method_string(http_method_e method);
|
|
||||||
const char *http_get_status_string(int status);
|
|
||||||
|
|
||||||
typedef struct http_version_t http_version_t;
|
|
||||||
struct http_version_t {
|
|
||||||
u8 major;
|
|
||||||
u8 minor;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct http_header_t http_header_t;
|
|
||||||
struct http_header_t {
|
|
||||||
strview_t key;
|
|
||||||
strview_t value;
|
|
||||||
http_header_t *next;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct http_req_t http_req_t;
|
|
||||||
struct http_req_t {
|
|
||||||
http_method_e method;
|
|
||||||
http_version_t version;
|
|
||||||
http_header_t *headers;
|
|
||||||
strview_t url;
|
|
||||||
strview_t body;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct http_res_t http_res_t;
|
|
||||||
struct http_res_t {
|
|
||||||
int status_code;
|
|
||||||
http_version_t version;
|
|
||||||
http_header_t *headers;
|
|
||||||
strview_t body;
|
|
||||||
};
|
|
||||||
|
|
||||||
http_header_t *http_parse_headers(arena_t *arena, strview_t header_string);
|
|
||||||
|
|
||||||
http_req_t http_parse_req(arena_t *arena, strview_t request);
|
|
||||||
http_res_t http_parse_res(arena_t *arena, strview_t response);
|
|
||||||
|
|
||||||
str_t http_req_to_str(arena_t *arena, http_req_t *req);
|
|
||||||
str_t http_res_to_str(arena_t *arena, http_res_t *res);
|
|
||||||
|
|
||||||
bool http_has_header(http_header_t *headers, strview_t key);
|
|
||||||
void http_set_header(http_header_t *headers, strview_t key, strview_t value);
|
|
||||||
strview_t http_get_header(http_header_t *headers, strview_t key);
|
|
||||||
|
|
||||||
str_t http_make_url_safe(arena_t *arena, strview_t string);
|
|
||||||
str_t http_decode_url_safe(arena_t *arena, strview_t string);
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
strview_t host;
|
|
||||||
strview_t uri;
|
|
||||||
} http_url_t;
|
|
||||||
|
|
||||||
http_url_t http_split_url(strview_t url);
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
arena_t *arena;
|
|
||||||
strview_t url;
|
|
||||||
http_version_t version; // 1.1 by default
|
|
||||||
http_method_e request_type;
|
|
||||||
http_header_t *headers;
|
|
||||||
int header_count; // optional, if set to 0 it traverses headers using h->next
|
|
||||||
strview_t body;
|
|
||||||
} http_request_desc_t;
|
|
||||||
|
|
||||||
typedef void (*http_request_callback_fn)(strview_t chunk, void *udata);
|
|
||||||
|
|
||||||
// arena_t *arena, strview_t url, [ http_header_t *headers, int header_count, strview_t body ]
|
|
||||||
#define http_get(arena, url, ...) http_request(&(http_request_desc_t){ arena, url, .request_type = HTTP_GET, .version = { 1, 1 }, __VA_ARGS__ })
|
|
||||||
|
|
||||||
http_res_t http_request(http_request_desc_t *request);
|
|
||||||
http_res_t http_request_cb(http_request_desc_t *request, http_request_callback_fn callback, void *userdata);
|
|
||||||
|
|
||||||
// SOCKETS //////////////////////////
|
|
||||||
|
|
||||||
typedef uintptr_t socket_t;
|
|
||||||
typedef struct skpoll_t skpoll_t;
|
|
||||||
|
|
||||||
#define SK_ADDR_LOOPBACK "127.0.0.1"
|
|
||||||
#define SK_ADDR_ANY "0.0.0.0"
|
|
||||||
#define SK_ADDR_BROADCAST "255.255.255.255"
|
|
||||||
|
|
||||||
struct skpoll_t {
|
|
||||||
uintptr_t socket;
|
|
||||||
short events;
|
|
||||||
short revents;
|
|
||||||
};
|
|
||||||
|
|
||||||
#define SOCKET_ERROR (-1)
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
SOCK_TCP,
|
|
||||||
SOCK_UDP,
|
|
||||||
} sktype_e;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
SOCK_EVENT_NONE,
|
|
||||||
SOCK_EVENT_READ = 1 << 0,
|
|
||||||
SOCK_EVENT_WRITE = 1 << 1,
|
|
||||||
SOCK_EVENT_ACCEPT = 1 << 2,
|
|
||||||
SOCK_EVENT_CONNECT = 1 << 3,
|
|
||||||
SOCK_EVENT_CLOSE = 1 << 4,
|
|
||||||
} skevent_e;
|
|
||||||
|
|
||||||
// Opens a socket
|
|
||||||
socket_t sk_open(sktype_e type);
|
|
||||||
// Opens a socket using 'protocol', options are
|
|
||||||
// ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp
|
|
||||||
socket_t sk_open_protocol(const char *protocol);
|
|
||||||
|
|
||||||
// Checks that a opened socket is valid, returns true on success
|
|
||||||
bool sk_is_valid(socket_t sock);
|
|
||||||
|
|
||||||
// Closes a socket, returns true on success
|
|
||||||
bool sk_close(socket_t sock);
|
|
||||||
|
|
||||||
// Fill out a sk_addrin_t structure with "ip" and "port"
|
|
||||||
// skaddrin_t sk_addrin_init(const char *ip, uint16_t port);
|
|
||||||
|
|
||||||
// Associate a local address with a socket
|
|
||||||
bool sk_bind(socket_t sock, const char *ip, u16 port);
|
|
||||||
|
|
||||||
// Place a socket in a state in which it is listening for an incoming connection
|
|
||||||
bool sk_listen(socket_t sock, int backlog);
|
|
||||||
|
|
||||||
// Permits an incoming connection attempt on a socket
|
|
||||||
socket_t sk_accept(socket_t sock);
|
|
||||||
|
|
||||||
// Connects to a server (e.g. "127.0.0.1" or "google.com") with a port(e.g. 1234), returns true on success
|
|
||||||
bool sk_connect(socket_t sock, const char *server, u16 server_port);
|
|
||||||
|
|
||||||
// Sends data on a socket, returns true on success
|
|
||||||
int sk_send(socket_t sock, const void *buf, int len);
|
|
||||||
// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error
|
|
||||||
int sk_recv(socket_t sock, void *buf, int len);
|
|
||||||
|
|
||||||
// Wait for an event on some sockets
|
|
||||||
int sk_poll(skpoll_t *to_poll, int num_to_poll, int timeout);
|
|
||||||
|
|
||||||
oshandle_t sk_bind_event(socket_t sock, skevent_e event);
|
|
||||||
void sk_destroy_event(oshandle_t handle);
|
|
||||||
void sk_reset_event(oshandle_t handle);
|
|
||||||
|
|
||||||
// WEBSOCKETS ///////////////////////
|
|
||||||
|
|
||||||
bool websocket_init(arena_t scratch, socket_t socket, strview_t key);
|
|
||||||
buffer_t websocket_encode(arena_t *arena, strview_t message);
|
|
||||||
str_t websocket_decode(arena_t *arena, buffer_t message);
|
|
||||||
|
|
||||||
// SHA 1 ////////////////////////////
|
|
||||||
|
|
||||||
typedef struct sha1_t sha1_t;
|
|
||||||
struct sha1_t {
|
|
||||||
u32 digest[5];
|
|
||||||
u8 block[64];
|
|
||||||
usize block_index;
|
|
||||||
usize byte_count;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct sha1_result_t sha1_result_t;
|
|
||||||
struct sha1_result_t {
|
|
||||||
u32 digest[5];
|
|
||||||
};
|
|
||||||
|
|
||||||
sha1_t sha1_init(void);
|
|
||||||
sha1_result_t sha1(sha1_t *ctx, const void *buf, usize len);
|
|
||||||
str_t sha1_str(arena_t *arena, sha1_t *ctx, const void *buf, usize len);
|
|
||||||
|
|
||||||
// BASE 64 //////////////////////////
|
|
||||||
|
|
||||||
buffer_t base64_encode(arena_t *arena, buffer_t buffer);
|
|
||||||
buffer_t base64_decode(arena_t *arena, buffer_t buffer);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
235
os.c
235
os.c
|
|
@ -1,235 +0,0 @@
|
||||||
#include "os.h"
|
|
||||||
|
|
||||||
#if COLLA_WIN
|
|
||||||
#include "win/os_win32.c"
|
|
||||||
#else
|
|
||||||
#error "platform not supported yet"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// == HANDLE ====================================
|
|
||||||
|
|
||||||
oshandle_t os_handle_zero(void) {
|
|
||||||
return (oshandle_t){0};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool os_handle_match(oshandle_t a, oshandle_t b) {
|
|
||||||
return a.data == b.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool os_handle_valid(oshandle_t handle) {
|
|
||||||
return !os_handle_match(handle, os_handle_zero());
|
|
||||||
}
|
|
||||||
|
|
||||||
// == LOGGING ===================================
|
|
||||||
|
|
||||||
os_log_colour_e log__level_to_colour(os_log_level_e level) {
|
|
||||||
os_log_colour_e colour = LOG_COL_RESET;
|
|
||||||
switch (level) {
|
|
||||||
case LOG_DEBUG: colour = LOG_COL_BLUE; break;
|
|
||||||
case LOG_INFO: colour = LOG_COL_GREEN; break;
|
|
||||||
case LOG_WARN: colour = LOG_COL_YELLOW; break;
|
|
||||||
case LOG_ERR: colour = LOG_COL_MAGENTA; break;
|
|
||||||
case LOG_FATAL: colour = LOG_COL_RED; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
return colour;
|
|
||||||
}
|
|
||||||
|
|
||||||
void os_log_print(os_log_level_e level, const char *fmt, ...) {
|
|
||||||
va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
os_log_printv(level, fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
void os_log_printv(os_log_level_e level, const char *fmt, va_list args) {
|
|
||||||
const char *level_str = "";
|
|
||||||
switch (level) {
|
|
||||||
case LOG_DEBUG: level_str = "DEBUG"; break;
|
|
||||||
case LOG_INFO: level_str = "INFO"; break;
|
|
||||||
case LOG_WARN: level_str = "WARN"; break;
|
|
||||||
case LOG_ERR: level_str = "ERR"; break;
|
|
||||||
case LOG_FATAL: level_str = "FATAL"; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
os_log_set_colour(log__level_to_colour(level));
|
|
||||||
if (level != LOG_BASIC) {
|
|
||||||
fmt_print("[%s]: ", level_str);
|
|
||||||
}
|
|
||||||
os_log_set_colour(LOG_COL_RESET);
|
|
||||||
|
|
||||||
fmt_printv(fmt, args);
|
|
||||||
fmt_print("\n");
|
|
||||||
|
|
||||||
if (level == LOG_FATAL) {
|
|
||||||
os_abort(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// == FILE ======================================
|
|
||||||
|
|
||||||
void os_file_split_path(strview_t path, strview_t *dir, strview_t *name, strview_t *ext) {
|
|
||||||
usize dir_lin = strv_rfind(path, '/', 0);
|
|
||||||
usize dir_win = strv_rfind(path, '\\', 0);
|
|
||||||
dir_lin = dir_lin != STR_NONE ? dir_lin : 0;
|
|
||||||
dir_win = dir_win != STR_NONE ? dir_win : 0;
|
|
||||||
usize dir_pos = MAX(dir_lin, dir_win);
|
|
||||||
|
|
||||||
usize ext_pos = strv_rfind(path, '.', 0);
|
|
||||||
|
|
||||||
if (dir) {
|
|
||||||
*dir = strv_sub(path, 0, dir_pos);
|
|
||||||
}
|
|
||||||
if (name) {
|
|
||||||
*name = strv_sub(path, dir_pos ? dir_pos + 1 : 0, ext_pos);
|
|
||||||
}
|
|
||||||
if (ext) {
|
|
||||||
*ext = strv_sub(path, ext_pos, SIZE_MAX);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool os_file_putc(oshandle_t handle, char c) {
|
|
||||||
return os_file_write(handle, &c, sizeof(c)) == sizeof(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool os_file_puts(oshandle_t handle, strview_t str) {
|
|
||||||
return os_file_write(handle, str.buf, str.len) == str.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool os_file_print(arena_t scratch, oshandle_t handle, const char *fmt, ...) {
|
|
||||||
va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
bool result = os_file_printv(scratch, handle, fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool os_file_printv(arena_t scratch, oshandle_t handle, const char *fmt, va_list args) {
|
|
||||||
str_t s = str_fmtv(&scratch, fmt, args);
|
|
||||||
return os_file_puts(handle, strv(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
usize os_file_read_buf(oshandle_t handle, buffer_t *buf) {
|
|
||||||
return os_file_read(handle, buf->data, buf->len);
|
|
||||||
}
|
|
||||||
|
|
||||||
usize os_file_write_buf(oshandle_t handle, buffer_t buf) {
|
|
||||||
return os_file_write(handle, buf.data, buf.len);
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer_t os_file_read_all(arena_t *arena, strview_t path) {
|
|
||||||
oshandle_t fp = os_file_open(path, FILEMODE_READ);
|
|
||||||
if (!os_handle_valid(fp)) {
|
|
||||||
err("could not open file: %v", path);
|
|
||||||
return (buffer_t){0};
|
|
||||||
}
|
|
||||||
buffer_t out = os_file_read_all_fp(arena, fp);
|
|
||||||
os_file_close(fp);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer_t os_file_read_all_fp(arena_t *arena, oshandle_t handle) {
|
|
||||||
if (!os_handle_valid(handle)) return (buffer_t){0};
|
|
||||||
buffer_t out = {0};
|
|
||||||
|
|
||||||
out.len = os_file_size(handle);
|
|
||||||
out.data = alloc(arena, u8, out.len);
|
|
||||||
usize read = os_file_read_buf(handle, &out);
|
|
||||||
|
|
||||||
if (read != out.len) {
|
|
||||||
err("os_file_read_all_fp: read failed, should be %zu but is %zu", out.len, read);
|
|
||||||
arena_pop(arena, out.len);
|
|
||||||
return (buffer_t){0};
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t os_file_read_all_str(arena_t *arena, strview_t path) {
|
|
||||||
oshandle_t fp = os_file_open(path, FILEMODE_READ);
|
|
||||||
if (!os_handle_valid(fp)) {
|
|
||||||
err("could not open file %v: %v", path, os_get_error_string(os_get_last_error()));
|
|
||||||
return STR_EMPTY;
|
|
||||||
}
|
|
||||||
str_t out = os_file_read_all_str_fp(arena, fp);
|
|
||||||
os_file_close(fp);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t os_file_read_all_str_fp(arena_t *arena, oshandle_t handle) {
|
|
||||||
if (!os_handle_valid(handle)) {
|
|
||||||
return STR_EMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t out = STR_EMPTY;
|
|
||||||
|
|
||||||
out.len = os_file_size(handle);
|
|
||||||
out.buf = alloc(arena, u8, out.len + 1);
|
|
||||||
|
|
||||||
usize read = os_file_read(handle, out.buf, out.len);
|
|
||||||
if (read != out.len) {
|
|
||||||
err("os_file_read_all_str_fp: read failed, should be %zu but is %zu", out.len, read);
|
|
||||||
arena_pop(arena, out.len + 1);
|
|
||||||
return STR_EMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool os_file_write_all(strview_t name, buffer_t buffer) {
|
|
||||||
oshandle_t fp = os_file_open(name, FILEMODE_WRITE);
|
|
||||||
bool result = os_file_write_all_fp(fp, buffer);
|
|
||||||
os_file_close(fp);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool os_file_write_all_fp(oshandle_t handle, buffer_t buffer) {
|
|
||||||
return os_file_write(handle, buffer.data, buffer.len) == buffer.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool os_file_write_all_str(strview_t name, strview_t data) {
|
|
||||||
oshandle_t fp = os_file_open(name, FILEMODE_WRITE);
|
|
||||||
bool result = os_file_write_all_str_fp(fp, data);
|
|
||||||
os_file_close(fp);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool os_file_write_all_str_fp(oshandle_t handle, strview_t data) {
|
|
||||||
return os_file_write(handle, data.buf, data.len) == data.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
u64 os_file_time(strview_t path) {
|
|
||||||
oshandle_t fp = os_file_open(path, FILEMODE_READ);
|
|
||||||
u64 result = os_file_time_fp(fp);
|
|
||||||
os_file_close(fp);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool os_file_has_changed(strview_t path, u64 last_change) {
|
|
||||||
u64 timestamp = os_file_time(path);
|
|
||||||
return timestamp > last_change;
|
|
||||||
}
|
|
||||||
|
|
||||||
// == PROCESS ===================================
|
|
||||||
|
|
||||||
bool os_run_cmd(arena_t scratch, os_cmd_t *cmd, os_env_t *optional_env) {
|
|
||||||
oshandle_t proc = os_run_cmd_async(scratch, cmd, optional_env);
|
|
||||||
return os_handle_valid(proc) ? os_process_wait(proc, OS_WAIT_INFINITE, NULL) : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// == VMEM ======================================
|
|
||||||
|
|
||||||
usize os_pad_to_page(usize byte_count) {
|
|
||||||
usize page_size = os_get_system_info().page_size;
|
|
||||||
|
|
||||||
if (byte_count == 0) {
|
|
||||||
return page_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize padding = page_size - (byte_count & (page_size - 1));
|
|
||||||
if (padding == page_size) {
|
|
||||||
padding = 0;
|
|
||||||
}
|
|
||||||
return byte_count + padding;
|
|
||||||
}
|
|
||||||
238
os.h
238
os.h
|
|
@ -1,238 +0,0 @@
|
||||||
#ifndef COLLA_OS_H
|
|
||||||
#define COLLA_OS_H
|
|
||||||
|
|
||||||
#include "core.h"
|
|
||||||
#include "str.h"
|
|
||||||
#include "arena.h"
|
|
||||||
|
|
||||||
#define OS_ARENA_SIZE (MB(1))
|
|
||||||
#define OS_WAIT_INFINITE (0xFFFFFFFF)
|
|
||||||
|
|
||||||
typedef struct oshandle_t oshandle_t;
|
|
||||||
struct oshandle_t {
|
|
||||||
uptr data;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct os_system_info_t os_system_info_t;
|
|
||||||
struct os_system_info_t {
|
|
||||||
u32 processor_count;
|
|
||||||
u64 page_size;
|
|
||||||
str_t machine_name;
|
|
||||||
};
|
|
||||||
|
|
||||||
void os_init(void);
|
|
||||||
void os_cleanup(void);
|
|
||||||
os_system_info_t os_get_system_info(void);
|
|
||||||
void os_abort(int code);
|
|
||||||
|
|
||||||
iptr os_get_last_error(void);
|
|
||||||
// NOT thread safe
|
|
||||||
str_t os_get_error_string(iptr error);
|
|
||||||
|
|
||||||
// == HANDLE ====================================
|
|
||||||
|
|
||||||
oshandle_t os_handle_zero(void);
|
|
||||||
bool os_handle_match(oshandle_t a, oshandle_t b);
|
|
||||||
bool os_handle_valid(oshandle_t handle);
|
|
||||||
|
|
||||||
#define OS_MAX_WAITABLE_HANDLES 256
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
OS_WAIT_FINISHED,
|
|
||||||
OS_WAIT_ABANDONED,
|
|
||||||
OS_WAIT_TIMEOUT,
|
|
||||||
OS_WAIT_FAILED
|
|
||||||
} os_wait_result_e;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
os_wait_result_e result;
|
|
||||||
u32 index;
|
|
||||||
} os_wait_t;
|
|
||||||
|
|
||||||
os_wait_t os_wait_on_handles(oshandle_t *handles, int count, bool wait_all, u32 milliseconds);
|
|
||||||
|
|
||||||
// == LOGGING ===================================
|
|
||||||
|
|
||||||
typedef enum os_log_level_e {
|
|
||||||
LOG_BASIC,
|
|
||||||
LOG_DEBUG,
|
|
||||||
LOG_INFO,
|
|
||||||
LOG_WARN,
|
|
||||||
LOG_ERR,
|
|
||||||
LOG_FATAL,
|
|
||||||
} os_log_level_e;
|
|
||||||
|
|
||||||
typedef enum os_log_colour_e {
|
|
||||||
LOG_COL_BLACK = 0,
|
|
||||||
LOG_COL_BLUE = 1,
|
|
||||||
LOG_COL_GREEN = 2,
|
|
||||||
LOG_COL_CYAN = LOG_COL_BLUE | LOG_COL_GREEN,
|
|
||||||
LOG_COL_RED = 4,
|
|
||||||
LOG_COL_MAGENTA = LOG_COL_RED | LOG_COL_BLUE,
|
|
||||||
LOG_COL_YELLOW = LOG_COL_RED | LOG_COL_GREEN,
|
|
||||||
LOG_COL_GREY = LOG_COL_RED | LOG_COL_BLUE | LOG_COL_GREEN,
|
|
||||||
|
|
||||||
LOG_COL_LIGHT = 8,
|
|
||||||
|
|
||||||
LOG_COL_DARK_GREY = LOG_COL_BLACK | LOG_COL_LIGHT,
|
|
||||||
LOG_COL_LIGHT_BLUE = LOG_COL_BLUE | LOG_COL_LIGHT,
|
|
||||||
LOG_COL_LIGHT_GREEN = LOG_COL_GREEN | LOG_COL_LIGHT,
|
|
||||||
LOG_COL_LIGHT_CYAN = LOG_COL_CYAN | LOG_COL_LIGHT,
|
|
||||||
LOG_COL_LIGHT_RED = LOG_COL_RED | LOG_COL_LIGHT,
|
|
||||||
LOG_COL_LIGHT_MAGENTA = LOG_COL_MAGENTA | LOG_COL_LIGHT,
|
|
||||||
LOG_COL_LIGHT_YELLOW = LOG_COL_YELLOW | LOG_COL_LIGHT,
|
|
||||||
LOG_COL_WHITE = LOG_COL_GREY | LOG_COL_LIGHT,
|
|
||||||
|
|
||||||
LOG_COL_RESET,
|
|
||||||
|
|
||||||
LOG_COL__COUNT,
|
|
||||||
} os_log_colour_e;
|
|
||||||
|
|
||||||
void os_log_print(os_log_level_e level, const char *fmt, ...);
|
|
||||||
void os_log_printv(os_log_level_e level, const char *fmt, va_list args);
|
|
||||||
void os_log_set_colour(os_log_colour_e colour);
|
|
||||||
void os_log_set_colour_bg(os_log_colour_e foreground, os_log_colour_e background);
|
|
||||||
|
|
||||||
oshandle_t os_stdout(void);
|
|
||||||
oshandle_t os_stdin(void);
|
|
||||||
|
|
||||||
#define print(...) fmt_print(__VA_ARGS__)
|
|
||||||
#define println(...) os_log_print(LOG_BASIC, __VA_ARGS__)
|
|
||||||
#define debug(...) os_log_print(LOG_DEBUG, __VA_ARGS__)
|
|
||||||
#define info(...) os_log_print(LOG_INFO, __VA_ARGS__)
|
|
||||||
#define warn(...) os_log_print(LOG_WARN, __VA_ARGS__)
|
|
||||||
#define err(...) os_log_print(LOG_ERR, __VA_ARGS__)
|
|
||||||
#define fatal(...) os_log_print(LOG_FATAL, __VA_ARGS__)
|
|
||||||
|
|
||||||
// == FILE ======================================
|
|
||||||
|
|
||||||
typedef enum filemode_e {
|
|
||||||
FILEMODE_READ = 1 << 0,
|
|
||||||
FILEMODE_WRITE = 1 << 1,
|
|
||||||
} filemode_e;
|
|
||||||
|
|
||||||
bool os_file_exists(strview_t filename);
|
|
||||||
bool os_dir_exists(strview_t folder);
|
|
||||||
bool os_file_or_dir_exists(strview_t path);
|
|
||||||
bool os_dir_create(strview_t folder);
|
|
||||||
tstr_t os_file_fullpath(arena_t *arena, strview_t filename);
|
|
||||||
void os_file_split_path(strview_t path, strview_t *dir, strview_t *name, strview_t *ext);
|
|
||||||
bool os_file_delete(strview_t path);
|
|
||||||
bool os_dir_delete(strview_t path);
|
|
||||||
|
|
||||||
oshandle_t os_file_open(strview_t path, filemode_e mode);
|
|
||||||
void os_file_close(oshandle_t handle);
|
|
||||||
|
|
||||||
bool os_file_putc(oshandle_t handle, char c);
|
|
||||||
bool os_file_puts(oshandle_t handle, strview_t str);
|
|
||||||
bool os_file_print(arena_t scratch, oshandle_t handle, const char *fmt, ...);
|
|
||||||
bool os_file_printv(arena_t scratch, oshandle_t handle, const char *fmt, va_list args);
|
|
||||||
|
|
||||||
usize os_file_read(oshandle_t handle, void *buf, usize len);
|
|
||||||
usize os_file_write(oshandle_t handle, const void *buf, usize len);
|
|
||||||
|
|
||||||
usize os_file_read_buf(oshandle_t handle, buffer_t *buf);
|
|
||||||
usize os_file_write_buf(oshandle_t handle, buffer_t buf);
|
|
||||||
|
|
||||||
bool os_file_seek(oshandle_t handle, usize offset);
|
|
||||||
bool os_file_seek_end(oshandle_t handle);
|
|
||||||
void os_file_rewind(oshandle_t handle);
|
|
||||||
usize os_file_tell(oshandle_t handle);
|
|
||||||
usize os_file_size(oshandle_t handle);
|
|
||||||
bool os_file_is_finished(oshandle_t handle);
|
|
||||||
|
|
||||||
buffer_t os_file_read_all(arena_t *arena, strview_t path);
|
|
||||||
buffer_t os_file_read_all_fp(arena_t *arena, oshandle_t handle);
|
|
||||||
|
|
||||||
str_t os_file_read_all_str(arena_t *arena, strview_t path);
|
|
||||||
str_t os_file_read_all_str_fp(arena_t *arena, oshandle_t handle);
|
|
||||||
|
|
||||||
bool os_file_write_all(strview_t name, buffer_t buffer);
|
|
||||||
bool os_file_write_all_fp(oshandle_t handle, buffer_t buffer);
|
|
||||||
|
|
||||||
bool os_file_write_all_str(strview_t name, strview_t data);
|
|
||||||
bool os_file_write_all_str_fp(oshandle_t handle, strview_t data);
|
|
||||||
|
|
||||||
u64 os_file_time(strview_t path);
|
|
||||||
u64 os_file_time_fp(oshandle_t handle);
|
|
||||||
bool os_file_has_changed(strview_t path, u64 last_change);
|
|
||||||
|
|
||||||
// == DIR WALKER ================================
|
|
||||||
|
|
||||||
typedef enum dir_type_e {
|
|
||||||
DIRTYPE_FILE,
|
|
||||||
DIRTYPE_DIR,
|
|
||||||
} dir_type_e;
|
|
||||||
|
|
||||||
typedef struct dir_entry_t dir_entry_t;
|
|
||||||
struct dir_entry_t {
|
|
||||||
str_t name;
|
|
||||||
dir_type_e type;
|
|
||||||
usize file_size;
|
|
||||||
};
|
|
||||||
|
|
||||||
#define dir_foreach(arena, it, dir) for (dir_entry_t *it = os_dir_next(arena, dir); it; it = os_dir_next(arena, dir))
|
|
||||||
|
|
||||||
typedef struct dir_t dir_t;
|
|
||||||
dir_t *os_dir_open(arena_t *arena, strview_t path);
|
|
||||||
bool os_dir_is_valid(dir_t *dir);
|
|
||||||
// optional, only call this if you want to return before os_dir_next returns NULL
|
|
||||||
void os_dir_close(dir_t *dir);
|
|
||||||
|
|
||||||
dir_entry_t *os_dir_next(arena_t *arena, dir_t *dir);
|
|
||||||
|
|
||||||
// == PROCESS ===================================
|
|
||||||
|
|
||||||
typedef struct os_env_t os_env_t;
|
|
||||||
typedef strv_list_t os_cmd_t;
|
|
||||||
#define os_make_cmd(...) &(os_cmd_t){ .items = (strview_t[]){ __VA_ARGS__ }, .count = arrlen(((strview_t[]){ __VA_ARGS__ })) }
|
|
||||||
|
|
||||||
void os_set_env_var(arena_t scratch, strview_t key, strview_t value);
|
|
||||||
str_t os_get_env_var(arena_t *arena, strview_t key);
|
|
||||||
os_env_t *os_get_env(arena_t *arena);
|
|
||||||
bool os_run_cmd(arena_t scratch, os_cmd_t *cmd, os_env_t *optional_env);
|
|
||||||
oshandle_t os_run_cmd_async(arena_t scratch, os_cmd_t *cmd, os_env_t *optional_env);
|
|
||||||
bool os_process_wait(oshandle_t proc, uint time, int *out_exit);
|
|
||||||
|
|
||||||
// == MEMORY ====================================
|
|
||||||
|
|
||||||
void *os_alloc(usize size);
|
|
||||||
void os_free(void *ptr);
|
|
||||||
|
|
||||||
void *os_reserve(usize size, usize *out_padded_size);
|
|
||||||
bool os_commit(void *ptr, usize num_of_pages);
|
|
||||||
bool os_release(void *ptr, usize size);
|
|
||||||
usize os_pad_to_page(usize byte_count);
|
|
||||||
|
|
||||||
// == THREAD ====================================
|
|
||||||
|
|
||||||
typedef int (thread_func_t)(u64 thread_id, void *userdata);
|
|
||||||
|
|
||||||
oshandle_t os_thread_launch(thread_func_t func, void *userdata);
|
|
||||||
bool os_thread_detach(oshandle_t thread);
|
|
||||||
bool os_thread_join(oshandle_t thread, int *code);
|
|
||||||
|
|
||||||
u64 os_thread_get_id(oshandle_t thread);
|
|
||||||
|
|
||||||
// == MUTEX =====================================
|
|
||||||
|
|
||||||
oshandle_t os_mutex_create(void);
|
|
||||||
void os_mutex_free(oshandle_t mutex);
|
|
||||||
void os_mutex_lock(oshandle_t mutex);
|
|
||||||
void os_mutex_unlock(oshandle_t mutex);
|
|
||||||
bool os_mutex_try_lock(oshandle_t mutex);
|
|
||||||
|
|
||||||
#if !COLLA_NO_CONDITION_VARIABLE
|
|
||||||
// == CONDITION VARIABLE ========================
|
|
||||||
|
|
||||||
oshandle_t os_cond_create(void);
|
|
||||||
void os_cond_free(oshandle_t cond);
|
|
||||||
|
|
||||||
void os_cond_signal(oshandle_t cond);
|
|
||||||
void os_cond_broadcast(oshandle_t cond);
|
|
||||||
|
|
||||||
void os_cond_wait(oshandle_t cond, oshandle_t mutex, int milliseconds);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
199
parsers.h
199
parsers.h
|
|
@ -1,199 +0,0 @@
|
||||||
#ifndef COLLA_PARSERS_H
|
|
||||||
#define COLLA_PARSERS_H
|
|
||||||
|
|
||||||
#include "core.h"
|
|
||||||
#include "os.h"
|
|
||||||
#include "str.h"
|
|
||||||
|
|
||||||
// == INI ============================================
|
|
||||||
|
|
||||||
typedef struct inivalue_t inivalue_t;
|
|
||||||
struct inivalue_t {
|
|
||||||
strview_t key;
|
|
||||||
strview_t value;
|
|
||||||
inivalue_t *next;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct initable_t initable_t;
|
|
||||||
struct initable_t {
|
|
||||||
strview_t name;
|
|
||||||
inivalue_t *values;
|
|
||||||
inivalue_t *tail;
|
|
||||||
initable_t *next;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct ini_t ini_t;
|
|
||||||
struct ini_t {
|
|
||||||
strview_t text;
|
|
||||||
initable_t *tables;
|
|
||||||
initable_t *tail;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct iniopt_t iniopt_t;
|
|
||||||
struct iniopt_t {
|
|
||||||
bool merge_duplicate_tables; // default false
|
|
||||||
bool merge_duplicate_keys; // default false
|
|
||||||
char key_value_divider; // default =
|
|
||||||
strview_t comment_vals; // default ;#
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct iniarray_t iniarray_t;
|
|
||||||
struct iniarray_t {
|
|
||||||
strview_t *values;
|
|
||||||
usize count;
|
|
||||||
};
|
|
||||||
|
|
||||||
#define INI_ROOT strv("__ROOT__")
|
|
||||||
|
|
||||||
ini_t ini_parse(arena_t *arena, strview_t filename, iniopt_t *opt);
|
|
||||||
ini_t ini_parse_fp(arena_t *arena, oshandle_t file, iniopt_t *opt);
|
|
||||||
ini_t ini_parse_str(arena_t *arena, strview_t str, iniopt_t *opt);
|
|
||||||
|
|
||||||
bool ini_is_valid(ini_t *ini);
|
|
||||||
|
|
||||||
initable_t *ini_get_table(ini_t *ini, strview_t name);
|
|
||||||
inivalue_t *ini_get(initable_t *table, strview_t key);
|
|
||||||
|
|
||||||
iniarray_t ini_as_arr(arena_t *arena, inivalue_t *value, char delim);
|
|
||||||
u64 ini_as_uint(inivalue_t *value);
|
|
||||||
i64 ini_as_int(inivalue_t *value);
|
|
||||||
double ini_as_num(inivalue_t *value);
|
|
||||||
bool ini_as_bool(inivalue_t *value);
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
INI_PRETTY_COLOUR_KEY,
|
|
||||||
INI_PRETTY_COLOUR_VALUE,
|
|
||||||
INI_PRETTY_COLOUR_DIVIDER,
|
|
||||||
INI_PRETTY_COLOUR_TABLE,
|
|
||||||
INI_PRETTY_COLOUR__COUNT,
|
|
||||||
} ini_pretty_colours_e;
|
|
||||||
|
|
||||||
typedef struct ini_pretty_opts_t ini_pretty_opts_t;
|
|
||||||
struct ini_pretty_opts_t {
|
|
||||||
oshandle_t custom_target;
|
|
||||||
bool use_custom_colours;
|
|
||||||
os_log_colour_e colours[INI_PRETTY_COLOUR__COUNT];
|
|
||||||
};
|
|
||||||
|
|
||||||
void ini_pretty_print(ini_t *ini, const ini_pretty_opts_t *options);
|
|
||||||
|
|
||||||
|
|
||||||
// == JSON ===========================================
|
|
||||||
|
|
||||||
typedef enum jsontype_e {
|
|
||||||
JSON_NULL,
|
|
||||||
JSON_ARRAY,
|
|
||||||
JSON_STRING,
|
|
||||||
JSON_NUMBER,
|
|
||||||
JSON_BOOL,
|
|
||||||
JSON_OBJECT,
|
|
||||||
} jsontype_e;
|
|
||||||
|
|
||||||
typedef enum jsonflags_e {
|
|
||||||
JSON_DEFAULT = 0,
|
|
||||||
JSON_NO_TRAILING_COMMAS = 1 << 0,
|
|
||||||
JSON_NO_COMMENTS = 1 << 1,
|
|
||||||
} jsonflags_e;
|
|
||||||
|
|
||||||
typedef struct json_t json_t;
|
|
||||||
struct json_t {
|
|
||||||
json_t *next;
|
|
||||||
json_t *prev;
|
|
||||||
|
|
||||||
strview_t key;
|
|
||||||
|
|
||||||
union {
|
|
||||||
json_t *array;
|
|
||||||
strview_t string;
|
|
||||||
double number;
|
|
||||||
bool boolean;
|
|
||||||
json_t *object;
|
|
||||||
};
|
|
||||||
jsontype_e type;
|
|
||||||
};
|
|
||||||
|
|
||||||
json_t *json_parse(arena_t *arena, strview_t filename, jsonflags_e flags);
|
|
||||||
json_t *json_parse_str(arena_t *arena, strview_t str, jsonflags_e flags);
|
|
||||||
|
|
||||||
json_t *json_get(json_t *node, strview_t key);
|
|
||||||
|
|
||||||
#define json_check(val, js_type) ((val) && (val)->type == js_type)
|
|
||||||
#define json_for(it, arr) for (json_t *it = json_check(arr, JSON_ARRAY) ? arr->array : NULL; it; it = it->next)
|
|
||||||
|
|
||||||
typedef enum json_pretty_colours_e {
|
|
||||||
JSON_PRETTY_COLOUR_KEY,
|
|
||||||
JSON_PRETTY_COLOUR_STRING,
|
|
||||||
JSON_PRETTY_COLOUR_NUM,
|
|
||||||
JSON_PRETTY_COLOUR_NULL,
|
|
||||||
JSON_PRETTY_COLOUR_TRUE,
|
|
||||||
JSON_PRETTY_COLOUR_FALSE,
|
|
||||||
JSON_PRETTY_COLOUR__COUNT,
|
|
||||||
} json_pretty_colours_e;
|
|
||||||
|
|
||||||
typedef struct json_pretty_opts_t json_pretty_opts_t;
|
|
||||||
struct json_pretty_opts_t {
|
|
||||||
oshandle_t custom_target;
|
|
||||||
bool use_custom_colours;
|
|
||||||
os_log_colour_e colours[JSON_PRETTY_COLOUR__COUNT];
|
|
||||||
};
|
|
||||||
|
|
||||||
void json_pretty_print(json_t *root, const json_pretty_opts_t *options);
|
|
||||||
|
|
||||||
// == XML ============================================
|
|
||||||
|
|
||||||
typedef struct xmlattr_t xmlattr_t;
|
|
||||||
struct xmlattr_t {
|
|
||||||
strview_t key;
|
|
||||||
strview_t value;
|
|
||||||
xmlattr_t *next;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct xmltag_t xmltag_t;
|
|
||||||
struct xmltag_t {
|
|
||||||
strview_t key;
|
|
||||||
xmlattr_t *attributes;
|
|
||||||
strview_t content;
|
|
||||||
xmltag_t *child;
|
|
||||||
xmltag_t *tail;
|
|
||||||
xmltag_t *next;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct xml_t xml_t;
|
|
||||||
struct xml_t {
|
|
||||||
strview_t text;
|
|
||||||
xmltag_t *root;
|
|
||||||
xmltag_t *tail;
|
|
||||||
};
|
|
||||||
|
|
||||||
xml_t xml_parse(arena_t *arena, strview_t filename);
|
|
||||||
xml_t xml_parse_str(arena_t *arena, strview_t xmlstr);
|
|
||||||
|
|
||||||
xmltag_t *xml_get_tag(xmltag_t *parent, strview_t key, bool recursive);
|
|
||||||
strview_t xml_get_attribute(xmltag_t *tag, strview_t key);
|
|
||||||
|
|
||||||
// == HTML ===========================================
|
|
||||||
|
|
||||||
typedef struct htmltag_t htmltag_t;
|
|
||||||
struct htmltag_t {
|
|
||||||
str_t key;
|
|
||||||
xmlattr_t *attributes;
|
|
||||||
strview_t content;
|
|
||||||
htmltag_t *children;
|
|
||||||
htmltag_t *tail;
|
|
||||||
htmltag_t *next;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct html_t html_t;
|
|
||||||
struct html_t {
|
|
||||||
strview_t text;
|
|
||||||
htmltag_t *root;
|
|
||||||
htmltag_t *tail;
|
|
||||||
};
|
|
||||||
|
|
||||||
html_t html_parse(arena_t *arena, strview_t filename);
|
|
||||||
html_t html_parse_str(arena_t *arena, strview_t str);
|
|
||||||
|
|
||||||
htmltag_t *html_get_tag(htmltag_t *parent, strview_t key, bool recursive);
|
|
||||||
strview_t html_get_attribute(htmltag_t *tag, strview_t key);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
#include "pretty_print.h"
|
|
||||||
|
|
||||||
#include <stdarg.h>
|
|
||||||
|
|
||||||
#include "core.h"
|
|
||||||
|
|
||||||
strview_t pretty__colour[LOG_COL__COUNT] = {
|
|
||||||
[LOG_COL_BLACK] = cstrv("black"),
|
|
||||||
[LOG_COL_BLUE] = cstrv("blue"),
|
|
||||||
[LOG_COL_GREEN] = cstrv("green"),
|
|
||||||
[LOG_COL_CYAN] = cstrv("cyan"),
|
|
||||||
[LOG_COL_RED] = cstrv("red"),
|
|
||||||
[LOG_COL_MAGENTA] = cstrv("magenta"),
|
|
||||||
[LOG_COL_YELLOW] = cstrv("yellow"),
|
|
||||||
[LOG_COL_GREY] = cstrv("grey"),
|
|
||||||
|
|
||||||
[LOG_COL_DARK_GREY] = cstrv("dark_grey"),
|
|
||||||
[LOG_COL_WHITE] = cstrv("white"),
|
|
||||||
[LOG_COL_LIGHT_BLUE] = cstrv("light_blue"),
|
|
||||||
[LOG_COL_LIGHT_GREEN] = cstrv("light_green"),
|
|
||||||
[LOG_COL_LIGHT_CYAN] = cstrv("light_cyan"),
|
|
||||||
[LOG_COL_LIGHT_RED] = cstrv("light_red"),
|
|
||||||
[LOG_COL_LIGHT_MAGENTA] = cstrv("light_magenta"),
|
|
||||||
[LOG_COL_LIGHT_YELLOW] = cstrv("light_yellow"),
|
|
||||||
|
|
||||||
[LOG_COL_RESET] = cstrv("/"),
|
|
||||||
};
|
|
||||||
|
|
||||||
strview_t pretty_log_to_colour(os_log_colour_e colour) {
|
|
||||||
return pretty__colour[colour];
|
|
||||||
}
|
|
||||||
|
|
||||||
void pretty_print(arena_t scratch, const char *fmt, ...) {
|
|
||||||
va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
pretty_printv(scratch, fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
void pretty_printv(arena_t scratch, const char *fmt, va_list args) {
|
|
||||||
va_list tmp_args;
|
|
||||||
va_copy(tmp_args, args);
|
|
||||||
int len = fmt_bufferv(NULL, 0, fmt, tmp_args);
|
|
||||||
va_end(tmp_args);
|
|
||||||
|
|
||||||
char *buf = alloc(&scratch, char, len + 1);
|
|
||||||
|
|
||||||
fmt_bufferv(buf, len + 1, fmt, args);
|
|
||||||
|
|
||||||
oshandle_t out = os_stdout();
|
|
||||||
instream_t in = istr_init(strv(buf, len));
|
|
||||||
while (!istr_is_finished(&in)) {
|
|
||||||
strview_t part = istr_get_view(&in, '<');
|
|
||||||
bool has_escape = strv_ends_with(part, '\\');
|
|
||||||
|
|
||||||
if (has_escape) {
|
|
||||||
part.len -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
os_file_write(out, part.buf, part.len);
|
|
||||||
istr_skip(&in, 1);
|
|
||||||
|
|
||||||
if (has_escape) {
|
|
||||||
os_file_putc(out, '<');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
strview_t tag = istr_get_view(&in, '>');
|
|
||||||
|
|
||||||
for (usize i = 0; i < arrlen(pretty__colour); ++i) {
|
|
||||||
if (strv_equals(tag, pretty__colour[i])) {
|
|
||||||
os_log_set_colour(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
istr_skip(&in, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "os.h"
|
|
||||||
#include "arena.h"
|
|
||||||
#include "str.h"
|
|
||||||
|
|
||||||
strview_t pretty_log_to_colour(os_log_colour_e colour);
|
|
||||||
void pretty_print(arena_t scratch, const char *fmt, ...);
|
|
||||||
void pretty_printv(arena_t scratch, const char *fmt, va_list args);
|
|
||||||
|
|
||||||
1945
stb/stb_sprintf.h
1945
stb/stb_sprintf.h
File diff suppressed because it is too large
Load diff
795
str.c
795
str.c
|
|
@ -1,795 +0,0 @@
|
||||||
#include "str.h"
|
|
||||||
|
|
||||||
#include "os.h"
|
|
||||||
|
|
||||||
#include <limits.h>
|
|
||||||
#include <math.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#if COLLA_WIN
|
|
||||||
#include "win/str_win32.c"
|
|
||||||
#else
|
|
||||||
#error "platform not supported"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// == STR_T ========================================================
|
|
||||||
|
|
||||||
strview_t strv__ignore(str_t s, size_t l) {
|
|
||||||
COLLA_UNUSED(s); COLLA_UNUSED(l);
|
|
||||||
return STRV_EMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t str_init(arena_t *arena, const char *buf) {
|
|
||||||
return str_init_len(arena, buf, buf ? strlen(buf) : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t str_init_len(arena_t *arena, const char *buf, usize len) {
|
|
||||||
if (!buf || !len) return STR_EMPTY;
|
|
||||||
char *str = alloc(arena, char, len + 1);
|
|
||||||
memmove(str, buf, len);
|
|
||||||
return (str_t){ str, len };
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t str_init_view(arena_t *arena, strview_t view) {
|
|
||||||
return str_init_len(arena, view.buf, view.len);
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t str_fmt(arena_t *arena, const char *fmt, ...) {
|
|
||||||
va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
str_t out = str_fmtv(arena, fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t str_fmtv(arena_t *arena, const char *fmt, va_list args) {
|
|
||||||
va_list vcopy;
|
|
||||||
va_copy(vcopy, args);
|
|
||||||
// stb_vsnprintf returns the length + null_term
|
|
||||||
int len = fmt_bufferv(NULL, 0, fmt, vcopy);
|
|
||||||
va_end(vcopy);
|
|
||||||
|
|
||||||
char *buffer = alloc(arena, char, len + 1);
|
|
||||||
fmt_bufferv(buffer, len + 1, fmt, args);
|
|
||||||
|
|
||||||
return (str_t) { .buf = buffer, .len = (usize)len };
|
|
||||||
}
|
|
||||||
|
|
||||||
tstr_t tstr_init(TCHAR *str, usize optional_len) {
|
|
||||||
if (str && !optional_len) {
|
|
||||||
#if COLLA_UNICODE
|
|
||||||
optional_len = wcslen(str);
|
|
||||||
#else
|
|
||||||
optional_len = strlen(str);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
return (tstr_t){
|
|
||||||
.buf = str,
|
|
||||||
.len = optional_len,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
str16_t str16_init(u16 *str, usize optional_len) {
|
|
||||||
if (str && !optional_len) {
|
|
||||||
optional_len = wcslen(str);
|
|
||||||
}
|
|
||||||
return (str16_t){
|
|
||||||
.buf = str,
|
|
||||||
.len = optional_len,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t str_from_str16(arena_t *arena, str16_t src) {
|
|
||||||
if (!src.buf) return STR_EMPTY;
|
|
||||||
if (!src.len) return STR_EMPTY;
|
|
||||||
|
|
||||||
str_t out = str_os_from_str16(arena, src);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t str_from_tstr(arena_t *arena, tstr_t src) {
|
|
||||||
#if COLLA_UNICODE
|
|
||||||
return str_from_str16(arena, src);
|
|
||||||
#else
|
|
||||||
return str(arena, strv(src));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
str16_t str16_from_str(arena_t *arena, str_t src) {
|
|
||||||
return strv_to_str16(arena, strv(src));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool str_equals(str_t a, str_t b) {
|
|
||||||
return str_compare(a, b) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int str_compare(str_t a, str_t b) {
|
|
||||||
// TODO unsinged underflow if a.len < b.len
|
|
||||||
return a.len == b.len ? memcmp(a.buf, b.buf, a.len) : (int)(a.len - b.len);
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t str_dup(arena_t *arena, str_t src) {
|
|
||||||
return str_init_len(arena, src.buf, src.len);
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t str_cat(arena_t *arena, str_t a, str_t b) {
|
|
||||||
str_t out = STR_EMPTY;
|
|
||||||
|
|
||||||
out.len += a.len + b.len;
|
|
||||||
out.buf = alloc(arena, char, out.len + 1);
|
|
||||||
memcpy(out.buf, a.buf, a.len);
|
|
||||||
memcpy(out.buf + a.len, b.buf, b.len);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool str_is_empty(str_t ctx) {
|
|
||||||
return !ctx.buf || !ctx.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
void str_lower(str_t *src) {
|
|
||||||
for (usize i = 0; i < src->len; ++i) {
|
|
||||||
if (src->buf[i] >= 'A' && src->buf[i] <= 'Z') {
|
|
||||||
src->buf[i] += 'a' - 'A';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void str_upper(str_t *src) {
|
|
||||||
for (usize i = 0; i < src->len; ++i) {
|
|
||||||
if (src->buf[i] >= 'a' && src->buf[i] <= 'z') {
|
|
||||||
src->buf[i] -= 'a' - 'A';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void str_replace(str_t *ctx, char from, char to) {
|
|
||||||
if (!ctx) return;
|
|
||||||
char *buf = ctx->buf;
|
|
||||||
for (usize i = 0; i < ctx->len; ++i) {
|
|
||||||
buf[i] = buf[i] == from ? to : buf[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
strview_t str_sub(str_t ctx, usize from, usize to) {
|
|
||||||
if (to > ctx.len) to = ctx.len;
|
|
||||||
if (from > to) from = to;
|
|
||||||
return (strview_t){ ctx.buf + from, to - from };
|
|
||||||
}
|
|
||||||
|
|
||||||
// == STRVIEW_T ====================================================
|
|
||||||
|
|
||||||
strview_t strv_init(const char *cstr) {
|
|
||||||
return strv_init_len(cstr, cstr ? strlen(cstr) : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
strview_t strv_init_len(const char *buf, usize size) {
|
|
||||||
return (strview_t){
|
|
||||||
.buf = buf,
|
|
||||||
.len = size,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
strview_t strv_init_str(str_t str) {
|
|
||||||
return (strview_t){
|
|
||||||
.buf = str.buf,
|
|
||||||
.len = str.len
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool strv_is_empty(strview_t ctx) {
|
|
||||||
return ctx.len == 0 || !ctx.buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool strv_equals(strview_t a, strview_t b) {
|
|
||||||
return strv_compare(a, b) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int strv_compare(strview_t a, strview_t b) {
|
|
||||||
// TODO unsinged underflow if a.len < b.len
|
|
||||||
return a.len == b.len ?
|
|
||||||
memcmp(a.buf, b.buf, a.len) :
|
|
||||||
(int)(a.len - b.len);
|
|
||||||
}
|
|
||||||
|
|
||||||
char strv_front(strview_t ctx) {
|
|
||||||
return ctx.len > 0 ? ctx.buf[0] : '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
char strv_back(strview_t ctx) {
|
|
||||||
return ctx.len > 0 ? ctx.buf[ctx.len - 1] : '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
str16_t strv_to_str16(arena_t *arena, strview_t src) {
|
|
||||||
return strv_os_to_str16(arena, src);
|
|
||||||
}
|
|
||||||
|
|
||||||
tstr_t strv_to_tstr(arena_t *arena, strview_t src) {
|
|
||||||
#if UNICODE
|
|
||||||
return strv_to_str16(arena, src);
|
|
||||||
#else
|
|
||||||
return str(arena, src);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t strv_to_upper(arena_t *arena, strview_t src) {
|
|
||||||
str_t out = str(arena, src);
|
|
||||||
str_upper(&out);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t strv_to_lower(arena_t *arena, strview_t src) {
|
|
||||||
str_t out = str(arena, src);
|
|
||||||
str_lower(&out);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
strview_t strv_remove_prefix(strview_t ctx, usize n) {
|
|
||||||
if (n > ctx.len) n = ctx.len;
|
|
||||||
return (strview_t){
|
|
||||||
.buf = ctx.buf + n,
|
|
||||||
.len = ctx.len - n,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
strview_t strv_remove_suffix(strview_t ctx, usize n) {
|
|
||||||
if (n > ctx.len) n = ctx.len;
|
|
||||||
return (strview_t){
|
|
||||||
.buf = ctx.buf,
|
|
||||||
.len = ctx.len - n,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
strview_t strv_trim(strview_t ctx) {
|
|
||||||
return strv_trim_left(strv_trim_right(ctx));
|
|
||||||
}
|
|
||||||
|
|
||||||
strview_t strv_trim_left(strview_t ctx) {
|
|
||||||
strview_t out = ctx;
|
|
||||||
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 strv_trim_right(strview_t ctx) {
|
|
||||||
strview_t out = ctx;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
strview_t strv_sub(strview_t ctx, usize from, usize to) {
|
|
||||||
if (ctx.len == 0) return STRV_EMPTY;
|
|
||||||
if (to > ctx.len) to = ctx.len;
|
|
||||||
if (from > to) from = to;
|
|
||||||
return (strview_t){ ctx.buf + from, to - from };
|
|
||||||
}
|
|
||||||
|
|
||||||
bool strv_starts_with(strview_t ctx, char c) {
|
|
||||||
return ctx.len > 0 && ctx.buf[0] == c;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool strv_starts_with_view(strview_t ctx, strview_t view) {
|
|
||||||
return ctx.len >= view.len && memcmp(ctx.buf, view.buf, view.len) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool strv_ends_with(strview_t ctx, char c) {
|
|
||||||
return ctx.len > 0 && ctx.buf[ctx.len - 1] == c;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool strv_ends_with_view(strview_t ctx, strview_t view) {
|
|
||||||
return ctx.len >= view.len && memcmp(ctx.buf + ctx.len - view.len, view.buf, view.len) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool strv_contains(strview_t ctx, char c) {
|
|
||||||
for(usize i = 0; i < ctx.len; ++i) {
|
|
||||||
if(ctx.buf[i] == c) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool strv_contains_view(strview_t ctx, strview_t view) {
|
|
||||||
if (ctx.len < view.len) return false;
|
|
||||||
usize end = (ctx.len - view.len) + 1;
|
|
||||||
|
|
||||||
for (usize i = 0; i < end; ++i) {
|
|
||||||
if (memcmp(ctx.buf + i, view.buf, view.len) == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool strv_contains_either(strview_t ctx, strview_t chars) {
|
|
||||||
for (usize i = 0; i < ctx.len; ++i) {
|
|
||||||
if (strv_contains(chars, ctx.buf[i])) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize strv_find(strview_t ctx, char c, usize from) {
|
|
||||||
for (usize i = from; i < ctx.len; ++i) {
|
|
||||||
if (ctx.buf[i] == c) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return STR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize strv_find_view(strview_t ctx, strview_t view, usize from) {
|
|
||||||
if (view.len > ctx.len) return STR_NONE;
|
|
||||||
|
|
||||||
usize end = (ctx.len - view.len) + 1;
|
|
||||||
|
|
||||||
for (usize i = from; i < end; ++i) {
|
|
||||||
if (memcmp(ctx.buf + i, view.buf, view.len) == 0) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return STR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize strv_find_either(strview_t ctx, strview_t chars, usize from) {
|
|
||||||
if (from > ctx.len) from = ctx.len;
|
|
||||||
|
|
||||||
for (usize i = from; i < ctx.len; ++i) {
|
|
||||||
if (strv_contains(chars, ctx.buf[i])) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return STR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize strv_rfind(strview_t ctx, char c, usize from_right) {
|
|
||||||
if (ctx.len == 0) return STR_NONE;
|
|
||||||
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 STR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize strv_rfind_view(strview_t ctx, strview_t view, usize from_right) {
|
|
||||||
if (ctx.len == 0) return STR_NONE;
|
|
||||||
if (from_right > ctx.len) from_right = ctx.len;
|
|
||||||
isize end = (isize)(ctx.len - from_right);
|
|
||||||
if (end < (isize)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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return STR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// == CTYPE ========================================================
|
|
||||||
|
|
||||||
bool char_is_space(char c) {
|
|
||||||
return (c >= '\t' && c <= '\r') || c == ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
bool char_is_alpha(char c) {
|
|
||||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
|
||||||
}
|
|
||||||
|
|
||||||
bool char_is_num(char c) {
|
|
||||||
return c >= '0' && c <= '9';
|
|
||||||
}
|
|
||||||
|
|
||||||
char char_lower(char c) {
|
|
||||||
return c >= 'A' && c <= 'Z' ? c + 32 : c;
|
|
||||||
}
|
|
||||||
|
|
||||||
char char_upper(char c) {
|
|
||||||
return c <= 'a' && c >= 'z' ? c - 32 : c;
|
|
||||||
}
|
|
||||||
|
|
||||||
// == INPUT STREAM =================================================
|
|
||||||
|
|
||||||
instream_t istr_init(strview_t str) {
|
|
||||||
return (instream_t) {
|
|
||||||
.beg = str.buf,
|
|
||||||
.cur = str.buf,
|
|
||||||
.len = str.len,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
char istr_get(instream_t *ctx) {
|
|
||||||
return istr_remaining(ctx) ? *ctx->cur++ : '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
char istr_peek(instream_t *ctx) {
|
|
||||||
return istr_remaining(ctx) ? *ctx->cur : '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
char istr_peek_next(instream_t *ctx) {
|
|
||||||
return istr_remaining(ctx) > 1 ? *(ctx->cur + 1) : '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
char istr_prev(instream_t *ctx) {
|
|
||||||
return istr_tell(ctx) ? *(ctx->cur - 1) : '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
char istr_prev_prev(instream_t *ctx) {
|
|
||||||
return istr_tell(ctx) > 1 ? *(ctx->cur - 2) : '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
void istr_ignore(instream_t *ctx, char delim) {
|
|
||||||
while (!istr_is_finished(ctx) && *ctx->cur != delim) {
|
|
||||||
ctx->cur++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void istr_ignore_and_skip(instream_t *ctx, char delim) {
|
|
||||||
istr_ignore(ctx, delim);
|
|
||||||
istr_skip(ctx, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void istr_skip(instream_t *ctx, usize n) {
|
|
||||||
if (!ctx) return;
|
|
||||||
usize rem = istr_remaining(ctx);
|
|
||||||
if (n > rem) n = rem;
|
|
||||||
ctx->cur += n;
|
|
||||||
}
|
|
||||||
|
|
||||||
void istr_skip_whitespace(instream_t *ctx) {
|
|
||||||
while (!istr_is_finished(ctx) && char_is_space(*ctx->cur)) {
|
|
||||||
ctx->cur++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void istr_rewind(instream_t *ctx) {
|
|
||||||
if (ctx) ctx->cur = ctx->beg;
|
|
||||||
}
|
|
||||||
|
|
||||||
void istr_rewind_n(instream_t *ctx, usize amount) {
|
|
||||||
if (!ctx) return;
|
|
||||||
usize rem = istr_remaining(ctx);
|
|
||||||
ctx->cur -= MIN(amount, rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
usize istr_tell(instream_t *ctx) {
|
|
||||||
return ctx ? ctx->cur - ctx->beg : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize istr_remaining(instream_t *ctx) {
|
|
||||||
return ctx ? ctx->len - (ctx->cur - ctx->beg) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool istr_is_finished(instream_t *ctx) {
|
|
||||||
return !(ctx && istr_remaining(ctx) > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool istr_get_bool(instream_t *ctx, bool *val) {
|
|
||||||
if (!ctx || !ctx->cur || !val) return false;
|
|
||||||
usize rem = istr_remaining(ctx);
|
|
||||||
if (rem >= 4 && memcmp(ctx->cur, "true", 4) == 0) {
|
|
||||||
*val = true;
|
|
||||||
ctx->cur += 4;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (rem >= 5 && memcmp(ctx->cur, "false", 5) == 0) {
|
|
||||||
*val = false;
|
|
||||||
ctx->cur += 5;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool istr_get_u8(instream_t *ctx, u8 *val) {
|
|
||||||
u64 out = 0;
|
|
||||||
bool result = istr_get_u64(ctx, &out);
|
|
||||||
if (result && out < UINT8_MAX) {
|
|
||||||
*val = (u8)out;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool istr_get_u16(instream_t *ctx, u16 *val) {
|
|
||||||
u64 out = 0;
|
|
||||||
bool result = istr_get_u64(ctx, &out);
|
|
||||||
if (result && out < UINT16_MAX) {
|
|
||||||
*val = (u16)out;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool istr_get_u32(instream_t *ctx, u32 *val) {
|
|
||||||
u64 out = 0;
|
|
||||||
bool result = istr_get_u64(ctx, &out);
|
|
||||||
if (result && out < UINT32_MAX) {
|
|
||||||
*val = (u32)out;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool istr_get_u64(instream_t *ctx, u64 *val) {
|
|
||||||
if (!ctx || !ctx->cur || !val) return false;
|
|
||||||
char *end = NULL;
|
|
||||||
*val = strtoull(ctx->cur, &end, 0);
|
|
||||||
|
|
||||||
if (ctx->cur == end) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (*val == ULLONG_MAX) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx->cur = end;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool istr_get_i8(instream_t *ctx, i8 *val) {
|
|
||||||
i64 out = 0;
|
|
||||||
bool result = istr_get_i64(ctx, &out);
|
|
||||||
if (result && out > INT8_MIN && out < INT8_MAX) {
|
|
||||||
*val = (i8)out;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool istr_get_i16(instream_t *ctx, i16 *val) {
|
|
||||||
i64 out = 0;
|
|
||||||
bool result = istr_get_i64(ctx, &out);
|
|
||||||
if (result && out > INT16_MIN && out < INT16_MAX) {
|
|
||||||
*val = (i16)out;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool istr_get_i32(instream_t *ctx, i32 *val) {
|
|
||||||
i64 out = 0;
|
|
||||||
bool result = istr_get_i64(ctx, &out);
|
|
||||||
if (result && out > INT32_MIN && out < INT32_MAX) {
|
|
||||||
*val = (i32)out;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool istr_get_i64(instream_t *ctx, i64 *val) {
|
|
||||||
if (!ctx || !ctx->cur || !val) return false;
|
|
||||||
char *end = NULL;
|
|
||||||
*val = strtoll(ctx->cur, &end, 0);
|
|
||||||
|
|
||||||
if (ctx->cur == end) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if(*val == INT64_MAX || *val == INT64_MIN) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx->cur = end;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool istr_get_num(instream_t *ctx, double *val) {
|
|
||||||
if (!ctx || !ctx->cur || !val) return false;
|
|
||||||
char *end = NULL;
|
|
||||||
*val = strtod(ctx->cur, &end);
|
|
||||||
|
|
||||||
if(ctx->cur == end) {
|
|
||||||
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");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx->cur = end;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
strview_t istr_get_view(instream_t *ctx, char delim) {
|
|
||||||
if (!ctx || !ctx->cur) return STRV_EMPTY;
|
|
||||||
const char *from = ctx->cur;
|
|
||||||
istr_ignore(ctx, delim);
|
|
||||||
usize len = ctx->cur - from;
|
|
||||||
return strv(from, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
strview_t istr_get_view_either(instream_t *ctx, strview_t chars) {
|
|
||||||
if (!ctx || !ctx->cur) return STRV_EMPTY;
|
|
||||||
const char *from = ctx->cur;
|
|
||||||
while (!istr_is_finished(ctx) && !strv_contains(chars, *ctx->cur)) {
|
|
||||||
ctx->cur++;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize len = ctx->cur - from;
|
|
||||||
return strv(from, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
strview_t istr_get_view_len(instream_t *ctx, usize len) {
|
|
||||||
if (!ctx || !ctx->cur) return STRV_EMPTY;
|
|
||||||
const char *from = ctx->cur;
|
|
||||||
istr_skip(ctx, len);
|
|
||||||
usize buflen = ctx->cur - from;
|
|
||||||
return (strview_t){ from, buflen };
|
|
||||||
}
|
|
||||||
|
|
||||||
strview_t istr_get_line(instream_t *ctx) {
|
|
||||||
strview_t line = istr_get_view(ctx, '\n');
|
|
||||||
istr_skip(ctx, 1);
|
|
||||||
if (strv_ends_with(line, '\r')) {
|
|
||||||
line = strv_remove_suffix(line, 1);
|
|
||||||
}
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
|
|
||||||
// == OUTPUT STREAM ================================================
|
|
||||||
|
|
||||||
outstream_t ostr_init(arena_t *exclusive_arena) {
|
|
||||||
return (outstream_t) {
|
|
||||||
.beg = (char *)(exclusive_arena ? exclusive_arena->cur : NULL),
|
|
||||||
.arena = exclusive_arena,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void ostr_clear(outstream_t *ctx) {
|
|
||||||
arena_pop(ctx->arena, ostr_tell(ctx));
|
|
||||||
}
|
|
||||||
|
|
||||||
usize ostr_tell(outstream_t *ctx) {
|
|
||||||
return ctx->arena ? (char *)ctx->arena->cur - ctx->beg : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
char ostr_back(outstream_t *ctx) {
|
|
||||||
usize len = ostr_tell(ctx);
|
|
||||||
return len ? ctx->beg[len - 1] : '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t ostr_to_str(outstream_t *ctx) {
|
|
||||||
ostr_putc(ctx, '\0');
|
|
||||||
|
|
||||||
usize len = ostr_tell(ctx);
|
|
||||||
|
|
||||||
str_t out = {
|
|
||||||
.buf = ctx->beg,
|
|
||||||
.len = len ? len - 1 : 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
memset(ctx, 0, sizeof(outstream_t));
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
strview_t ostr_as_view(outstream_t *ctx) {
|
|
||||||
return strv(ctx->beg, ostr_tell(ctx));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ostr_pop(outstream_t *ctx, usize count) {
|
|
||||||
if (!ctx->arena) return;
|
|
||||||
arena_pop(ctx->arena, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ostr_print(outstream_t *ctx, const char *fmt, ...) {
|
|
||||||
va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
ostr_printv(ctx, fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ostr_printv(outstream_t *ctx, const char *fmt, va_list args) {
|
|
||||||
if (!ctx->arena) return;
|
|
||||||
str_fmtv(ctx->arena, fmt, args);
|
|
||||||
// remove null terminator
|
|
||||||
arena_pop(ctx->arena, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ostr_putc(outstream_t *ctx, char c) {
|
|
||||||
if (!ctx->arena) return;
|
|
||||||
char *newc = alloc(ctx->arena, char);
|
|
||||||
*newc = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ostr_puts(outstream_t *ctx, strview_t v) {
|
|
||||||
if (strv_is_empty(v)) return;
|
|
||||||
str(ctx->arena, v);
|
|
||||||
// remove null terminator
|
|
||||||
arena_pop(ctx->arena, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ostr_append_bool(outstream_t *ctx, bool val) {
|
|
||||||
ostr_puts(ctx, val ? strv("true") : strv("false"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ostr_append_uint(outstream_t *ctx, u64 val) {
|
|
||||||
ostr_print(ctx, "%I64u", val);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ostr_append_int(outstream_t *ctx, i64 val) {
|
|
||||||
ostr_print(ctx, "%I64d", val);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ostr_append_num(outstream_t *ctx, double val) {
|
|
||||||
ostr_print(ctx, "%g", val);
|
|
||||||
}
|
|
||||||
|
|
||||||
// == INPUT BINARY STREAM ==========================================
|
|
||||||
|
|
||||||
ibstream_t ibstr_init(buffer_t buffer) {
|
|
||||||
return (ibstream_t){
|
|
||||||
.beg = buffer.data,
|
|
||||||
.cur = buffer.data,
|
|
||||||
.len = buffer.len,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ibstr_is_finished(ibstream_t *ib) {
|
|
||||||
return !(ib && ibstr_remaining(ib) > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
usize ibstr_tell(ibstream_t *ib) {
|
|
||||||
return ib && ib->cur ? ib->cur - ib->beg : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize ibstr_remaining(ibstream_t *ib) {
|
|
||||||
return ib ? ib->len - ibstr_tell(ib) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize ibstr_read(ibstream_t *ib, void *buffer, usize len) {
|
|
||||||
usize rem = ibstr_remaining(ib);
|
|
||||||
if (len > rem) len = rem;
|
|
||||||
memmove(buffer, ib->cur, len);
|
|
||||||
ib->cur += len;
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ibstr_skip(ibstream_t *ib, usize count) {
|
|
||||||
usize rem = ibstr_remaining(ib);
|
|
||||||
if (count > rem) count = rem;
|
|
||||||
ib->cur += count;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ibstr_get_u8(ibstream_t *ib, u8 *out) {
|
|
||||||
return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ibstr_get_u16(ibstream_t *ib, u16 *out) {
|
|
||||||
return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ibstr_get_u32(ibstream_t *ib, u32 *out) {
|
|
||||||
return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ibstr_get_u64(ibstream_t *ib, u64 *out) {
|
|
||||||
return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ibstr_get_i8(ibstream_t *ib, i8 *out) {
|
|
||||||
return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ibstr_get_i16(ibstream_t *ib, i16 *out) {
|
|
||||||
return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ibstr_get_i32(ibstream_t *ib, i32 *out) {
|
|
||||||
return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ibstr_get_i64(ibstream_t *ib, i64 *out) {
|
|
||||||
return ibstr_read(ib, out, sizeof(*out)) == sizeof(*out);
|
|
||||||
}
|
|
||||||
278
str.h
278
str.h
|
|
@ -1,278 +0,0 @@
|
||||||
#ifndef COLLA_STR_H
|
|
||||||
#define COLLA_STR_H
|
|
||||||
|
|
||||||
#include "core.h"
|
|
||||||
#include "darr.h"
|
|
||||||
|
|
||||||
#define STR_NONE SIZE_MAX
|
|
||||||
|
|
||||||
typedef struct str_t str_t;
|
|
||||||
struct str_t {
|
|
||||||
char *buf;
|
|
||||||
usize len;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct str16_t str16_t;
|
|
||||||
struct str16_t {
|
|
||||||
u16 *buf;
|
|
||||||
usize len;
|
|
||||||
};
|
|
||||||
|
|
||||||
#if COLLA_UNICODE
|
|
||||||
typedef str16_t tstr_t;
|
|
||||||
#else
|
|
||||||
typedef str_t tstr_t;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef struct strview_t strview_t;
|
|
||||||
struct strview_t {
|
|
||||||
const char *buf;
|
|
||||||
usize len;
|
|
||||||
};
|
|
||||||
|
|
||||||
darr_define(str_list_t, str_t);
|
|
||||||
darr_define(strv_list_t, strview_t);
|
|
||||||
|
|
||||||
// == STR_T ========================================================
|
|
||||||
|
|
||||||
#define str__1(arena, x) \
|
|
||||||
_Generic((x), \
|
|
||||||
const char *: str_init, \
|
|
||||||
char *: str_init, \
|
|
||||||
strview_t: str_init_view \
|
|
||||||
)(arena, x)
|
|
||||||
|
|
||||||
#define str__2(arena, cstr, clen) str_init_len(arena, cstr, clen)
|
|
||||||
#define str__impl(_1, _2, n, ...) str__##n
|
|
||||||
|
|
||||||
// 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__)
|
|
||||||
|
|
||||||
#define STR_EMPTY (str_t){0}
|
|
||||||
|
|
||||||
str_t str_init(arena_t *arena, const char *buf);
|
|
||||||
str_t str_init_len(arena_t *arena, const char *buf, usize len);
|
|
||||||
str_t str_init_view(arena_t *arena, strview_t view);
|
|
||||||
str_t str_fmt(arena_t *arena, const char *fmt, ...);
|
|
||||||
str_t str_fmtv(arena_t *arena, const char *fmt, va_list args);
|
|
||||||
|
|
||||||
tstr_t tstr_init(TCHAR *str, usize optional_len);
|
|
||||||
str16_t str16_init(u16 *str, usize optional_len);
|
|
||||||
|
|
||||||
str_t str_from_str16(arena_t *arena, str16_t src);
|
|
||||||
str_t str_from_tstr(arena_t *arena, tstr_t src);
|
|
||||||
str16_t str16_from_str(arena_t *arena, str_t src);
|
|
||||||
|
|
||||||
bool str_equals(str_t a, str_t b);
|
|
||||||
int str_compare(str_t a, str_t b);
|
|
||||||
|
|
||||||
str_t str_dup(arena_t *arena, str_t src);
|
|
||||||
str_t str_cat(arena_t *arena, str_t a, str_t b);
|
|
||||||
bool str_is_empty(str_t ctx);
|
|
||||||
|
|
||||||
void str_lower(str_t *src);
|
|
||||||
void str_upper(str_t *src);
|
|
||||||
|
|
||||||
void str_replace(str_t *ctx, char from, char to);
|
|
||||||
// if len == SIZE_MAX, copies until end
|
|
||||||
strview_t str_sub(str_t ctx, usize from, usize to);
|
|
||||||
|
|
||||||
// == STRVIEW_T ====================================================
|
|
||||||
|
|
||||||
// these macros might be THE worst code ever written, but they work ig
|
|
||||||
// detects if you're trying to create a string view from either:
|
|
||||||
// - a str_t -> calls strv_init_str
|
|
||||||
// - a string literal -> calls strv_init_len with comptime size
|
|
||||||
// - a c string -> calls strv_init with runtime size
|
|
||||||
|
|
||||||
#define STRV_EMPTY (strview_t){0}
|
|
||||||
|
|
||||||
// needed for strv__init_literal _Generic implementation, it's never actually called
|
|
||||||
strview_t strv__ignore(str_t s, size_t l);
|
|
||||||
|
|
||||||
#define strv__check(x, ...) ((#x)[0] == '"')
|
|
||||||
#define strv__init_literal(x, ...) \
|
|
||||||
_Generic((x), \
|
|
||||||
char *: strv_init_len, \
|
|
||||||
const char *: strv_init_len, \
|
|
||||||
str_t: strv__ignore \
|
|
||||||
)(x, sizeof(x) - 1)
|
|
||||||
|
|
||||||
#define strv__1(x) \
|
|
||||||
_Generic((x), \
|
|
||||||
char *: strv_init, \
|
|
||||||
const char *: strv_init, \
|
|
||||||
str_t: strv_init_str \
|
|
||||||
)(x)
|
|
||||||
|
|
||||||
#define strv__2(cstr, clen) strv_init_len(cstr, clen)
|
|
||||||
|
|
||||||
#define strv__impl(_1, _2, n, ...) strv__##n
|
|
||||||
|
|
||||||
#define strv(...) strv__check(__VA_ARGS__) ? strv__init_literal(__VA_ARGS__) : strv__impl(__VA_ARGS__, 2, 1, 0)(__VA_ARGS__)
|
|
||||||
|
|
||||||
#define cstrv(cstr) { cstr, sizeof(cstr) - 1, }
|
|
||||||
|
|
||||||
strview_t strv_init(const char *cstr);
|
|
||||||
strview_t strv_init_len(const char *buf, usize size);
|
|
||||||
strview_t strv_init_str(str_t str);
|
|
||||||
|
|
||||||
bool strv_is_empty(strview_t ctx);
|
|
||||||
bool strv_equals(strview_t a, strview_t b);
|
|
||||||
int strv_compare(strview_t a, strview_t b);
|
|
||||||
|
|
||||||
char strv_front(strview_t ctx);
|
|
||||||
char strv_back(strview_t ctx);
|
|
||||||
|
|
||||||
str16_t strv_to_str16(arena_t *arena, strview_t src);
|
|
||||||
tstr_t strv_to_tstr(arena_t *arena, strview_t src);
|
|
||||||
|
|
||||||
str_t strv_to_upper(arena_t *arena, strview_t src);
|
|
||||||
str_t strv_to_lower(arena_t *arena, strview_t src);
|
|
||||||
|
|
||||||
strview_t strv_remove_prefix(strview_t ctx, usize n);
|
|
||||||
strview_t strv_remove_suffix(strview_t ctx, usize n);
|
|
||||||
strview_t strv_trim(strview_t ctx);
|
|
||||||
strview_t strv_trim_left(strview_t ctx);
|
|
||||||
strview_t strv_trim_right(strview_t ctx);
|
|
||||||
|
|
||||||
strview_t strv_sub(strview_t ctx, usize from, usize to);
|
|
||||||
|
|
||||||
bool strv_starts_with(strview_t ctx, char c);
|
|
||||||
bool strv_starts_with_view(strview_t ctx, strview_t view);
|
|
||||||
|
|
||||||
bool strv_ends_with(strview_t ctx, char c);
|
|
||||||
bool strv_ends_with_view(strview_t ctx, strview_t view);
|
|
||||||
|
|
||||||
bool strv_contains(strview_t ctx, char c);
|
|
||||||
bool strv_contains_view(strview_t ctx, strview_t view);
|
|
||||||
bool strv_contains_either(strview_t ctx, strview_t chars);
|
|
||||||
|
|
||||||
usize strv_find(strview_t ctx, char c, usize from);
|
|
||||||
usize strv_find_view(strview_t ctx, strview_t view, usize from);
|
|
||||||
usize strv_find_either(strview_t ctx, strview_t chars, usize from);
|
|
||||||
|
|
||||||
usize strv_rfind(strview_t ctx, char c, usize from_right);
|
|
||||||
usize strv_rfind_view(strview_t ctx, strview_t view, usize from_right);
|
|
||||||
|
|
||||||
// == CTYPE ========================================================
|
|
||||||
|
|
||||||
bool char_is_space(char c);
|
|
||||||
bool char_is_alpha(char c);
|
|
||||||
bool char_is_num(char c);
|
|
||||||
char char_lower(char c);
|
|
||||||
char char_upper(char c);
|
|
||||||
|
|
||||||
// == INPUT STREAM =================================================
|
|
||||||
|
|
||||||
typedef struct instream_t instream_t;
|
|
||||||
struct instream_t {
|
|
||||||
const char *beg;
|
|
||||||
const char *cur;
|
|
||||||
usize len;
|
|
||||||
};
|
|
||||||
|
|
||||||
instream_t istr_init(strview_t str);
|
|
||||||
|
|
||||||
// get the current character and advance
|
|
||||||
char istr_get(instream_t *ctx);
|
|
||||||
// get the current character but don't advance
|
|
||||||
char istr_peek(instream_t *ctx);
|
|
||||||
// get the next character but don't advance
|
|
||||||
char istr_peek_next(instream_t *ctx);
|
|
||||||
// returns the previous character
|
|
||||||
char istr_prev(instream_t *ctx);
|
|
||||||
// returns the character before the previous
|
|
||||||
char istr_prev_prev(instream_t *ctx);
|
|
||||||
// ignore characters until the delimiter
|
|
||||||
void istr_ignore(instream_t *ctx, char delim);
|
|
||||||
// ignore characters until the delimiter and skip it
|
|
||||||
void istr_ignore_and_skip(instream_t *ctx, char delim);
|
|
||||||
// skip n characters
|
|
||||||
void istr_skip(instream_t *ctx, usize n);
|
|
||||||
// skips whitespace (' ', '\\n', '\\t', '\\r')
|
|
||||||
void istr_skip_whitespace(instream_t *ctx);
|
|
||||||
// returns to the beginning of the stream
|
|
||||||
void istr_rewind(instream_t *ctx);
|
|
||||||
// returns back <amount> characters
|
|
||||||
void istr_rewind_n(instream_t *ctx, usize amount);
|
|
||||||
// returns the number of bytes read from beginning of stream
|
|
||||||
usize istr_tell(instream_t *ctx);
|
|
||||||
// returns the number of bytes left to read in the stream
|
|
||||||
usize istr_remaining(instream_t *ctx);
|
|
||||||
// return true if the stream doesn't have any new bytes to read
|
|
||||||
bool istr_is_finished(instream_t *ctx);
|
|
||||||
|
|
||||||
bool istr_get_bool(instream_t *ctx, bool *val);
|
|
||||||
bool istr_get_u8(instream_t *ctx, u8 *val);
|
|
||||||
bool istr_get_u16(instream_t *ctx, u16 *val);
|
|
||||||
bool istr_get_u32(instream_t *ctx, u32 *val);
|
|
||||||
bool istr_get_u64(instream_t *ctx, u64 *val);
|
|
||||||
bool istr_get_i8(instream_t *ctx, i8 *val);
|
|
||||||
bool istr_get_i16(instream_t *ctx, i16 *val);
|
|
||||||
bool istr_get_i32(instream_t *ctx, i32 *val);
|
|
||||||
bool istr_get_i64(instream_t *ctx, i64 *val);
|
|
||||||
bool istr_get_num(instream_t *ctx, double *val);
|
|
||||||
strview_t istr_get_view(instream_t *ctx, char delim);
|
|
||||||
strview_t istr_get_view_either(instream_t *ctx, strview_t chars);
|
|
||||||
strview_t istr_get_view_len(instream_t *ctx, usize len);
|
|
||||||
strview_t istr_get_line(instream_t *ctx);
|
|
||||||
|
|
||||||
// == OUTPUT STREAM ================================================
|
|
||||||
|
|
||||||
typedef struct outstream_t outstream_t;
|
|
||||||
struct outstream_t {
|
|
||||||
char *beg;
|
|
||||||
arena_t *arena;
|
|
||||||
};
|
|
||||||
|
|
||||||
outstream_t ostr_init(arena_t *exclusive_arena);
|
|
||||||
void ostr_clear(outstream_t *ctx);
|
|
||||||
|
|
||||||
usize ostr_tell(outstream_t *ctx);
|
|
||||||
|
|
||||||
char ostr_back(outstream_t *ctx);
|
|
||||||
str_t ostr_to_str(outstream_t *ctx);
|
|
||||||
strview_t ostr_as_view(outstream_t *ctx);
|
|
||||||
|
|
||||||
void ostr_pop(outstream_t *ctx, usize count);
|
|
||||||
|
|
||||||
void ostr_print(outstream_t *ctx, const char *fmt, ...);
|
|
||||||
void ostr_printv(outstream_t *ctx, const char *fmt, va_list args);
|
|
||||||
void ostr_putc(outstream_t *ctx, char c);
|
|
||||||
void ostr_puts(outstream_t *ctx, strview_t v);
|
|
||||||
|
|
||||||
void ostr_append_bool(outstream_t *ctx, bool val);
|
|
||||||
void ostr_append_uint(outstream_t *ctx, u64 val);
|
|
||||||
void ostr_append_int(outstream_t *ctx, i64 val);
|
|
||||||
void ostr_append_num(outstream_t *ctx, double val);
|
|
||||||
|
|
||||||
// == INPUT BINARY STREAM ==========================================
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
const u8 *beg;
|
|
||||||
const u8 *cur;
|
|
||||||
usize len;
|
|
||||||
} ibstream_t;
|
|
||||||
|
|
||||||
ibstream_t ibstr_init(buffer_t buffer);
|
|
||||||
|
|
||||||
bool ibstr_is_finished(ibstream_t *ib);
|
|
||||||
usize ibstr_tell(ibstream_t *ib);
|
|
||||||
usize ibstr_remaining(ibstream_t *ib);
|
|
||||||
usize ibstr_read(ibstream_t *ib, void *buffer, usize len);
|
|
||||||
void ibstr_skip(ibstream_t *ib, usize count);
|
|
||||||
|
|
||||||
bool ibstr_get_u8(ibstream_t *ib, u8 *out);
|
|
||||||
bool ibstr_get_u16(ibstream_t *ib, u16 *out);
|
|
||||||
bool ibstr_get_u32(ibstream_t *ib, u32 *out);
|
|
||||||
bool ibstr_get_u64(ibstream_t *ib, u64 *out);
|
|
||||||
|
|
||||||
bool ibstr_get_i8(ibstream_t *ib, i8 *out);
|
|
||||||
bool ibstr_get_i16(ibstream_t *ib, i16 *out);
|
|
||||||
bool ibstr_get_i32(ibstream_t *ib, i32 *out);
|
|
||||||
bool ibstr_get_i64(ibstream_t *ib, i64 *out);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
LIBRARY
|
|
||||||
|
|
||||||
EXPORTS
|
|
||||||
GetThreadId
|
|
||||||
InitializeConditionVariable
|
|
||||||
WakeConditionVariable
|
|
||||||
WakeAllConditionVariable
|
|
||||||
SleepConditionVariableCS
|
|
||||||
InternetOpen
|
|
||||||
InternetConnect
|
|
||||||
HttpOpenRequest
|
|
||||||
HttpSendRequest
|
|
||||||
|
|
@ -1,237 +0,0 @@
|
||||||
#include "../arena.h"
|
|
||||||
#include "../os.h"
|
|
||||||
#include "../core.h"
|
|
||||||
|
|
||||||
#include "runner.h"
|
|
||||||
|
|
||||||
UNIT_TEST(arena_init_virtual) {
|
|
||||||
arena_t arena = arena_make(ARENA_VIRTUAL, MB(1));
|
|
||||||
ASSERT(arena.type == ARENA_VIRTUAL);
|
|
||||||
ASSERT(arena.beg != NULL);
|
|
||||||
ASSERT(arena.cur == arena.beg);
|
|
||||||
ASSERT(arena.end == arena.beg + MB(1));
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(arena_init_malloc) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
ASSERT(arena.type == ARENA_MALLOC);
|
|
||||||
ASSERT(arena.beg != NULL);
|
|
||||||
ASSERT(arena.cur == arena.beg);
|
|
||||||
ASSERT(arena.end == arena.beg + KB(4));
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(arena_init_malloc_always) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC_ALWAYS, KB(4));
|
|
||||||
ASSERT(arena.type == ARENA_MALLOC_ALWAYS);
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(arena_init_static) {
|
|
||||||
u8 buffer[KB(4)];
|
|
||||||
arena_t arena = arena_make(ARENA_STATIC, KB(4), buffer);
|
|
||||||
ASSERT(arena.type == ARENA_STATIC);
|
|
||||||
ASSERT(arena.beg == buffer);
|
|
||||||
ASSERT(arena.cur == arena.beg);
|
|
||||||
ASSERT(arena.end == arena.beg + KB(4));
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(arena_alloc_basic) {
|
|
||||||
arena_t arena = arena_make(ARENA_VIRTUAL, MB(1));
|
|
||||||
int *ptr = alloc(&arena, int);
|
|
||||||
ASSERT(ptr != NULL);
|
|
||||||
*ptr = 42;
|
|
||||||
ASSERT(*ptr == 42);
|
|
||||||
ASSERT(arena.cur > arena.beg);
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(arena_alloc_array) {
|
|
||||||
arena_t arena = arena_make(ARENA_VIRTUAL, MB(1));
|
|
||||||
int *arr = alloc(&arena, int, .count = 10);
|
|
||||||
ASSERT(arr != NULL);
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
arr[i] = i;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
ASSERT(arr[i] == i);
|
|
||||||
}
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(arena_alloc_custom_align) {
|
|
||||||
arena_t arena = arena_make(ARENA_VIRTUAL, MB(1));
|
|
||||||
void *ptr = alloc(&arena, char, .align = 64);
|
|
||||||
ASSERT(ptr != NULL);
|
|
||||||
ASSERT(((uintptr_t)ptr & 63) == 0); // Should be 64-byte aligned
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(arena_alloc_nozero) {
|
|
||||||
arena_t arena = arena_make(ARENA_VIRTUAL, MB(1));
|
|
||||||
int *ptr1 = alloc(&arena, int);
|
|
||||||
ASSERT(*ptr1 == 0); // Default zeroed
|
|
||||||
|
|
||||||
int *ptr2 = alloc(&arena, int, .flags = ALLOC_NOZERO);
|
|
||||||
// We can't assert on the value as it's uninitialized
|
|
||||||
ASSERT(ptr2 != NULL);
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(arena_alloc_soft_fail) {
|
|
||||||
u8 buffer[10];
|
|
||||||
arena_t arena = arena_make(ARENA_STATIC, 10, buffer);
|
|
||||||
|
|
||||||
void *ptr1 = alloc(&arena, char, .count = 5);
|
|
||||||
ASSERT(ptr1 != NULL);
|
|
||||||
|
|
||||||
// This would normally fail, but with SOFT_FAIL it returns NULL
|
|
||||||
void *ptr2 = alloc(&arena, char, .count = 10, .flags = ALLOC_SOFT_FAIL);
|
|
||||||
ASSERT(ptr2 == NULL);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(arena_scratch) {
|
|
||||||
arena_t arena = arena_make(ARENA_VIRTUAL, MB(1));
|
|
||||||
arena_t scratch = arena_scratch(&arena, KB(1));
|
|
||||||
|
|
||||||
ASSERT(scratch.beg != NULL);
|
|
||||||
ASSERT(scratch.type == ARENA_STATIC);
|
|
||||||
|
|
||||||
void *ptr = alloc(&scratch, int);
|
|
||||||
ASSERT(ptr != NULL);
|
|
||||||
|
|
||||||
// Scratch cleanup happens implicitly when parent arena is cleaned up
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(arena_tell) {
|
|
||||||
arena_t arena = arena_make(ARENA_VIRTUAL, MB(1));
|
|
||||||
usize pos1 = arena_tell(&arena);
|
|
||||||
ASSERT(pos1 == 0);
|
|
||||||
|
|
||||||
alloc(&arena, int);
|
|
||||||
usize pos2 = arena_tell(&arena);
|
|
||||||
ASSERT(pos2 > pos1);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(arena_remaining) {
|
|
||||||
arena_t arena = arena_make(ARENA_VIRTUAL, KB(64));
|
|
||||||
usize initial_remaining = arena_remaining(&arena);
|
|
||||||
ASSERT(initial_remaining == KB(64));
|
|
||||||
|
|
||||||
alloc(&arena, char, .count = KB(4));
|
|
||||||
usize after_alloc = arena_remaining(&arena);
|
|
||||||
ASSERT(after_alloc < initial_remaining);
|
|
||||||
ASSERT(after_alloc >= KB(60)); // Account for possible alignment padding
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(arena_capacity) {
|
|
||||||
arena_t arena = arena_make(ARENA_VIRTUAL, KB(64));
|
|
||||||
usize cap = arena_capacity(&arena);
|
|
||||||
ASSERT(cap == KB(64));
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(arena_rewind) {
|
|
||||||
arena_t arena = arena_make(ARENA_VIRTUAL, MB(1));
|
|
||||||
|
|
||||||
usize mark = arena_tell(&arena);
|
|
||||||
|
|
||||||
int *ptr1 = alloc(&arena, int);
|
|
||||||
*ptr1 = 42;
|
|
||||||
|
|
||||||
alloc(&arena, char, .count = 100);
|
|
||||||
|
|
||||||
arena_rewind(&arena, mark);
|
|
||||||
|
|
||||||
int *ptr2 = alloc(&arena, int);
|
|
||||||
ASSERT(ptr2 == ptr1); // Should reuse the same memory
|
|
||||||
|
|
||||||
// Original value is lost after rewind
|
|
||||||
*ptr2 = 24;
|
|
||||||
ASSERT(*ptr2 == 24);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(arena_pop) {
|
|
||||||
arena_t arena = arena_make(ARENA_VIRTUAL, MB(1));
|
|
||||||
|
|
||||||
alloc(&arena, char, .count = 100);
|
|
||||||
usize pos = arena_tell(&arena);
|
|
||||||
|
|
||||||
alloc(&arena, char, .count = 50);
|
|
||||||
|
|
||||||
arena_pop(&arena, 50);
|
|
||||||
ASSERT(arena_tell(&arena) == pos);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(arena_malloc_arena) {
|
|
||||||
void *ptr = alloc(&malloc_arena, int);
|
|
||||||
ASSERT(ptr != NULL);
|
|
||||||
|
|
||||||
// We need to free each allocation from malloc_arena manually
|
|
||||||
os_free(ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(arena_alloc_mixed_types) {
|
|
||||||
arena_t arena = arena_make(ARENA_VIRTUAL, MB(1));
|
|
||||||
|
|
||||||
int *i = alloc(&arena, int);
|
|
||||||
float *f = alloc(&arena, float);
|
|
||||||
char *c = alloc(&arena, char);
|
|
||||||
|
|
||||||
*i = 42;
|
|
||||||
*f = 3.14f;
|
|
||||||
*c = 'A';
|
|
||||||
|
|
||||||
ASSERT(*i == 42);
|
|
||||||
ASSERT(*f == 3.14f);
|
|
||||||
ASSERT(*c == 'A');
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(arena_multiple_arenas) {
|
|
||||||
arena_t arena1 = arena_make(ARENA_VIRTUAL, KB(4));
|
|
||||||
arena_t arena2 = arena_make(ARENA_VIRTUAL, KB(4));
|
|
||||||
|
|
||||||
int *ptr1 = alloc(&arena1, int);
|
|
||||||
int *ptr2 = alloc(&arena2, int);
|
|
||||||
|
|
||||||
*ptr1 = 42;
|
|
||||||
*ptr2 = 24;
|
|
||||||
|
|
||||||
ASSERT(*ptr1 == 42);
|
|
||||||
ASSERT(*ptr2 == 24);
|
|
||||||
|
|
||||||
arena_cleanup(&arena1);
|
|
||||||
arena_cleanup(&arena2);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(arena_stress_test) {
|
|
||||||
arena_t arena = arena_make(ARENA_VIRTUAL, MB(10));
|
|
||||||
|
|
||||||
// Allocate many objects
|
|
||||||
for (int i = 0; i < 1000; i++) {
|
|
||||||
int *ptr = alloc(&arena, int);
|
|
||||||
ASSERT(ptr != NULL);
|
|
||||||
*ptr = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate a large block
|
|
||||||
void *large = alloc(&arena, char, .count = MB(5));
|
|
||||||
ASSERT(large != NULL);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
@ -1,229 +0,0 @@
|
||||||
#include "runner.h"
|
|
||||||
#include "../core.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
UNIT_TEST(arrlen_macro) {
|
|
||||||
int array[5] = {1, 2, 3, 4, 5};
|
|
||||||
ASSERT(arrlen(array) == 5);
|
|
||||||
|
|
||||||
char str[] = "hello";
|
|
||||||
ASSERT(arrlen(str) == 6); // Including null terminator
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(min_max_macros) {
|
|
||||||
ASSERT(MIN(5, 10) == 5);
|
|
||||||
ASSERT(MIN(-5, 10) == -5);
|
|
||||||
ASSERT(MIN(5.5, 10.1) == 5.5);
|
|
||||||
|
|
||||||
ASSERT(MAX(5, 10) == 10);
|
|
||||||
ASSERT(MAX(-5, 10) == 10);
|
|
||||||
ASSERT(MAX(5.5, 10.1) == 10.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(size_constants) {
|
|
||||||
ASSERT(KB(1) == 1024);
|
|
||||||
ASSERT(MB(1) == 1024 * 1024);
|
|
||||||
ASSERT(GB(1) == 1024 * 1024 * 1024);
|
|
||||||
ASSERT(TB(1) == 1024ULL * 1024ULL * 1024ULL * 1024ULL);
|
|
||||||
|
|
||||||
ASSERT(KB(2) == 2048);
|
|
||||||
ASSERT(MB(2) == 2 * 1024 * 1024);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(linked_list) {
|
|
||||||
// Define a simple node structure
|
|
||||||
typedef struct Node {
|
|
||||||
int value;
|
|
||||||
struct Node *next;
|
|
||||||
} Node;
|
|
||||||
|
|
||||||
// Create some nodes
|
|
||||||
Node n1 = {1, NULL};
|
|
||||||
Node n2 = {2, NULL};
|
|
||||||
Node n3 = {3, NULL};
|
|
||||||
|
|
||||||
// Initialize list
|
|
||||||
Node *list = NULL;
|
|
||||||
|
|
||||||
// Push nodes onto list
|
|
||||||
list_push(list, &n3);
|
|
||||||
list_push(list, &n2);
|
|
||||||
list_push(list, &n1);
|
|
||||||
|
|
||||||
// Check list order
|
|
||||||
ASSERT(list == &n1);
|
|
||||||
ASSERT(list->next == &n2);
|
|
||||||
ASSERT(list->next->next == &n3);
|
|
||||||
ASSERT(list->next->next->next == NULL);
|
|
||||||
|
|
||||||
// Pop from list
|
|
||||||
list_pop(list);
|
|
||||||
ASSERT(list == &n2);
|
|
||||||
ASSERT(list->next == &n3);
|
|
||||||
|
|
||||||
list_pop(list);
|
|
||||||
ASSERT(list == &n3);
|
|
||||||
|
|
||||||
list_pop(list);
|
|
||||||
ASSERT(list == NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(double_linked_list) {
|
|
||||||
// Define a double linked node
|
|
||||||
typedef struct DNode {
|
|
||||||
int value;
|
|
||||||
struct DNode *next;
|
|
||||||
struct DNode *prev;
|
|
||||||
} DNode;
|
|
||||||
|
|
||||||
// Create some nodes
|
|
||||||
DNode n1 = {1, NULL, NULL};
|
|
||||||
DNode n2 = {2, NULL, NULL};
|
|
||||||
DNode n3 = {3, NULL, NULL};
|
|
||||||
|
|
||||||
// Initialize list
|
|
||||||
DNode *list = NULL;
|
|
||||||
|
|
||||||
// Push nodes
|
|
||||||
dlist_push(list, &n3);
|
|
||||||
dlist_push(list, &n2);
|
|
||||||
dlist_push(list, &n1);
|
|
||||||
|
|
||||||
// Check list structure
|
|
||||||
ASSERT(list == &n1);
|
|
||||||
ASSERT(list->next == &n2);
|
|
||||||
ASSERT(list->next->next == &n3);
|
|
||||||
ASSERT(list->prev == NULL);
|
|
||||||
ASSERT(list->next->prev == &n1);
|
|
||||||
ASSERT(list->next->next->prev == &n2);
|
|
||||||
|
|
||||||
// Pop middle node
|
|
||||||
dlist_pop(list, &n2);
|
|
||||||
|
|
||||||
// Check updated structure
|
|
||||||
ASSERT(list == &n1);
|
|
||||||
ASSERT(list->next == &n3);
|
|
||||||
ASSERT(list->next->prev == &n1);
|
|
||||||
|
|
||||||
// Pop first node
|
|
||||||
dlist_pop(list, &n1);
|
|
||||||
ASSERT(list == &n3);
|
|
||||||
ASSERT(list->prev == NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(ordered_linked_list) {
|
|
||||||
// Define a simple node
|
|
||||||
typedef struct ONode {
|
|
||||||
int value;
|
|
||||||
struct ONode *next;
|
|
||||||
} ONode;
|
|
||||||
|
|
||||||
// Create nodes
|
|
||||||
ONode n1 = {1, NULL};
|
|
||||||
ONode n2 = {2, NULL};
|
|
||||||
ONode n3 = {3, NULL};
|
|
||||||
|
|
||||||
// Initialize head and tail
|
|
||||||
ONode *head = NULL;
|
|
||||||
ONode *tail = NULL;
|
|
||||||
|
|
||||||
// Push nodes in order
|
|
||||||
olist_push(head, tail, &n1);
|
|
||||||
ASSERT(head == &n1);
|
|
||||||
ASSERT(tail == &n1);
|
|
||||||
|
|
||||||
olist_push(head, tail, &n2);
|
|
||||||
ASSERT(head == &n1);
|
|
||||||
ASSERT(tail == &n2);
|
|
||||||
ASSERT(head->next == &n2);
|
|
||||||
|
|
||||||
olist_push(head, tail, &n3);
|
|
||||||
ASSERT(head == &n1);
|
|
||||||
ASSERT(tail == &n3);
|
|
||||||
ASSERT(head->next == &n2);
|
|
||||||
ASSERT(head->next->next == &n3);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(for_each_macro) {
|
|
||||||
// Define a simple node
|
|
||||||
typedef struct Node {
|
|
||||||
int value;
|
|
||||||
struct Node *next;
|
|
||||||
} Node;
|
|
||||||
|
|
||||||
// Create linked list
|
|
||||||
Node n1 = {1, NULL};
|
|
||||||
Node n2 = {2, NULL};
|
|
||||||
Node n3 = {3, NULL};
|
|
||||||
|
|
||||||
n1.next = &n2;
|
|
||||||
n2.next = &n3;
|
|
||||||
|
|
||||||
Node *list = &n1;
|
|
||||||
|
|
||||||
// Use for_each to sum values
|
|
||||||
int sum = 0;
|
|
||||||
for_each(it, list) {
|
|
||||||
sum += it->value;
|
|
||||||
}
|
|
||||||
|
|
||||||
ASSERT(sum == 6); // 1 + 2 + 3
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(fmt_print) {
|
|
||||||
// This function outputs to stdout, so we can't easily test its output
|
|
||||||
// Just verify it doesn't crash and returns a positive value
|
|
||||||
int result = fmt_print("Test %d %s\n", 42, "hello");
|
|
||||||
ASSERT(result > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(fmt_buffer) {
|
|
||||||
char buffer[128];
|
|
||||||
|
|
||||||
// Basic formatting
|
|
||||||
int result = fmt_buffer(buffer, sizeof(buffer), "Int: %d", 42);
|
|
||||||
ASSERT(result > 0);
|
|
||||||
ASSERT(strcmp(buffer, "Int: 42") == 0);
|
|
||||||
|
|
||||||
// Multiple arguments
|
|
||||||
result = fmt_buffer(buffer, sizeof(buffer), "%s %d %.2f", "Test", 123, 3.14159);
|
|
||||||
ASSERT(result > 0);
|
|
||||||
ASSERT(strcmp(buffer, "Test 123 3.14") == 0);
|
|
||||||
|
|
||||||
// Buffer size limiting
|
|
||||||
result = fmt_buffer(buffer, 5, "Long text that won't fit");
|
|
||||||
ASSERT(result == 24); // fmt_buffer returns the lenght if it did fit
|
|
||||||
ASSERT(strlen(buffer) == 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to test variadic function
|
|
||||||
int test_fmt_printv(const char *fmt, ...) {
|
|
||||||
va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
int result = fmt_printv(fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(fmt_printv) {
|
|
||||||
// Just verify it doesn't crash and returns positive value
|
|
||||||
int result = test_fmt_printv("Test %d %s\n", 42, "hello");
|
|
||||||
ASSERT(result > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to test variadic function
|
|
||||||
int test_fmt_bufferv(char *buf, usize len, const char *fmt, ...) {
|
|
||||||
va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
int result = fmt_bufferv(buf, len, fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(fmt_bufferv) {
|
|
||||||
char buffer[128];
|
|
||||||
int result = test_fmt_bufferv(buffer, sizeof(buffer), "%d %s", 42, "test");
|
|
||||||
ASSERT(result > 0);
|
|
||||||
ASSERT(strcmp(buffer, "42 test") == 0);
|
|
||||||
}
|
|
||||||
|
|
@ -1,179 +0,0 @@
|
||||||
#include "runner.h"
|
|
||||||
#include "../highlight.h"
|
|
||||||
#include "../arena.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
UNIT_TEST(highlight_init) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
// Create a basic configuration
|
|
||||||
hl_config_t config = {0};
|
|
||||||
|
|
||||||
// Define custom colors
|
|
||||||
config.colors[HL_COLOR_NORMAL] = strv_init("normal");
|
|
||||||
config.colors[HL_COLOR_KEYWORDS] = strv_init("keyword");
|
|
||||||
config.colors[HL_COLOR_STRING] = strv_init("string");
|
|
||||||
config.colors[HL_COLOR_COMMENT] = strv_init("comment");
|
|
||||||
|
|
||||||
// Initialize highlighter
|
|
||||||
hl_ctx_t *ctx = hl_init(&arena, &config);
|
|
||||||
ASSERT(ctx != NULL);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(highlight_basic) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
// Create a configuration
|
|
||||||
hl_config_t config = {0};
|
|
||||||
|
|
||||||
// Define custom colors for HTML output
|
|
||||||
config.colors[HL_COLOR_NORMAL] = strv_init("color:black");
|
|
||||||
config.colors[HL_COLOR_KEYWORDS] = strv_init("color:blue");
|
|
||||||
config.colors[HL_COLOR_STRING] = strv_init("color:green");
|
|
||||||
config.colors[HL_COLOR_COMMENT] = strv_init("color:gray");
|
|
||||||
config.colors[HL_COLOR_NUMBER] = strv_init("color:purple");
|
|
||||||
|
|
||||||
// Set HTML output flag
|
|
||||||
config.flags = HL_FLAG_HTML;
|
|
||||||
|
|
||||||
// Initialize highlighter
|
|
||||||
hl_ctx_t *ctx = hl_init(&arena, &config);
|
|
||||||
ASSERT(ctx != NULL);
|
|
||||||
|
|
||||||
// Sample C code to highlight
|
|
||||||
strview_t code = strv_init(
|
|
||||||
"// This is a comment\n"
|
|
||||||
"int main() {\n"
|
|
||||||
" printf(\"Hello, World!\\n\");\n"
|
|
||||||
" return 0;\n"
|
|
||||||
"}\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Highlight the code
|
|
||||||
str_t highlighted = hl_highlight(&arena, ctx, code);
|
|
||||||
|
|
||||||
// Verify the output
|
|
||||||
ASSERT(!str_is_empty(highlighted));
|
|
||||||
|
|
||||||
// We can't easily test the exact output without parsing HTML,
|
|
||||||
// but we can check that key strings are present
|
|
||||||
const char *html_start = "<span";
|
|
||||||
ASSERT(strstr(highlighted.buf, html_start) != NULL);
|
|
||||||
|
|
||||||
// Check if comment coloring is present
|
|
||||||
ASSERT(strstr(highlighted.buf, "// This is a comment") != NULL);
|
|
||||||
|
|
||||||
// Check if string highlighting is present
|
|
||||||
ASSERT(strstr(highlighted.buf, "Hello, World!") != NULL);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(highlight_custom_keywords) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
// Create configuration
|
|
||||||
hl_config_t config = {0};
|
|
||||||
|
|
||||||
// Define colors
|
|
||||||
config.colors[HL_COLOR_NORMAL] = strv_init("normal");
|
|
||||||
config.colors[HL_COLOR_KEYWORDS] = strv_init("keyword");
|
|
||||||
config.colors[HL_COLOR_CUSTOM_TYPES] = strv_init("custom-type");
|
|
||||||
|
|
||||||
// Define custom keywords
|
|
||||||
hl_keyword_t custom_keywords[] = {
|
|
||||||
{.keyword = strv_init("MyClass"), .color = HL_COLOR_CUSTOM_TYPES},
|
|
||||||
{.keyword = strv_init("custom_func"), .color = HL_COLOR_FUNC}
|
|
||||||
};
|
|
||||||
|
|
||||||
config.extra_kwrds = custom_keywords;
|
|
||||||
config.kwrds_count = 2;
|
|
||||||
|
|
||||||
// Initialize highlighter
|
|
||||||
hl_ctx_t *ctx = hl_init(&arena, &config);
|
|
||||||
ASSERT(ctx != NULL);
|
|
||||||
|
|
||||||
// Test code with custom keywords
|
|
||||||
strview_t code = strv_init(
|
|
||||||
"MyClass obj;\n"
|
|
||||||
"custom_func(obj);\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Highlight the code
|
|
||||||
str_t highlighted = hl_highlight(&arena, ctx, code);
|
|
||||||
|
|
||||||
// Verify output contains our code
|
|
||||||
ASSERT(!str_is_empty(highlighted));
|
|
||||||
ASSERT(strstr(highlighted.buf, "MyClass") != NULL);
|
|
||||||
ASSERT(strstr(highlighted.buf, "custom_func") != NULL);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(highlight_add_keyword) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
// Create configuration
|
|
||||||
hl_config_t config = {0};
|
|
||||||
|
|
||||||
// Define colors
|
|
||||||
config.colors[HL_COLOR_NORMAL] = strv_init("normal");
|
|
||||||
config.colors[HL_COLOR_KEYWORDS] = strv_init("keyword");
|
|
||||||
config.colors[HL_COLOR_CUSTOM_TYPES] = strv_init("custom-type");
|
|
||||||
|
|
||||||
// Initialize highlighter
|
|
||||||
hl_ctx_t *ctx = hl_init(&arena, &config);
|
|
||||||
ASSERT(ctx != NULL);
|
|
||||||
|
|
||||||
// Add a custom keyword after initialization
|
|
||||||
hl_keyword_t new_keyword = {
|
|
||||||
.keyword = strv_init("NewKeyword"),
|
|
||||||
.color = HL_COLOR_CUSTOM_TYPES
|
|
||||||
};
|
|
||||||
|
|
||||||
hl_add_keyword(&arena, ctx, &new_keyword);
|
|
||||||
|
|
||||||
// Test code with the new keyword
|
|
||||||
strview_t code = strv_init("NewKeyword x;\n");
|
|
||||||
|
|
||||||
// Highlight the code
|
|
||||||
str_t highlighted = hl_highlight(&arena, ctx, code);
|
|
||||||
|
|
||||||
// Verify output contains our code
|
|
||||||
ASSERT(!str_is_empty(highlighted));
|
|
||||||
ASSERT(strstr(highlighted.buf, "NewKeyword") != NULL);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(highlight_symbol_table) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
// Create configuration
|
|
||||||
hl_config_t config = {0};
|
|
||||||
|
|
||||||
// Define colors
|
|
||||||
config.colors[HL_COLOR_NORMAL] = strv_init("normal");
|
|
||||||
config.colors[HL_COLOR_SYMBOL] = strv_init("symbol");
|
|
||||||
|
|
||||||
// Initialize highlighter
|
|
||||||
hl_ctx_t *ctx = hl_init(&arena, &config);
|
|
||||||
ASSERT(ctx != NULL);
|
|
||||||
|
|
||||||
// Set '@' as a symbol
|
|
||||||
hl_set_symbol_in_table(ctx, '@', true);
|
|
||||||
|
|
||||||
// Test code with the symbol
|
|
||||||
strview_t code = strv_init("@decorator\n");
|
|
||||||
|
|
||||||
// Highlight the code
|
|
||||||
str_t highlighted = hl_highlight(&arena, ctx, code);
|
|
||||||
|
|
||||||
// Verify output contains our code
|
|
||||||
ASSERT(!str_is_empty(highlighted));
|
|
||||||
ASSERT(strstr(highlighted.buf, "@") != NULL);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
@ -1,238 +0,0 @@
|
||||||
#include "runner.h"
|
|
||||||
#include "../net.h"
|
|
||||||
#include "../arena.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
// Initialization Tests
|
|
||||||
UNIT_TEST(net_init_cleanup) {
|
|
||||||
net_init();
|
|
||||||
// Simple test to make sure initialization succeeds
|
|
||||||
ASSERT(net_get_last_error() == 0);
|
|
||||||
net_cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP Method/Status Tests
|
|
||||||
UNIT_TEST(http_method_strings) {
|
|
||||||
ASSERT(strcmp(http_get_method_string(HTTP_GET), "GET") == 0);
|
|
||||||
ASSERT(strcmp(http_get_method_string(HTTP_POST), "POST") == 0);
|
|
||||||
ASSERT(strcmp(http_get_method_string(HTTP_HEAD), "HEAD") == 0);
|
|
||||||
ASSERT(strcmp(http_get_method_string(HTTP_PUT), "PUT") == 0);
|
|
||||||
ASSERT(strcmp(http_get_method_string(HTTP_DELETE), "DELETE") == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(http_status_strings) {
|
|
||||||
ASSERT(strcmp(http_get_status_string(200), "OK") == 0);
|
|
||||||
ASSERT(strcmp(http_get_status_string(404), "NOT FOUND") == 0);
|
|
||||||
ASSERT(strcmp(http_get_status_string(500), "INTERNAL SERVER ERROR") == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP Headers Tests
|
|
||||||
UNIT_TEST(http_headers) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
// Parse headers
|
|
||||||
strview_t header_str = strv_init("Content-Type: application/json\r\nUser-Agent: Colla\r\n");
|
|
||||||
http_header_t *headers = http_parse_headers(&arena, header_str);
|
|
||||||
|
|
||||||
// Check headers were parsed correctly
|
|
||||||
ASSERT(headers != NULL);
|
|
||||||
|
|
||||||
// Headers are parsed in reverse order
|
|
||||||
ASSERT(strv_equals(headers->key, strv_init("User-Agent")));
|
|
||||||
ASSERT(strv_equals(headers->value, strv_init("Colla")));
|
|
||||||
ASSERT(strv_equals(headers->next->key, strv_init("Content-Type")));
|
|
||||||
ASSERT(strv_equals(headers->next->value, strv_init("application/json")));
|
|
||||||
|
|
||||||
// Test header operations
|
|
||||||
ASSERT(http_has_header(headers, strv_init("Content-Type")));
|
|
||||||
ASSERT(!http_has_header(headers, strv_init("Accept")));
|
|
||||||
|
|
||||||
strview_t content_type = http_get_header(headers, strv_init("Content-Type"));
|
|
||||||
ASSERT(strv_equals(content_type, strv_init("application/json")));
|
|
||||||
|
|
||||||
// Don't try to free headers as they're allocated in the arena
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP Request/Response Parsing Tests
|
|
||||||
UNIT_TEST(http_request_parsing) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
strview_t req_str = strv_init(
|
|
||||||
"GET /index.html HTTP/1.1\r\n"
|
|
||||||
"Host: example.com\r\n"
|
|
||||||
"User-Agent: Colla\r\n"
|
|
||||||
"\r\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
http_req_t req = http_parse_req(&arena, req_str);
|
|
||||||
|
|
||||||
ASSERT(req.method == HTTP_GET);
|
|
||||||
ASSERT(req.version.major == 1);
|
|
||||||
ASSERT(req.version.minor == 1);
|
|
||||||
ASSERT(strv_equals(req.url, strv("index.html")));
|
|
||||||
|
|
||||||
ASSERT(http_has_header(req.headers, strv_init("Host")));
|
|
||||||
ASSERT(strv_equals(http_get_header(req.headers, strv_init("Host")), strv_init("example.com")));
|
|
||||||
|
|
||||||
// Convert back to string
|
|
||||||
str_t req_out = http_req_to_str(&arena, &req);
|
|
||||||
ASSERT(!str_is_empty(req_out));
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(http_response_parsing) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
strview_t res_str = strv_init(
|
|
||||||
"HTTP/1.1 200 OK\r\n"
|
|
||||||
"Content-Type: text/html\r\n"
|
|
||||||
"Content-Length: 13\r\n"
|
|
||||||
"\r\n"
|
|
||||||
"Hello, World!"
|
|
||||||
);
|
|
||||||
|
|
||||||
http_res_t res = http_parse_res(&arena, res_str);
|
|
||||||
|
|
||||||
ASSERT(res.status_code == 200);
|
|
||||||
ASSERT(res.version.major == 1);
|
|
||||||
ASSERT(res.version.minor == 1);
|
|
||||||
ASSERT(strv_equals(res.body, strv_init("Hello, World!")));
|
|
||||||
|
|
||||||
ASSERT(http_has_header(res.headers, strv_init("Content-Type")));
|
|
||||||
ASSERT(strv_equals(http_get_header(res.headers, strv_init("Content-Type")), strv_init("text/html")));
|
|
||||||
|
|
||||||
// Convert back to string
|
|
||||||
str_t res_out = http_res_to_str(&arena, &res);
|
|
||||||
ASSERT(!str_is_empty(res_out));
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
// URL Encoding/Decoding Tests
|
|
||||||
UNIT_TEST(http_url_encoding) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
strview_t original = strv_init("hello world & special chars: ?=&/");
|
|
||||||
str_t encoded = http_make_url_safe(&arena, original);
|
|
||||||
str_t decoded = http_decode_url_safe(&arena, strv_init_str(encoded));
|
|
||||||
|
|
||||||
ASSERT(!str_is_empty(encoded));
|
|
||||||
ASSERT(str_equals(decoded, str_init(&arena, "hello world & special chars: ?=&/")));
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(http_url_splitting) {
|
|
||||||
strview_t url = strv_init("http://example.com/path?query=value");
|
|
||||||
http_url_t split = http_split_url(url);
|
|
||||||
|
|
||||||
ASSERT(strv_equals(split.host, strv_init("example.com")));
|
|
||||||
ASSERT(strv_equals(split.uri, strv_init("/path?query=value")));
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP Request Tests
|
|
||||||
// Note: These tests would actually make network requests, so we should mock them
|
|
||||||
// for real unit tests. Here we'll just test the setup part.
|
|
||||||
UNIT_TEST(http_request_setup) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
// Prepare headers
|
|
||||||
http_header_t headers[2] = {
|
|
||||||
{ .key = strv_init("Content-Type"), .value = strv_init("application/json"), .next = &headers[1] },
|
|
||||||
{ .key = strv_init("User-Agent"), .value = strv_init("Colla Test"), .next = NULL }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Setup request descriptor
|
|
||||||
http_request_desc_t desc = {
|
|
||||||
.arena = &arena,
|
|
||||||
.url = strv_init("http://example.com"),
|
|
||||||
.version = { .major = 1, .minor = 1 },
|
|
||||||
.request_type = HTTP_GET,
|
|
||||||
.headers = headers,
|
|
||||||
.header_count = 2,
|
|
||||||
.body = strv_init("")
|
|
||||||
};
|
|
||||||
|
|
||||||
// We don't actually make the request, just verify the setup is correct
|
|
||||||
ASSERT(desc.arena == &arena);
|
|
||||||
ASSERT(strv_equals(desc.url, strv_init("http://example.com")));
|
|
||||||
ASSERT(desc.request_type == HTTP_GET);
|
|
||||||
ASSERT(desc.headers == headers);
|
|
||||||
ASSERT(desc.header_count == 2);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Socket Tests
|
|
||||||
UNIT_TEST(socket_basic) {
|
|
||||||
net_init();
|
|
||||||
|
|
||||||
// Open a socket
|
|
||||||
socket_t sock = sk_open(SOCK_TCP);
|
|
||||||
ASSERT(sk_is_valid(sock));
|
|
||||||
|
|
||||||
// Close the socket
|
|
||||||
ASSERT(sk_close(sock));
|
|
||||||
|
|
||||||
net_cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
// SHA1 Tests
|
|
||||||
UNIT_TEST(sha1_hash) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
sha1_t ctx = sha1_init();
|
|
||||||
|
|
||||||
const char *data = "Hello, World!";
|
|
||||||
str_t hash = sha1_str(&arena, &ctx, data, strlen(data));
|
|
||||||
|
|
||||||
// The SHA1 hash for "Hello, World!" is known
|
|
||||||
// But we'll just verify it's not empty and has the expected format (40 hex chars)
|
|
||||||
ASSERT(!str_is_empty(hash));
|
|
||||||
ASSERT(hash.len == 40);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Base64 Tests
|
|
||||||
UNIT_TEST(base64_encoding) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
const char *original = "Hello, World!";
|
|
||||||
buffer_t input = { (u8*)original, strlen(original) };
|
|
||||||
|
|
||||||
// Encode
|
|
||||||
buffer_t encoded = base64_encode(&arena, input);
|
|
||||||
ASSERT(encoded.data != NULL);
|
|
||||||
ASSERT(encoded.len > 0);
|
|
||||||
|
|
||||||
// Decode
|
|
||||||
buffer_t decoded = base64_decode(&arena, encoded);
|
|
||||||
|
|
||||||
ASSERT(decoded.data != NULL);
|
|
||||||
ASSERT(decoded.len == input.len);
|
|
||||||
ASSERT(memcmp(decoded.data, input.data, input.len) == 0);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebSocket Tests
|
|
||||||
UNIT_TEST(websocket_encoding) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
strview_t message = strv_init("Hello, WebSocket!");
|
|
||||||
|
|
||||||
// Encode message to WebSocket format
|
|
||||||
buffer_t encoded = websocket_encode(&arena, message);
|
|
||||||
ASSERT(encoded.data != NULL);
|
|
||||||
ASSERT(encoded.len > 0);
|
|
||||||
|
|
||||||
// Decode WebSocket message
|
|
||||||
str_t decoded = websocket_decode(&arena, encoded);
|
|
||||||
ASSERT(!str_is_empty(decoded));
|
|
||||||
ASSERT(str_equals(decoded, str_init(&arena, "Hello, WebSocket!")));
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
374
tests/os_tests.c
374
tests/os_tests.c
|
|
@ -1,374 +0,0 @@
|
||||||
#include "runner.h"
|
|
||||||
#include "../os.h"
|
|
||||||
#include "../arena.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
// Handle Tests
|
|
||||||
UNIT_TEST(os_handle) {
|
|
||||||
oshandle_t zero = os_handle_zero();
|
|
||||||
ASSERT(!os_handle_valid(zero));
|
|
||||||
|
|
||||||
// Create a handle (using file open)
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
strview_t test_file = strv_init("test_file.txt");
|
|
||||||
|
|
||||||
// Create test file
|
|
||||||
oshandle_t h_write = os_file_open(test_file, FILEMODE_WRITE);
|
|
||||||
ASSERT(os_handle_valid(h_write));
|
|
||||||
os_file_puts(h_write, strv_init("test content"));
|
|
||||||
os_file_close(h_write);
|
|
||||||
|
|
||||||
// Open the file and test handle functions
|
|
||||||
oshandle_t h_read = os_file_open(test_file, FILEMODE_READ);
|
|
||||||
ASSERT(os_handle_valid(h_read));
|
|
||||||
ASSERT(!os_handle_match(h_read, zero));
|
|
||||||
|
|
||||||
oshandle_t h_read2 = os_file_open(test_file, FILEMODE_READ);
|
|
||||||
ASSERT(os_handle_valid(h_read2));
|
|
||||||
ASSERT(!os_handle_match(h_read, h_read2));
|
|
||||||
|
|
||||||
os_file_close(h_read);
|
|
||||||
os_file_close(h_read2);
|
|
||||||
os_file_delete(test_file);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
// File Operations Tests
|
|
||||||
UNIT_TEST(os_file_basic) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
strview_t test_file = strv_init("test_file.txt");
|
|
||||||
|
|
||||||
// Delete if exists
|
|
||||||
if (os_file_exists(test_file)) {
|
|
||||||
os_file_delete(test_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check existence
|
|
||||||
ASSERT(!os_file_exists(test_file));
|
|
||||||
|
|
||||||
// Create and write
|
|
||||||
oshandle_t h_write = os_file_open(test_file, FILEMODE_WRITE);
|
|
||||||
ASSERT(os_handle_valid(h_write));
|
|
||||||
|
|
||||||
os_file_putc(h_write, 'H');
|
|
||||||
os_file_puts(h_write, strv_init("ello World"));
|
|
||||||
|
|
||||||
os_file_close(h_write);
|
|
||||||
|
|
||||||
// Check existence after creation
|
|
||||||
ASSERT(os_file_exists(test_file));
|
|
||||||
|
|
||||||
// Read back
|
|
||||||
oshandle_t h_read = os_file_open(test_file, FILEMODE_READ);
|
|
||||||
ASSERT(os_handle_valid(h_read));
|
|
||||||
|
|
||||||
char buffer[12] = {0};
|
|
||||||
usize read = os_file_read(h_read, buffer, 11);
|
|
||||||
ASSERT(read == 11);
|
|
||||||
ASSERT(strcmp(buffer, "Hello World") == 0);
|
|
||||||
|
|
||||||
os_file_close(h_read);
|
|
||||||
|
|
||||||
// Clean up
|
|
||||||
os_file_delete(test_file);
|
|
||||||
ASSERT(!os_file_exists(test_file));
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(os_file_seek) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
strview_t test_file = strv_init("test_file.txt");
|
|
||||||
|
|
||||||
// Create and write
|
|
||||||
oshandle_t h_write = os_file_open(test_file, FILEMODE_WRITE);
|
|
||||||
ASSERT(os_handle_valid(h_write));
|
|
||||||
|
|
||||||
os_file_puts(h_write, strv_init("ABCDEFGHIJ"));
|
|
||||||
os_file_close(h_write);
|
|
||||||
|
|
||||||
// Open for reading
|
|
||||||
oshandle_t h_read = os_file_open(test_file, FILEMODE_READ);
|
|
||||||
ASSERT(os_handle_valid(h_read));
|
|
||||||
|
|
||||||
// Seek to position 5
|
|
||||||
ASSERT(os_file_seek(h_read, 5));
|
|
||||||
|
|
||||||
// Read from position 5
|
|
||||||
char buffer[6] = {0};
|
|
||||||
usize read = os_file_read(h_read, buffer, 5);
|
|
||||||
ASSERT(read == 5);
|
|
||||||
ASSERT(strcmp(buffer, "FGHIJ") == 0);
|
|
||||||
|
|
||||||
// Rewind and read from beginning
|
|
||||||
os_file_rewind(h_read);
|
|
||||||
|
|
||||||
char buffer2[6] = {0};
|
|
||||||
read = os_file_read(h_read, buffer2, 5);
|
|
||||||
ASSERT(read == 5);
|
|
||||||
ASSERT(strcmp(buffer2, "ABCDE") == 0);
|
|
||||||
|
|
||||||
// Test file position
|
|
||||||
ASSERT(os_file_tell(h_read) == 5);
|
|
||||||
|
|
||||||
// Test file size
|
|
||||||
ASSERT(os_file_size(h_read) == 10);
|
|
||||||
|
|
||||||
// Seek to end
|
|
||||||
ASSERT(os_file_seek_end(h_read));
|
|
||||||
ASSERT(os_file_is_finished(h_read));
|
|
||||||
|
|
||||||
os_file_close(h_read);
|
|
||||||
os_file_delete(test_file);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(os_file_read_write_all) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
strview_t test_file = strv_init("test_file.txt");
|
|
||||||
|
|
||||||
// Write string
|
|
||||||
strview_t test_data = strv_init("This is test data for read/write all functions");
|
|
||||||
ASSERT(os_file_write_all_str(test_file, test_data));
|
|
||||||
|
|
||||||
// Read back as string
|
|
||||||
str_t read_data = os_file_read_all_str(&arena, test_file);
|
|
||||||
ASSERT(str_equals(read_data, str_init(&arena, "This is test data for read/write all functions")));
|
|
||||||
|
|
||||||
// Read as buffer
|
|
||||||
buffer_t buffer = os_file_read_all(&arena, test_file);
|
|
||||||
ASSERT(buffer.len == test_data.len);
|
|
||||||
ASSERT(memcmp(buffer.data, test_data.buf, test_data.len) == 0);
|
|
||||||
|
|
||||||
// Write buffer
|
|
||||||
const char *new_data = "New buffer data";
|
|
||||||
buffer_t write_buffer = {(u8*)new_data, strlen(new_data)};
|
|
||||||
ASSERT(os_file_write_all(test_file, write_buffer));
|
|
||||||
|
|
||||||
// Read back after buffer write
|
|
||||||
str_t read_new = os_file_read_all_str(&arena, test_file);
|
|
||||||
ASSERT(str_equals(read_new, str_init(&arena, "New buffer data")));
|
|
||||||
|
|
||||||
// Clean up
|
|
||||||
os_file_delete(test_file);
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(os_file_path) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
// Test path splitting
|
|
||||||
strview_t path = strv_init("/path/to/file.txt");
|
|
||||||
strview_t dir, name, ext;
|
|
||||||
|
|
||||||
os_file_split_path(path, &dir, &name, &ext);
|
|
||||||
|
|
||||||
ASSERT(strv_equals(dir, strv_init("/path/to")));
|
|
||||||
ASSERT(strv_equals(name, strv_init("file")));
|
|
||||||
ASSERT(strv_equals(ext, strv_init(".txt")));
|
|
||||||
|
|
||||||
// Test full path resolution
|
|
||||||
strview_t relative_path = strv_init("test_file.txt");
|
|
||||||
tstr_t full_path = os_file_fullpath(&arena, relative_path);
|
|
||||||
|
|
||||||
// Can't easily test the exact value, but can verify it's not empty
|
|
||||||
ASSERT(full_path.len > 0);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Directory Tests
|
|
||||||
UNIT_TEST(os_dir_operations) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
strview_t test_dir = strv_init("test_dir");
|
|
||||||
|
|
||||||
// Delete if exists
|
|
||||||
if (os_dir_exists(test_dir)) {
|
|
||||||
os_dir_delete(test_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create directory
|
|
||||||
ASSERT(os_dir_create(test_dir));
|
|
||||||
ASSERT(os_dir_exists(test_dir));
|
|
||||||
|
|
||||||
// Create test file in directory
|
|
||||||
strview_t test_file_path = strv_init("test_dir/test_file.txt");
|
|
||||||
oshandle_t h_write = os_file_open(test_file_path, FILEMODE_WRITE);
|
|
||||||
ASSERT(os_handle_valid(h_write));
|
|
||||||
os_file_puts(h_write, strv_init("test content"));
|
|
||||||
os_file_close(h_write);
|
|
||||||
|
|
||||||
// Test directory listing
|
|
||||||
dir_t *dir = os_dir_open(&arena, test_dir);
|
|
||||||
ASSERT(os_dir_is_valid(dir));
|
|
||||||
|
|
||||||
bool found_file = false;
|
|
||||||
dir_foreach(&arena, entry, dir) {
|
|
||||||
if (str_equals(entry->name, str_init(&arena, "test_file.txt"))) {
|
|
||||||
found_file = true;
|
|
||||||
ASSERT(entry->type == DIRTYPE_FILE);
|
|
||||||
ASSERT(entry->file_size == 12); // "test content"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ASSERT(found_file);
|
|
||||||
|
|
||||||
// Clean up
|
|
||||||
os_file_delete(test_file_path);
|
|
||||||
os_dir_close(dir);
|
|
||||||
|
|
||||||
// Directory should now be empty, so we can delete it
|
|
||||||
os_dir_delete(test_dir);
|
|
||||||
ASSERT(!os_dir_exists(test_dir));
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Environment Variable Tests
|
|
||||||
UNIT_TEST(os_env_vars) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
arena_t scratch = arena_make(ARENA_MALLOC, KB(1));
|
|
||||||
|
|
||||||
// Set environment variable
|
|
||||||
strview_t key = strv_init("COLLA_TEST_VAR");
|
|
||||||
strview_t value = strv_init("test_value");
|
|
||||||
|
|
||||||
os_set_env_var(scratch, key, value);
|
|
||||||
|
|
||||||
// Get environment variable
|
|
||||||
str_t read_value = os_get_env_var(&arena, key);
|
|
||||||
ASSERT(str_equals(read_value, str_init(&arena, "test_value")));
|
|
||||||
|
|
||||||
// Get all environment variables
|
|
||||||
os_env_t *env = os_get_env(&arena);
|
|
||||||
ASSERT(env != NULL);
|
|
||||||
|
|
||||||
arena_cleanup(&scratch);
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Virtual Memory Tests
|
|
||||||
UNIT_TEST(os_virtual_memory) {
|
|
||||||
usize page_size;
|
|
||||||
void *memory = os_reserve(MB(1), &page_size);
|
|
||||||
ASSERT(memory != NULL);
|
|
||||||
ASSERT(page_size > 0);
|
|
||||||
|
|
||||||
// Commit a page
|
|
||||||
ASSERT(os_commit(memory, 1));
|
|
||||||
|
|
||||||
// Write to the committed memory
|
|
||||||
memset(memory, 0x42, os_get_system_info().page_size);
|
|
||||||
|
|
||||||
// Release the memory
|
|
||||||
ASSERT(os_release(memory, MB(1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Thread Tests
|
|
||||||
static int thread_test_func(u64 thread_id, void *userdata) {
|
|
||||||
int *value = (int*)userdata;
|
|
||||||
(*value)++;
|
|
||||||
return 42;
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(os_thread) {
|
|
||||||
// Create thread data
|
|
||||||
int value = 0;
|
|
||||||
|
|
||||||
// Launch thread
|
|
||||||
oshandle_t thread = os_thread_launch(thread_test_func, &value);
|
|
||||||
ASSERT(os_handle_valid(thread));
|
|
||||||
|
|
||||||
// Get thread ID
|
|
||||||
u64 thread_id = os_thread_get_id(thread);
|
|
||||||
ASSERT(thread_id != 0);
|
|
||||||
|
|
||||||
// Join thread
|
|
||||||
int exit_code;
|
|
||||||
ASSERT(os_thread_join(thread, &exit_code));
|
|
||||||
ASSERT(exit_code == 42);
|
|
||||||
ASSERT(value == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
int test_mutex_trylock(u64 id, void *userdata) {
|
|
||||||
oshandle_t mutex = *((oshandle_t*)userdata);
|
|
||||||
return os_mutex_try_lock(mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Mutex Tests
|
|
||||||
UNIT_TEST(os_mutex) {
|
|
||||||
oshandle_t mutex = os_mutex_create();
|
|
||||||
ASSERT(os_handle_valid(mutex));
|
|
||||||
|
|
||||||
oshandle_t thread = os_thread_launch(test_mutex_trylock, &mutex);
|
|
||||||
|
|
||||||
// Lock mutex
|
|
||||||
os_mutex_lock(mutex);
|
|
||||||
|
|
||||||
int locked = 0;
|
|
||||||
os_thread_join(thread, &locked);
|
|
||||||
ASSERT(locked == false);
|
|
||||||
|
|
||||||
// Unlock
|
|
||||||
os_mutex_unlock(mutex);
|
|
||||||
|
|
||||||
// Try lock should succeed now
|
|
||||||
ASSERT(os_mutex_try_lock(mutex));
|
|
||||||
|
|
||||||
// Unlock again
|
|
||||||
os_mutex_unlock(mutex);
|
|
||||||
|
|
||||||
// Free mutex
|
|
||||||
os_mutex_free(mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !COLLA_NO_CONDITION_VARIABLE
|
|
||||||
// Condition Variable Tests
|
|
||||||
struct cond_test_data {
|
|
||||||
oshandle_t mutex;
|
|
||||||
oshandle_t cond;
|
|
||||||
int counter;
|
|
||||||
};
|
|
||||||
|
|
||||||
static int cond_test_thread(u64 thread_id, void *userdata) {
|
|
||||||
struct cond_test_data *data = (struct cond_test_data*)userdata;
|
|
||||||
|
|
||||||
os_mutex_lock(data->mutex);
|
|
||||||
data->counter++;
|
|
||||||
os_mutex_unlock(data->mutex);
|
|
||||||
|
|
||||||
// Signal the condition
|
|
||||||
os_cond_signal(data->cond);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(os_condition_variable) {
|
|
||||||
struct cond_test_data data;
|
|
||||||
data.mutex = os_mutex_create();
|
|
||||||
data.cond = os_cond_create();
|
|
||||||
data.counter = 0;
|
|
||||||
|
|
||||||
// Lock mutex before launching thread
|
|
||||||
os_mutex_lock(data.mutex);
|
|
||||||
|
|
||||||
// Launch thread
|
|
||||||
oshandle_t thread = os_thread_launch(cond_test_thread, &data);
|
|
||||||
|
|
||||||
// Wait for condition with timeout
|
|
||||||
os_cond_wait(data.cond, data.mutex, 1000);
|
|
||||||
|
|
||||||
// We should have the lock again, and counter should be 1
|
|
||||||
ASSERT(data.counter == 1);
|
|
||||||
|
|
||||||
// Unlock and cleanup
|
|
||||||
os_mutex_unlock(data.mutex);
|
|
||||||
os_thread_join(thread, NULL);
|
|
||||||
|
|
||||||
os_mutex_free(data.mutex);
|
|
||||||
os_cond_free(data.cond);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,370 +0,0 @@
|
||||||
#include "runner.h"
|
|
||||||
#include "../parsers.h"
|
|
||||||
#include "../arena.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
// INI Parser Tests
|
|
||||||
UNIT_TEST(ini_parse_basic) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
strview_t ini_content = strv_init(
|
|
||||||
"[section1]\n"
|
|
||||||
"key1=value1\n"
|
|
||||||
"key2=value2\n"
|
|
||||||
"\n"
|
|
||||||
"[section2]\n"
|
|
||||||
"key3=value3\n"
|
|
||||||
"key4=value4\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
ini_t ini = ini_parse_str(&arena, ini_content, NULL);
|
|
||||||
ASSERT(ini_is_valid(&ini));
|
|
||||||
|
|
||||||
// Test section1
|
|
||||||
initable_t *section1 = ini_get_table(&ini, strv_init("section1"));
|
|
||||||
ASSERT(section1 != NULL);
|
|
||||||
ASSERT(strv_equals(section1->name, strv_init("section1")));
|
|
||||||
|
|
||||||
// Test section1 values
|
|
||||||
inivalue_t *key1 = ini_get(section1, strv_init("key1"));
|
|
||||||
ASSERT(key1 != NULL);
|
|
||||||
ASSERT(strv_equals(key1->key, strv_init("key1")));
|
|
||||||
ASSERT(strv_equals(key1->value, strv_init("value1")));
|
|
||||||
|
|
||||||
inivalue_t *key2 = ini_get(section1, strv_init("key2"));
|
|
||||||
ASSERT(key2 != NULL);
|
|
||||||
ASSERT(strv_equals(key2->key, strv_init("key2")));
|
|
||||||
ASSERT(strv_equals(key2->value, strv_init("value2")));
|
|
||||||
|
|
||||||
// Test section2
|
|
||||||
initable_t *section2 = ini_get_table(&ini, strv_init("section2"));
|
|
||||||
ASSERT(section2 != NULL);
|
|
||||||
ASSERT(strv_equals(section2->name, strv_init("section2")));
|
|
||||||
|
|
||||||
// Test section2 values
|
|
||||||
inivalue_t *key3 = ini_get(section2, strv_init("key3"));
|
|
||||||
ASSERT(key3 != NULL);
|
|
||||||
ASSERT(strv_equals(key3->key, strv_init("key3")));
|
|
||||||
ASSERT(strv_equals(key3->value, strv_init("value3")));
|
|
||||||
|
|
||||||
inivalue_t *key4 = ini_get(section2, strv_init("key4"));
|
|
||||||
ASSERT(key4 != NULL);
|
|
||||||
ASSERT(strv_equals(key4->key, strv_init("key4")));
|
|
||||||
ASSERT(strv_equals(key4->value, strv_init("value4")));
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(ini_parse_with_options) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
strview_t ini_content = strv_init(
|
|
||||||
"[section1]\n"
|
|
||||||
"key1:value1\n"
|
|
||||||
"# This is a comment\n"
|
|
||||||
"key2:value2\n"
|
|
||||||
"\n"
|
|
||||||
"[section1]\n" // Duplicate section
|
|
||||||
"key3:value3\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
iniopt_t options = {
|
|
||||||
.merge_duplicate_tables = true,
|
|
||||||
.merge_duplicate_keys = false,
|
|
||||||
.key_value_divider = ':',
|
|
||||||
.comment_vals = strv_init("#")
|
|
||||||
};
|
|
||||||
|
|
||||||
ini_t ini = ini_parse_str(&arena, ini_content, &options);
|
|
||||||
ASSERT(ini_is_valid(&ini));
|
|
||||||
|
|
||||||
// Test section1 (should be merged)
|
|
||||||
initable_t *section1 = ini_get_table(&ini, strv_init("section1"));
|
|
||||||
ASSERT(section1 != NULL);
|
|
||||||
|
|
||||||
// Check all keys exist in merged section
|
|
||||||
inivalue_t *key1 = ini_get(section1, strv_init("key1"));
|
|
||||||
ASSERT(key1 != NULL);
|
|
||||||
ASSERT(strv_equals(key1->value, strv_init("value1")));
|
|
||||||
|
|
||||||
inivalue_t *key2 = ini_get(section1, strv_init("key2"));
|
|
||||||
ASSERT(key2 != NULL);
|
|
||||||
ASSERT(strv_equals(key2->value, strv_init("value2")));
|
|
||||||
|
|
||||||
inivalue_t *key3 = ini_get(section1, strv_init("key3"));
|
|
||||||
ASSERT(key3 != NULL);
|
|
||||||
ASSERT(strv_equals(key3->value, strv_init("value3")));
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(ini_value_conversion) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
strview_t ini_content = strv_init(
|
|
||||||
"[values]\n"
|
|
||||||
"uint=42\n"
|
|
||||||
"int=-42\n"
|
|
||||||
"float=3.14\n"
|
|
||||||
"bool_true=true\n"
|
|
||||||
"bool_false=false\n"
|
|
||||||
"array=item1,item2,item3\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
ini_t ini = ini_parse_str(&arena, ini_content, NULL);
|
|
||||||
initable_t *values = ini_get_table(&ini, strv_init("values"));
|
|
||||||
ASSERT(values != NULL);
|
|
||||||
|
|
||||||
// Test uint conversion
|
|
||||||
inivalue_t *uint_val = ini_get(values, strv_init("uint"));
|
|
||||||
ASSERT(uint_val != NULL);
|
|
||||||
ASSERT(ini_as_uint(uint_val) == 42);
|
|
||||||
|
|
||||||
// Test int conversion
|
|
||||||
inivalue_t *int_val = ini_get(values, strv_init("int"));
|
|
||||||
ASSERT(int_val != NULL);
|
|
||||||
ASSERT(ini_as_int(int_val) == -42);
|
|
||||||
|
|
||||||
// Test float conversion
|
|
||||||
inivalue_t *float_val = ini_get(values, strv_init("float"));
|
|
||||||
ASSERT(float_val != NULL);
|
|
||||||
ASSERT(ini_as_num(float_val) > 3.13 && ini_as_num(float_val) < 3.15);
|
|
||||||
|
|
||||||
// Test bool conversion
|
|
||||||
inivalue_t *bool_true = ini_get(values, strv_init("bool_true"));
|
|
||||||
ASSERT(bool_true != NULL);
|
|
||||||
ASSERT(ini_as_bool(bool_true) == true);
|
|
||||||
|
|
||||||
inivalue_t *bool_false = ini_get(values, strv_init("bool_false"));
|
|
||||||
ASSERT(bool_false != NULL);
|
|
||||||
ASSERT(ini_as_bool(bool_false) == false);
|
|
||||||
|
|
||||||
// Test array conversion
|
|
||||||
inivalue_t *array_val = ini_get(values, strv_init("array"));
|
|
||||||
ASSERT(array_val != NULL);
|
|
||||||
|
|
||||||
iniarray_t array = ini_as_arr(&arena, array_val, ',');
|
|
||||||
ASSERT(array.count == 3);
|
|
||||||
ASSERT(strv_equals(array.values[0], strv_init("item1")));
|
|
||||||
ASSERT(strv_equals(array.values[1], strv_init("item2")));
|
|
||||||
ASSERT(strv_equals(array.values[2], strv_init("item3")));
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSON Parser Tests
|
|
||||||
UNIT_TEST(json_parse_basic) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
strview_t json_content = strv_init(
|
|
||||||
"{\n"
|
|
||||||
" \"string\": \"value\",\n"
|
|
||||||
" \"number\": 42,\n"
|
|
||||||
" \"bool\": true,\n"
|
|
||||||
" \"null\": null,\n"
|
|
||||||
" \"array\": [1, 2, 3],\n"
|
|
||||||
" \"object\": {\n"
|
|
||||||
" \"nested\": \"nested_value\"\n"
|
|
||||||
" }\n"
|
|
||||||
"}"
|
|
||||||
);
|
|
||||||
|
|
||||||
json_t *root = json_parse_str(&arena, json_content, JSON_DEFAULT);
|
|
||||||
ASSERT(root != NULL);
|
|
||||||
ASSERT(root->type == JSON_OBJECT);
|
|
||||||
|
|
||||||
// Test string
|
|
||||||
json_t *string_node = json_get(root, strv_init("string"));
|
|
||||||
ASSERT(json_check(string_node, JSON_STRING));
|
|
||||||
ASSERT(strv_equals(string_node->string, strv_init("value")));
|
|
||||||
|
|
||||||
// Test number
|
|
||||||
json_t *number_node = json_get(root, strv_init("number"));
|
|
||||||
ASSERT(json_check(number_node, JSON_NUMBER));
|
|
||||||
ASSERT(number_node->number == 42);
|
|
||||||
|
|
||||||
// Test bool
|
|
||||||
json_t *bool_node = json_get(root, strv_init("bool"));
|
|
||||||
ASSERT(json_check(bool_node, JSON_BOOL));
|
|
||||||
ASSERT(bool_node->boolean == true);
|
|
||||||
|
|
||||||
// Test null
|
|
||||||
json_t *null_node = json_get(root, strv_init("null"));
|
|
||||||
ASSERT(json_check(null_node, JSON_NULL));
|
|
||||||
|
|
||||||
// Test array
|
|
||||||
json_t *array_node = json_get(root, strv_init("array"));
|
|
||||||
ASSERT(json_check(array_node, JSON_ARRAY));
|
|
||||||
|
|
||||||
// Test array contents
|
|
||||||
int count = 0;
|
|
||||||
int sum = 0;
|
|
||||||
json_for(item, array_node) {
|
|
||||||
ASSERT(json_check(item, JSON_NUMBER));
|
|
||||||
sum += (int)item->number;
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
ASSERT(count == 3);
|
|
||||||
ASSERT(sum == 6); // 1 + 2 + 3
|
|
||||||
|
|
||||||
// Test nested object
|
|
||||||
json_t *object_node = json_get(root, strv_init("object"));
|
|
||||||
ASSERT(json_check(object_node, JSON_OBJECT));
|
|
||||||
|
|
||||||
json_t *nested_node = json_get(object_node, strv_init("nested"));
|
|
||||||
ASSERT(json_check(nested_node, JSON_STRING));
|
|
||||||
ASSERT(strv_equals(nested_node->string, strv_init("nested_value")));
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(json_parse_with_options) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
// JSON with comments and trailing commas
|
|
||||||
strview_t json_content = strv_init(
|
|
||||||
"{\n"
|
|
||||||
" \"key1\": \"value1\",\n"
|
|
||||||
" // This is a comment\n"
|
|
||||||
" \"key2\": \"value2\",\n"
|
|
||||||
" \"array\": [\n"
|
|
||||||
" 1,\n"
|
|
||||||
" 2,\n"
|
|
||||||
" 3,\n" // Trailing comma
|
|
||||||
" ],\n" // Trailing comma
|
|
||||||
"}"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Test with default flags (should allow comments and trailing commas)
|
|
||||||
json_t *root1 = json_parse_str(&arena, json_content, JSON_DEFAULT);
|
|
||||||
ASSERT(root1 != NULL);
|
|
||||||
ASSERT(json_get(root1, strv_init("key1")) != NULL);
|
|
||||||
ASSERT(json_get(root1, strv_init("key2")) != NULL);
|
|
||||||
|
|
||||||
// Test with NO_COMMENTS and NO_TRAILING_COMMAS flags
|
|
||||||
json_t *root2 = json_parse_str(&arena, json_content, JSON_NO_COMMENTS | JSON_NO_TRAILING_COMMAS);
|
|
||||||
|
|
||||||
// This should fail parsing due to the strict flags - but the behavior depends on implementation
|
|
||||||
// Some parsers might ignore the errors, others might return NULL
|
|
||||||
// We'll check both possibilities
|
|
||||||
if (root2 != NULL) {
|
|
||||||
// If parsing succeeded despite strict flags, ensure the content is correct
|
|
||||||
ASSERT(json_get(root2, strv_init("key1")) != NULL);
|
|
||||||
// key2 might be missing if comment handling failed
|
|
||||||
}
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
// XML Parser Tests
|
|
||||||
UNIT_TEST(xml_parse_basic) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
strview_t xml_content = strv_init(
|
|
||||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
|
||||||
"<root>\n"
|
|
||||||
" <item id=\"1\" type=\"book\">\n"
|
|
||||||
" <title>Test Title</title>\n"
|
|
||||||
" <author>Test Author</author>\n"
|
|
||||||
" </item>\n"
|
|
||||||
" <item id=\"2\" type=\"magazine\">\n"
|
|
||||||
" <title>Another Title</title>\n"
|
|
||||||
" </item>\n"
|
|
||||||
"</root>"
|
|
||||||
);
|
|
||||||
|
|
||||||
xml_t xml = xml_parse_str(&arena, xml_content);
|
|
||||||
ASSERT(xml.root != NULL);
|
|
||||||
ASSERT(strv_equals(xml.root->key, strv_init("root")));
|
|
||||||
|
|
||||||
// Find item tags
|
|
||||||
xmltag_t *item = xml_get_tag(xml.root, strv_init("item"), false);
|
|
||||||
ASSERT(item != NULL);
|
|
||||||
|
|
||||||
// Check attributes
|
|
||||||
strview_t id = xml_get_attribute(item, strv_init("id"));
|
|
||||||
ASSERT(strv_equals(id, strv_init("1")));
|
|
||||||
|
|
||||||
strview_t type = xml_get_attribute(item, strv_init("type"));
|
|
||||||
ASSERT(strv_equals(type, strv_init("book")));
|
|
||||||
|
|
||||||
// Check nested tags
|
|
||||||
xmltag_t *title = xml_get_tag(item, strv_init("title"), false);
|
|
||||||
ASSERT(title != NULL);
|
|
||||||
ASSERT(strv_equals(title->content, strv_init("Test Title")));
|
|
||||||
|
|
||||||
xmltag_t *author = xml_get_tag(item, strv_init("author"), false);
|
|
||||||
ASSERT(author != NULL);
|
|
||||||
ASSERT(strv_equals(author->content, strv_init("Test Author")));
|
|
||||||
|
|
||||||
// Check recursive tag finding
|
|
||||||
xmltag_t *title_recursive = xml_get_tag(xml.root, strv_init("title"), true);
|
|
||||||
ASSERT(title_recursive != NULL);
|
|
||||||
ASSERT(strv_equals(title_recursive->content, strv_init("Test Title")));
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTML Parser Tests
|
|
||||||
UNIT_TEST(html_parse_basic) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
strview_t html_content = strv_init(
|
|
||||||
"<!DOCTYPE html>\n"
|
|
||||||
"<html>\n"
|
|
||||||
"<head>\n"
|
|
||||||
" <title>Test Page</title>\n"
|
|
||||||
"</head>\n"
|
|
||||||
"<body>\n"
|
|
||||||
" <h1>Hello World</h1>\n"
|
|
||||||
" <p class=\"intro\">This is a test.</p>\n"
|
|
||||||
" <div id=\"content\">\n"
|
|
||||||
" <p>More content here.</p>\n"
|
|
||||||
" </div>\n"
|
|
||||||
"</body>\n"
|
|
||||||
"</html>"
|
|
||||||
);
|
|
||||||
|
|
||||||
html_t html = html_parse_str(&arena, html_content);
|
|
||||||
ASSERT(html.root != NULL);
|
|
||||||
ASSERT(str_equals(html.root->key, str_init(&arena, "html")));
|
|
||||||
|
|
||||||
// Find head and body
|
|
||||||
htmltag_t *head = html_get_tag(html.root, strv_init("head"), false);
|
|
||||||
ASSERT(head != NULL);
|
|
||||||
|
|
||||||
htmltag_t *body = html_get_tag(html.root, strv_init("body"), false);
|
|
||||||
ASSERT(body != NULL);
|
|
||||||
|
|
||||||
// Find title in head
|
|
||||||
htmltag_t *title = html_get_tag(head, strv_init("title"), false);
|
|
||||||
ASSERT(title != NULL);
|
|
||||||
ASSERT(strv_equals(title->content, strv_init("Test Page")));
|
|
||||||
|
|
||||||
// Find elements in body
|
|
||||||
htmltag_t *h1 = html_get_tag(body, strv_init("h1"), false);
|
|
||||||
ASSERT(h1 != NULL);
|
|
||||||
ASSERT(strv_equals(h1->content, strv_init("Hello World")));
|
|
||||||
|
|
||||||
// Find paragraph with class
|
|
||||||
htmltag_t *p = html_get_tag(body, strv_init("p"), false);
|
|
||||||
ASSERT(p != NULL);
|
|
||||||
|
|
||||||
strview_t p_class = html_get_attribute(p, strv_init("class"));
|
|
||||||
ASSERT(strv_equals(p_class, strv_init("intro")));
|
|
||||||
ASSERT(strv_equals(p->content, strv_init("This is a test.")));
|
|
||||||
|
|
||||||
// Find div by id
|
|
||||||
htmltag_t *div = html_get_tag(body, strv_init("div"), false);
|
|
||||||
ASSERT(div != NULL);
|
|
||||||
|
|
||||||
strview_t div_id = html_get_attribute(div, strv_init("id"));
|
|
||||||
ASSERT(strv_equals(div_id, strv_init("content")));
|
|
||||||
|
|
||||||
// Find nested paragraph using recursive search
|
|
||||||
htmltag_t *nested_p = html_get_tag(div, strv_init("p"), false);
|
|
||||||
ASSERT(nested_p != NULL);
|
|
||||||
ASSERT(strv_equals(nested_p->content, strv_init("More content here.")));
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
#include "runner.h"
|
|
||||||
#include "../pretty_print.h"
|
|
||||||
#include "../arena.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
UNIT_TEST(pretty_print_basic) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
// The pretty_print function outputs to console, so we can't easily verify its exact output
|
|
||||||
// Instead, we'll just verify it doesn't crash
|
|
||||||
pretty_print(arena, "Hello, <red>World!</>");
|
|
||||||
|
|
||||||
// Test with formatting
|
|
||||||
pretty_print(arena, "Value: <blue>%d</>, String: <green>%s</>", 42, "test");
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to test variadic function
|
|
||||||
void test_pretty_printv(arena_t arena, const char *fmt, ...) {
|
|
||||||
va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
pretty_printv(arena, fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(pretty_printv) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
// Test the helper function
|
|
||||||
test_pretty_printv(arena, "Test <yellow>%d</> <cyan>%s</>", 42, "variadic");
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../core.h"
|
|
||||||
#include "../str.h"
|
|
||||||
|
|
||||||
typedef struct unit_test_t unit_test_t;
|
|
||||||
struct unit_test_t {
|
|
||||||
strview_t fname;
|
|
||||||
strview_t name;
|
|
||||||
void (*fn)(void);
|
|
||||||
unit_test_t *next;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern unit_test_t *test_head;
|
|
||||||
extern unit_test_t *test_tail;
|
|
||||||
extern const char *last_fail_reason;
|
|
||||||
extern bool last_failed;
|
|
||||||
|
|
||||||
void ut_register(const char *file, const char *name, void (*fn)(void));
|
|
||||||
|
|
||||||
// #pragma data_seg(".CRT$XCU")
|
|
||||||
|
|
||||||
#define INITIALIZER(f) \
|
|
||||||
static void f(void); \
|
|
||||||
__declspec(allocate(".CRT$XCU")) void (*f##_)(void) = f; \
|
|
||||||
__pragma(comment(linker,"/include:" #f "_")) \
|
|
||||||
static void f(void)
|
|
||||||
|
|
||||||
#define UNIT_TEST(name) \
|
|
||||||
void ut__test_##name(void); \
|
|
||||||
INITIALIZER(ut__register_##name) { ut_register(__FILE__, #name, ut__test_##name); } \
|
|
||||||
void ut__test_##name(void)
|
|
||||||
|
|
||||||
|
|
||||||
#define ASSERT(cond) \
|
|
||||||
if (!(cond)) { \
|
|
||||||
last_fail_reason = "assert(" COLLA_STRINGIFY(cond) ") at " COLLA_STRINGIFY(__LINE__); \
|
|
||||||
last_failed = true; \
|
|
||||||
return; \
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,457 +0,0 @@
|
||||||
#include "runner.h"
|
|
||||||
#include "../str.h"
|
|
||||||
#include "../arena.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
// String (str_t) Tests
|
|
||||||
UNIT_TEST(str_init_basic) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
str_t s = str_init(&arena, "hello");
|
|
||||||
|
|
||||||
ASSERT(s.len == 5);
|
|
||||||
ASSERT(s.buf != NULL);
|
|
||||||
ASSERT(memcmp(s.buf, "hello", 5) == 0);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(str_init_len) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
str_t s = str_init_len(&arena, "hello world", 5);
|
|
||||||
|
|
||||||
ASSERT(s.len == 5);
|
|
||||||
ASSERT(s.buf != NULL);
|
|
||||||
ASSERT(memcmp(s.buf, "hello", 5) == 0);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(str_fmt) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
str_t s = str_fmt(&arena, "Number: %d, String: %s", 42, "test");
|
|
||||||
|
|
||||||
ASSERT(s.buf != NULL);
|
|
||||||
ASSERT(memcmp(s.buf, "Number: 42, String: test", s.len) == 0);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(str_equals) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
str_t s1 = str_init(&arena, "hello");
|
|
||||||
str_t s2 = str_init(&arena, "hello");
|
|
||||||
str_t s3 = str_init(&arena, "world");
|
|
||||||
|
|
||||||
ASSERT(str_equals(s1, s2) == true);
|
|
||||||
ASSERT(str_equals(s1, s3) == false);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(str_compare) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
str_t s1 = str_init(&arena, "abc");
|
|
||||||
str_t s2 = str_init(&arena, "abc");
|
|
||||||
str_t s3 = str_init(&arena, "abd");
|
|
||||||
str_t s4 = str_init(&arena, "abb");
|
|
||||||
|
|
||||||
ASSERT(str_compare(s1, s2) == 0);
|
|
||||||
ASSERT(str_compare(s1, s3) < 0);
|
|
||||||
ASSERT(str_compare(s1, s4) > 0);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(str_dup) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
str_t s1 = str_init(&arena, "test");
|
|
||||||
str_t s2 = str_dup(&arena, s1);
|
|
||||||
|
|
||||||
ASSERT(s1.len == s2.len);
|
|
||||||
ASSERT(s1.buf != s2.buf); // Different memory locations
|
|
||||||
ASSERT(memcmp(s1.buf, s2.buf, s1.len) == 0);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(str_cat) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
str_t s1 = str_init(&arena, "hello ");
|
|
||||||
str_t s2 = str_init(&arena, "world");
|
|
||||||
str_t s3 = str_cat(&arena, s1, s2);
|
|
||||||
|
|
||||||
ASSERT(s3.len == s1.len + s2.len);
|
|
||||||
ASSERT(memcmp(s3.buf, "hello world", s3.len) == 0);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(str_is_empty) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
str_t s1 = str_init(&arena, "test");
|
|
||||||
str_t s2 = STR_EMPTY;
|
|
||||||
|
|
||||||
ASSERT(str_is_empty(s1) == false);
|
|
||||||
ASSERT(str_is_empty(s2) == true);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(str_lower_upper) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
str_t s1 = str_init(&arena, "TeSt");
|
|
||||||
str_lower(&s1);
|
|
||||||
ASSERT(memcmp(s1.buf, "test", 4) == 0);
|
|
||||||
|
|
||||||
str_t s2 = str_init(&arena, "TeSt");
|
|
||||||
str_upper(&s2);
|
|
||||||
ASSERT(memcmp(s2.buf, "TEST", 4) == 0);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(str_replace) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
str_t s = str_init(&arena, "hello");
|
|
||||||
str_replace(&s, 'l', 'x');
|
|
||||||
ASSERT(memcmp(s.buf, "hexxo", 5) == 0);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(str_sub) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
str_t s = str_init(&arena, "hello world");
|
|
||||||
strview_t sv = str_sub(s, 6, 11);
|
|
||||||
|
|
||||||
ASSERT(sv.len == 5);
|
|
||||||
ASSERT(memcmp(sv.buf, "world", 5) == 0);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
// String View (strview_t) Tests
|
|
||||||
UNIT_TEST(strv_init) {
|
|
||||||
strview_t sv = strv_init("hello");
|
|
||||||
|
|
||||||
ASSERT(sv.len == 5);
|
|
||||||
ASSERT(sv.buf != NULL);
|
|
||||||
ASSERT(memcmp(sv.buf, "hello", 5) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(strv_init_len) {
|
|
||||||
strview_t sv = strv_init_len("hello world", 5);
|
|
||||||
|
|
||||||
ASSERT(sv.len == 5);
|
|
||||||
ASSERT(sv.buf != NULL);
|
|
||||||
ASSERT(memcmp(sv.buf, "hello", 5) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(strv_init_str) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
|
|
||||||
str_t s = str_init(&arena, "hello");
|
|
||||||
strview_t sv = strv_init_str(s);
|
|
||||||
|
|
||||||
ASSERT(sv.len == s.len);
|
|
||||||
ASSERT(sv.buf == s.buf);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(strv_is_empty) {
|
|
||||||
strview_t sv1 = strv_init("test");
|
|
||||||
strview_t sv2 = STRV_EMPTY;
|
|
||||||
|
|
||||||
ASSERT(strv_is_empty(sv1) == false);
|
|
||||||
ASSERT(strv_is_empty(sv2) == true);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(strv_equals) {
|
|
||||||
strview_t sv1 = strv_init("hello");
|
|
||||||
strview_t sv2 = strv_init("hello");
|
|
||||||
strview_t sv3 = strv_init("world");
|
|
||||||
|
|
||||||
ASSERT(strv_equals(sv1, sv2) == true);
|
|
||||||
ASSERT(strv_equals(sv1, sv3) == false);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(strv_compare) {
|
|
||||||
strview_t sv1 = strv_init("abc");
|
|
||||||
strview_t sv2 = strv_init("abc");
|
|
||||||
strview_t sv3 = strv_init("abd");
|
|
||||||
strview_t sv4 = strv_init("abb");
|
|
||||||
|
|
||||||
ASSERT(strv_compare(sv1, sv2) == 0);
|
|
||||||
ASSERT(strv_compare(sv1, sv3) < 0);
|
|
||||||
ASSERT(strv_compare(sv1, sv4) > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(strv_front_back) {
|
|
||||||
strview_t sv = strv_init("hello");
|
|
||||||
|
|
||||||
ASSERT(strv_front(sv) == 'h');
|
|
||||||
ASSERT(strv_back(sv) == 'o');
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(strv_remove_prefix_suffix) {
|
|
||||||
strview_t sv = strv_init("hello");
|
|
||||||
|
|
||||||
strview_t prefix_removed = strv_remove_prefix(sv, 2);
|
|
||||||
ASSERT(prefix_removed.len == 3);
|
|
||||||
ASSERT(memcmp(prefix_removed.buf, "llo", 3) == 0);
|
|
||||||
|
|
||||||
strview_t suffix_removed = strv_remove_suffix(sv, 2);
|
|
||||||
ASSERT(suffix_removed.len == 3);
|
|
||||||
ASSERT(memcmp(suffix_removed.buf, "hel", 3) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(strv_trim) {
|
|
||||||
strview_t sv1 = strv_init(" hello ");
|
|
||||||
strview_t sv2 = strv_init(" hello");
|
|
||||||
strview_t sv3 = strv_init("hello ");
|
|
||||||
|
|
||||||
strview_t trimmed1 = strv_trim(sv1);
|
|
||||||
strview_t trimmed2 = strv_trim_left(sv2);
|
|
||||||
strview_t trimmed3 = strv_trim_right(sv3);
|
|
||||||
|
|
||||||
ASSERT(trimmed1.len == 5);
|
|
||||||
ASSERT(memcmp(trimmed1.buf, "hello", 5) == 0);
|
|
||||||
|
|
||||||
ASSERT(trimmed2.len == 5);
|
|
||||||
ASSERT(memcmp(trimmed2.buf, "hello", 5) == 0);
|
|
||||||
|
|
||||||
ASSERT(trimmed3.len == 5);
|
|
||||||
ASSERT(memcmp(trimmed3.buf, "hello", 5) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(strv_sub) {
|
|
||||||
strview_t sv = strv_init("hello world");
|
|
||||||
strview_t sub = strv_sub(sv, 6, 11);
|
|
||||||
|
|
||||||
ASSERT(sub.len == 5);
|
|
||||||
ASSERT(memcmp(sub.buf, "world", 5) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(strv_starts_ends_with) {
|
|
||||||
strview_t sv = strv_init("hello");
|
|
||||||
|
|
||||||
ASSERT(strv_starts_with(sv, 'h') == true);
|
|
||||||
ASSERT(strv_starts_with(sv, 'e') == false);
|
|
||||||
|
|
||||||
ASSERT(strv_ends_with(sv, 'o') == true);
|
|
||||||
ASSERT(strv_ends_with(sv, 'l') == false);
|
|
||||||
|
|
||||||
strview_t prefix = strv_init("hel");
|
|
||||||
strview_t suffix = strv_init("llo");
|
|
||||||
|
|
||||||
ASSERT(strv_starts_with_view(sv, prefix) == true);
|
|
||||||
ASSERT(strv_ends_with_view(sv, suffix) == true);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(strv_contains) {
|
|
||||||
strview_t sv = strv_init("hello world");
|
|
||||||
|
|
||||||
ASSERT(strv_contains(sv, 'e') == true);
|
|
||||||
ASSERT(strv_contains(sv, 'z') == false);
|
|
||||||
|
|
||||||
strview_t sub = strv_init("world");
|
|
||||||
ASSERT(strv_contains_view(sv, sub) == true);
|
|
||||||
|
|
||||||
strview_t chars = strv_init("xyz");
|
|
||||||
ASSERT(strv_contains_either(sv, chars) == false);
|
|
||||||
|
|
||||||
strview_t chars2 = strv_init("xyo");
|
|
||||||
ASSERT(strv_contains_either(sv, chars2) == true);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(strv_find) {
|
|
||||||
strview_t sv = strv_init("hello world");
|
|
||||||
|
|
||||||
ASSERT(strv_find(sv, 'o', 0) == 4);
|
|
||||||
ASSERT(strv_find(sv, 'o', 5) == 7);
|
|
||||||
ASSERT(strv_find(sv, 'z', 0) == STR_NONE);
|
|
||||||
|
|
||||||
strview_t sub = strv_init("world");
|
|
||||||
ASSERT(strv_find_view(sv, sub, 0) == 6);
|
|
||||||
|
|
||||||
strview_t chars = strv_init("xwo");
|
|
||||||
ASSERT(strv_find_either(sv, chars, 0) == 4); // 'w' at position 6
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(strv_rfind) {
|
|
||||||
strview_t sv = strv_init("hello world");
|
|
||||||
|
|
||||||
ASSERT(strv_rfind(sv, 'o', 0) == 7);
|
|
||||||
ASSERT(strv_rfind(sv, 'o', 5) == 4);
|
|
||||||
ASSERT(strv_rfind(sv, 'z', 0) == STR_NONE);
|
|
||||||
|
|
||||||
strview_t sub = strv_init("world");
|
|
||||||
ASSERT(strv_rfind_view(sv, sub, 0) == 6);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Character Functions Tests
|
|
||||||
UNIT_TEST(char_functions) {
|
|
||||||
ASSERT(char_is_space(' ') == true);
|
|
||||||
ASSERT(char_is_space('\t') == true);
|
|
||||||
ASSERT(char_is_space('a') == false);
|
|
||||||
|
|
||||||
ASSERT(char_is_alpha('a') == true);
|
|
||||||
ASSERT(char_is_alpha('Z') == true);
|
|
||||||
ASSERT(char_is_alpha('1') == false);
|
|
||||||
|
|
||||||
ASSERT(char_is_num('0') == true);
|
|
||||||
ASSERT(char_is_num('9') == true);
|
|
||||||
ASSERT(char_is_num('a') == false);
|
|
||||||
|
|
||||||
ASSERT(char_lower('A') == 'a');
|
|
||||||
ASSERT(char_lower('a') == 'a');
|
|
||||||
ASSERT(char_lower('1') == '1');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input Stream Tests
|
|
||||||
UNIT_TEST(instream_basic) {
|
|
||||||
strview_t sv = strv_init("hello world");
|
|
||||||
instream_t is = istr_init(sv);
|
|
||||||
|
|
||||||
ASSERT(istr_get(&is) == 'h');
|
|
||||||
ASSERT(istr_get(&is) == 'e');
|
|
||||||
ASSERT(istr_peek(&is) == 'l');
|
|
||||||
ASSERT(istr_peek_next(&is) == 'l');
|
|
||||||
ASSERT(istr_get(&is) == 'l');
|
|
||||||
ASSERT(istr_prev(&is) == 'l');
|
|
||||||
ASSERT(istr_prev_prev(&is) == 'e');
|
|
||||||
|
|
||||||
istr_skip(&is, 2);
|
|
||||||
ASSERT(istr_peek(&is) == ' ');
|
|
||||||
|
|
||||||
istr_skip_whitespace(&is);
|
|
||||||
ASSERT(istr_peek(&is) == 'w');
|
|
||||||
|
|
||||||
istr_rewind(&is);
|
|
||||||
ASSERT(istr_peek(&is) == 'h');
|
|
||||||
|
|
||||||
ASSERT(istr_tell(&is) == 0);
|
|
||||||
ASSERT(istr_remaining(&is) == 11);
|
|
||||||
ASSERT(istr_is_finished(&is) == false);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(instream_ignore) {
|
|
||||||
strview_t sv = strv_init("hello,world");
|
|
||||||
instream_t is = istr_init(sv);
|
|
||||||
|
|
||||||
istr_ignore(&is, ',');
|
|
||||||
ASSERT(istr_peek(&is) == ',');
|
|
||||||
|
|
||||||
istr_ignore_and_skip(&is, ',');
|
|
||||||
ASSERT(istr_peek(&is) == 'w');
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(instream_get_values) {
|
|
||||||
strview_t sv = strv_init("true 42 3.14 hello");
|
|
||||||
instream_t is = istr_init(sv);
|
|
||||||
|
|
||||||
bool b;
|
|
||||||
ASSERT(istr_get_bool(&is, &b) == true);
|
|
||||||
ASSERT(b == true);
|
|
||||||
|
|
||||||
istr_skip_whitespace(&is);
|
|
||||||
|
|
||||||
u32 u;
|
|
||||||
ASSERT(istr_get_u32(&is, &u) == true);
|
|
||||||
ASSERT(u == 42);
|
|
||||||
|
|
||||||
istr_skip_whitespace(&is);
|
|
||||||
|
|
||||||
double d;
|
|
||||||
ASSERT(istr_get_num(&is, &d) == true);
|
|
||||||
ASSERT(d > 3.13 && d < 3.15);
|
|
||||||
|
|
||||||
istr_skip_whitespace(&is);
|
|
||||||
|
|
||||||
strview_t word = istr_get_view(&is, ' ');
|
|
||||||
ASSERT(word.len == 5);
|
|
||||||
ASSERT(memcmp(word.buf, "hello", 5) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output Stream Tests
|
|
||||||
UNIT_TEST(outstream_basic) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
outstream_t os = ostr_init(&arena);
|
|
||||||
|
|
||||||
ostr_putc(&os, 'h');
|
|
||||||
ostr_putc(&os, 'i');
|
|
||||||
|
|
||||||
ASSERT(ostr_tell(&os) == 2);
|
|
||||||
ASSERT(ostr_back(&os) == 'i');
|
|
||||||
|
|
||||||
ostr_print(&os, " %d", 42);
|
|
||||||
|
|
||||||
strview_t result = ostr_as_view(&os);
|
|
||||||
ASSERT(result.len == 5);
|
|
||||||
ASSERT(memcmp(result.buf, "hi 42", 5) == 0);
|
|
||||||
|
|
||||||
ostr_pop(&os, 3);
|
|
||||||
result = ostr_as_view(&os);
|
|
||||||
ASSERT(result.len == 2);
|
|
||||||
ASSERT(memcmp(result.buf, "hi", 2) == 0);
|
|
||||||
|
|
||||||
ostr_clear(&os);
|
|
||||||
ASSERT(ostr_tell(&os) == 0);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
UNIT_TEST(outstream_append) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(4));
|
|
||||||
outstream_t os = ostr_init(&arena);
|
|
||||||
|
|
||||||
ostr_append_bool(&os, true);
|
|
||||||
ostr_putc(&os, ' ');
|
|
||||||
ostr_append_uint(&os, 42);
|
|
||||||
ostr_putc(&os, ' ');
|
|
||||||
ostr_append_int(&os, -10);
|
|
||||||
ostr_putc(&os, ' ');
|
|
||||||
ostr_append_num(&os, 3.14);
|
|
||||||
|
|
||||||
str_t result = ostr_to_str(&os);
|
|
||||||
ASSERT(result.len > 0);
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Binary Input Stream Tests
|
|
||||||
UNIT_TEST(binary_stream) {
|
|
||||||
u8 data[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
|
|
||||||
buffer_t buffer = {data, sizeof(data)};
|
|
||||||
|
|
||||||
ibstream_t bs = ibstr_init(buffer);
|
|
||||||
|
|
||||||
ASSERT(ibstr_remaining(&bs) == 8);
|
|
||||||
ASSERT(ibstr_tell(&bs) == 0);
|
|
||||||
ASSERT(ibstr_is_finished(&bs) == false);
|
|
||||||
|
|
||||||
u8 val8;
|
|
||||||
ASSERT(ibstr_get_u8(&bs, &val8) == true);
|
|
||||||
ASSERT(val8 == 0x01);
|
|
||||||
|
|
||||||
u16 val16;
|
|
||||||
ASSERT(ibstr_get_u16(&bs, &val16) == true);
|
|
||||||
ASSERT(val16 == 0x0302); // Assuming little-endian
|
|
||||||
|
|
||||||
ibstr_skip(&bs, 1);
|
|
||||||
|
|
||||||
u32 val32;
|
|
||||||
ASSERT(ibstr_get_u32(&bs, &val32) == true);
|
|
||||||
ASSERT(val32 == 0x08070605); // Assuming little-endian
|
|
||||||
|
|
||||||
ASSERT(ibstr_is_finished(&bs) == true);
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
#include "runner.h"
|
|
||||||
#include "../str.h"
|
|
||||||
#include "../arena.h"
|
|
||||||
|
|
||||||
UNIT_TEST(str_format) {
|
|
||||||
arena_t arena = arena_make(ARENA_MALLOC, KB(1));
|
|
||||||
str_t s = str_fmt(&arena, "%d %s", 42, "test");
|
|
||||||
str_t lit = str(&arena, "42 test");
|
|
||||||
ASSERT(str_equals(s, lit));
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
427
tools/nob.c
427
tools/nob.c
|
|
@ -1,427 +0,0 @@
|
||||||
#define COLLA_NO_CONDITION_VARIABLE 1
|
|
||||||
#define COLLA_NO_NET 1
|
|
||||||
|
|
||||||
#include "../build.c"
|
|
||||||
#include <windows.h>
|
|
||||||
#include <direct.h>
|
|
||||||
|
|
||||||
#if COLLA_TCC
|
|
||||||
|
|
||||||
WINBASEAPI LPCH WINAPI GetEnvironmentStringsj(VOID);
|
|
||||||
WINBASEAPI LPWCH WINAPI GetEnvironmentStringsW(VOID);
|
|
||||||
WINBASEAPI BOOL WINAPI FreeEnvironmentStringsA(LPCH penv);
|
|
||||||
WINBASEAPI BOOL WINAPI FreeEnvironmentStringsW(LPWCH penv);
|
|
||||||
#ifdef UNICODE
|
|
||||||
#define GetEnvironmentStrings GetEnvironmentStringsW
|
|
||||||
#define FreeEnvironmentStrings FreeEnvironmentStringsW
|
|
||||||
#else
|
|
||||||
#define GetEnvironmentStrings GetEnvironmentStringsA
|
|
||||||
#define FreeEnvironmentStrings FreeEnvironmentStringsA
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int strv_to_int(strview_t strv) {
|
|
||||||
instream_t in = istr_init(strv);
|
|
||||||
i32 value = 0;
|
|
||||||
if (!istr_get_i32(&in, &value)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t find_vcvars_path(arena_t *arena) {
|
|
||||||
strview_t base_path = strv("C:/Program Files/Microsoft Visual Studio");
|
|
||||||
// find year
|
|
||||||
int year = 0;
|
|
||||||
{
|
|
||||||
arena_t tmp = *arena;
|
|
||||||
|
|
||||||
dir_t *dir = os_dir_open(&tmp, base_path);
|
|
||||||
if (!os_dir_is_valid(dir)) {
|
|
||||||
err("couldn't open directory (%v)", base_path);
|
|
||||||
return STR_EMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
dir_foreach(&tmp, entry, dir) {
|
|
||||||
if (entry->type != DIRTYPE_DIR) continue;
|
|
||||||
|
|
||||||
int number = strv_to_int(strv(entry->name));
|
|
||||||
if (number > year) year = number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (year == 0) {
|
|
||||||
err("couldn't find visual studio year version");
|
|
||||||
return STR_EMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t path_with_year = str_fmt(arena, "%v/%d", base_path, year);
|
|
||||||
|
|
||||||
// find edition
|
|
||||||
const char *editions[] = {
|
|
||||||
"Enterprise",
|
|
||||||
"Professional",
|
|
||||||
"Community",
|
|
||||||
};
|
|
||||||
|
|
||||||
int edition = 0;
|
|
||||||
|
|
||||||
for (; edition < arrlen(editions); ++edition) {
|
|
||||||
arena_t tmp = *arena;
|
|
||||||
str_t path = str_fmt(&tmp, "%v/%s", path_with_year, editions[edition]);
|
|
||||||
if (os_file_exists(strv(path))) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (edition >= arrlen(editions)) {
|
|
||||||
err("couldn't find visual studio edition");
|
|
||||||
return STR_EMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t vcvars = str_fmt(arena, "%v/%s/VC/Auxiliary/Build/vcvars64.bat", path_with_year, editions[edition]);
|
|
||||||
|
|
||||||
return vcvars;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool load_cache(arena_t *arena) {
|
|
||||||
if (!os_file_exists(strv("build/cache.ini"))) {
|
|
||||||
err("build/cache.ini doesn't exist");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
arena_t scratch = *arena;
|
|
||||||
|
|
||||||
ini_t ini = ini_parse(&scratch, strv("build/cache.ini"), &(iniopt_t){ .comment_vals = strv("#") });
|
|
||||||
initable_t *root = ini_get_table(&ini, INI_ROOT);
|
|
||||||
if (!root) fatal("fail");
|
|
||||||
|
|
||||||
for_each (val, root->values) {
|
|
||||||
os_set_env_var(scratch, val->key, val->value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef enum optimise_level_e {
|
|
||||||
OPTIMISE_NONE,
|
|
||||||
OPTIMISE_FAST,
|
|
||||||
OPTIMISE_SMALL,
|
|
||||||
OPTIMISE__COUNT,
|
|
||||||
} optimise_level_e;
|
|
||||||
|
|
||||||
typedef enum warning_level_e {
|
|
||||||
WARNING_NONE,
|
|
||||||
WARNING_DEFAULT,
|
|
||||||
WARNING_ALL,
|
|
||||||
WARNING__COUNT,
|
|
||||||
} warning_level_e;
|
|
||||||
|
|
||||||
typedef enum sanitiser_e {
|
|
||||||
SANITISER_NONE,
|
|
||||||
SANITISER_ADDRESS,
|
|
||||||
SANITISER__COUNT,
|
|
||||||
} sanitiser_e;
|
|
||||||
|
|
||||||
typedef enum cversion_e {
|
|
||||||
CVERSION_LATEST,
|
|
||||||
CVERSION_17,
|
|
||||||
CVERSION_11,
|
|
||||||
CVERSION__COUNT
|
|
||||||
} cversion_e;
|
|
||||||
|
|
||||||
typedef struct options_t options_t;
|
|
||||||
struct options_t {
|
|
||||||
strview_t input_fname;
|
|
||||||
strview_t out_fname;
|
|
||||||
optimise_level_e optimisation;
|
|
||||||
warning_level_e warnings;
|
|
||||||
bool warnings_as_error;
|
|
||||||
sanitiser_e sanitiser;
|
|
||||||
bool fast_math;
|
|
||||||
bool debug;
|
|
||||||
strv_list_t *defines;
|
|
||||||
cversion_e cstd;
|
|
||||||
bool run;
|
|
||||||
strv_list_t *run_args;
|
|
||||||
bool is_cpp;
|
|
||||||
};
|
|
||||||
|
|
||||||
void print_help_message(void) {
|
|
||||||
puts("usage:");
|
|
||||||
puts(" -r / -run [input.c] [args...] compiles and runs <input.c>, forwards <args...>");
|
|
||||||
puts(" -h / -help print this message");
|
|
||||||
puts(" -o / -out [filename] output filename (default: build/<file>.exe)");
|
|
||||||
puts(" -O / -optimise [fast,small] optimisation level");
|
|
||||||
puts(" -w / -warning [default,all] warning level");
|
|
||||||
puts(" -werror treat warnings as errors");
|
|
||||||
puts(" -fsanitize [address] turn on sanitiser");
|
|
||||||
puts(" -fastmath turn on fast math");
|
|
||||||
puts(" -g / -debug generate debug information");
|
|
||||||
puts(" -D / -define [key=value,key] add a preprocessor define ");
|
|
||||||
puts(" -std [c11,c17,clatest] select c standard (default: clatest)");
|
|
||||||
puts(" -cpp compile c++ instead of c");
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
optimise_level_e get_optimisation_level(strview_t arg) {
|
|
||||||
if (strv_equals(arg, strv("fast"))) {
|
|
||||||
return OPTIMISE_FAST;
|
|
||||||
}
|
|
||||||
else if (strv_equals(arg, strv("small"))) {
|
|
||||||
return OPTIMISE_SMALL;
|
|
||||||
}
|
|
||||||
warn("unrecognised optimisation level: (%v)", arg);
|
|
||||||
return OPTIMISE_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
warning_level_e get_warning_level(strview_t arg) {
|
|
||||||
if (strv_equals(arg, strv("default"))) {
|
|
||||||
return WARNING_DEFAULT;
|
|
||||||
}
|
|
||||||
else if (strv_equals(arg, strv("all"))) {
|
|
||||||
return WARNING_ALL;
|
|
||||||
}
|
|
||||||
warn("unrecognised warning level: (%v)", arg);
|
|
||||||
return WARNING_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
sanitiser_e get_sanitiser(strview_t arg) {
|
|
||||||
if (strv_equals(arg, strv("address"))) {
|
|
||||||
return SANITISER_ADDRESS;
|
|
||||||
}
|
|
||||||
warn("unrecognised sanitiser: (%v)", arg);
|
|
||||||
return SANITISER_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
cversion_e get_cversion(strview_t arg) {
|
|
||||||
if (strv_equals(arg, strv("clatest"))) {
|
|
||||||
return CVERSION_LATEST;
|
|
||||||
}
|
|
||||||
else if (strv_equals(arg, strv("c17"))) {
|
|
||||||
return CVERSION_17;
|
|
||||||
}
|
|
||||||
else if (strv_equals(arg, strv("c11"))) {
|
|
||||||
return CVERSION_11;
|
|
||||||
}
|
|
||||||
warn("unrecognised c std version: (%v)", arg);
|
|
||||||
return CVERSION_LATEST;
|
|
||||||
}
|
|
||||||
|
|
||||||
options_t parse_options(arena_t *arena, int argc, char **argv) {
|
|
||||||
options_t out = {0};
|
|
||||||
|
|
||||||
for (int i = 1; i < argc; ++i) {
|
|
||||||
strview_t arg = strv(argv[i]);
|
|
||||||
|
|
||||||
#define CHECK_OPT_BEG() if (false) {}
|
|
||||||
#define CHECK_OPT1(opt) else if (strv_equals(arg, strv("-" opt)))
|
|
||||||
#define CHECK_OPT2(small, big) else if (strv_equals(arg, strv("-" small)) || strv_equals(arg, strv("-" big)))
|
|
||||||
|
|
||||||
#define GET_NEXT_ARG() (i + 1) < argc ? strv(argv[++i]) : STRV_EMPTY
|
|
||||||
|
|
||||||
CHECK_OPT_BEG()
|
|
||||||
CHECK_OPT2("h", "help") {
|
|
||||||
print_help_message();
|
|
||||||
}
|
|
||||||
CHECK_OPT2("o", "out") {
|
|
||||||
strview_t out_fname = GET_NEXT_ARG();
|
|
||||||
str_t out_fname_str = str_fmt(arena, "build/%v", out_fname);
|
|
||||||
out.out_fname = strv(out_fname_str);
|
|
||||||
}
|
|
||||||
CHECK_OPT2("O", "optimise") {
|
|
||||||
out.optimisation = get_optimisation_level(GET_NEXT_ARG());
|
|
||||||
}
|
|
||||||
CHECK_OPT2("w", "warning") {
|
|
||||||
out.warnings = get_warning_level(GET_NEXT_ARG());
|
|
||||||
}
|
|
||||||
CHECK_OPT1("werror") {
|
|
||||||
out.warnings_as_error = true;
|
|
||||||
}
|
|
||||||
CHECK_OPT1("fsanitize") {
|
|
||||||
out.sanitiser = get_sanitiser(GET_NEXT_ARG());
|
|
||||||
}
|
|
||||||
CHECK_OPT1("fastmath") {
|
|
||||||
out.fast_math = true;
|
|
||||||
}
|
|
||||||
CHECK_OPT2("g", "debug") {
|
|
||||||
out.debug = true;
|
|
||||||
}
|
|
||||||
CHECK_OPT2("D", "define") {
|
|
||||||
darr_push(arena, out.defines, GET_NEXT_ARG());
|
|
||||||
}
|
|
||||||
CHECK_OPT1("std") {
|
|
||||||
out.cstd = get_cversion(GET_NEXT_ARG());
|
|
||||||
}
|
|
||||||
CHECK_OPT1("cpp") {
|
|
||||||
out.is_cpp = true;
|
|
||||||
}
|
|
||||||
CHECK_OPT2("r", "run") {
|
|
||||||
out.run = true;
|
|
||||||
out.input_fname = GET_NEXT_ARG();
|
|
||||||
for (i += 1; i < argc; ++i) {
|
|
||||||
darr_push(arena, out.run_args, strv(argv[i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
out.input_fname = arg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#undef CHECK_OPT_BEG
|
|
||||||
#undef CHECK_OPT1
|
|
||||||
#undef CHECK_OPT2
|
|
||||||
#undef GET_NEXT_ARG
|
|
||||||
|
|
||||||
if (strv_is_empty(out.out_fname)) {
|
|
||||||
strview_t name;
|
|
||||||
os_file_split_path(out.input_fname, NULL, &name, NULL);
|
|
||||||
str_t out_fname = str_fmt(arena, "build\\%v", name);
|
|
||||||
out.out_fname = strv(out_fname);
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
|
||||||
os_init();
|
|
||||||
|
|
||||||
arena_t arena = arena_make(ARENA_VIRTUAL, GB(1));
|
|
||||||
|
|
||||||
if (argc < 2) {
|
|
||||||
print_help_message();
|
|
||||||
}
|
|
||||||
|
|
||||||
options_t opt = parse_options(&arena, argc, argv);
|
|
||||||
|
|
||||||
if (!os_dir_exists(strv("build/"))) {
|
|
||||||
info("creating build folder");
|
|
||||||
_mkdir("build");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!os_file_exists(strv("build/cache.ini"))) {
|
|
||||||
info("couldn't find cache.ini, creating it now");
|
|
||||||
|
|
||||||
arena_t scratch = arena;
|
|
||||||
str_t vcvars_path = find_vcvars_path(&scratch);
|
|
||||||
|
|
||||||
os_cmd_t *cmd = NULL;
|
|
||||||
darr_push(&scratch, cmd, strv(vcvars_path));
|
|
||||||
darr_push(&scratch, cmd, strv("&&"));
|
|
||||||
darr_push(&scratch, cmd, strv("set"));
|
|
||||||
darr_push(&scratch, cmd, strv(">"));
|
|
||||||
darr_push(&scratch, cmd, strv("build\\cache.ini"));
|
|
||||||
|
|
||||||
if (!os_run_cmd(scratch, cmd, NULL)) {
|
|
||||||
fatal("failed to run vcvars64.bat");
|
|
||||||
os_abort(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
arena_t scratch = arena;
|
|
||||||
|
|
||||||
if (!load_cache(&scratch)) {
|
|
||||||
os_abort(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
os_cmd_t *cmd = NULL;
|
|
||||||
|
|
||||||
darr_push(&scratch, cmd, strv("cl"));
|
|
||||||
darr_push(&scratch, cmd, strv("/nologo"));
|
|
||||||
darr_push(&scratch, cmd, strv("/utf-8"));
|
|
||||||
if (!opt.is_cpp) {
|
|
||||||
darr_push(&scratch, cmd, strv("/TC"));
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t output = str_fmt(&scratch, "/Fe:%v.exe", opt.out_fname);
|
|
||||||
str_t object = str_fmt(&scratch, "/Fo:%v.obj", opt.out_fname);
|
|
||||||
darr_push(&scratch, cmd, strv(output));
|
|
||||||
darr_push(&scratch, cmd, strv(object));
|
|
||||||
|
|
||||||
strview_t optimisations[OPTIMISE__COUNT] = {
|
|
||||||
strv("/Od"), // disabled
|
|
||||||
strv("/O2"), // fast code
|
|
||||||
strv("/O1"), // small code
|
|
||||||
};
|
|
||||||
darr_push(&scratch, cmd, optimisations[opt.optimisation]);
|
|
||||||
|
|
||||||
strview_t warnings[WARNING__COUNT] = {
|
|
||||||
strv("/W0"),
|
|
||||||
strv("/W3"),
|
|
||||||
strv("/W4"),
|
|
||||||
};
|
|
||||||
darr_push(&scratch, cmd, warnings[opt.warnings]);
|
|
||||||
|
|
||||||
if (opt.warnings_as_error) {
|
|
||||||
darr_push(&scratch, cmd, strv("/WX"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opt.sanitiser) {
|
|
||||||
strview_t sanitisers[SANITISER__COUNT] = {
|
|
||||||
strv(""),
|
|
||||||
strv("/fsanitize=address"),
|
|
||||||
};
|
|
||||||
darr_push(&scratch, cmd, sanitisers[opt.sanitiser]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opt.fast_math) {
|
|
||||||
darr_push(&scratch, cmd, strv("/fp:fast"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opt.debug) {
|
|
||||||
darr_push(&scratch, cmd, strv("/Zi"));
|
|
||||||
darr_push(&scratch, cmd, strv("/D_DEBUG"));
|
|
||||||
}
|
|
||||||
|
|
||||||
for_each (def, opt.defines) {
|
|
||||||
for (int i = 0; i < def->count; ++i) {
|
|
||||||
str_t define = str_fmt(&scratch, "/D%v", def->items[i]);
|
|
||||||
darr_push(&scratch, cmd, strv(define));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
strview_t cversion[CVERSION__COUNT] = {
|
|
||||||
strv("clatest"),
|
|
||||||
strv("c17"),
|
|
||||||
strv("c11"),
|
|
||||||
};
|
|
||||||
|
|
||||||
str_t cstd = str_fmt(&scratch, "/std:%v", cversion[opt.cstd]);
|
|
||||||
darr_push(&scratch, cmd, strv(cstd));
|
|
||||||
|
|
||||||
darr_push(&scratch, cmd, opt.input_fname);
|
|
||||||
|
|
||||||
// /LD -> create dynamic lib
|
|
||||||
// /LDd -> create debug dynamic lib
|
|
||||||
// /link
|
|
||||||
|
|
||||||
if (!os_run_cmd(scratch, cmd, NULL)) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opt.run) {
|
|
||||||
arena_t scratch = arena;
|
|
||||||
os_cmd_t *cmd = NULL;
|
|
||||||
|
|
||||||
darr_push(&scratch, cmd, opt.out_fname);
|
|
||||||
|
|
||||||
for_each (arg, opt.run_args) {
|
|
||||||
for (int i = 0; i < arg->count; ++i) {
|
|
||||||
darr_push(&scratch, cmd, arg->items[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!os_run_cmd(scratch, cmd, NULL)) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
arena_cleanup(&arena);
|
|
||||||
|
|
||||||
os_cleanup();
|
|
||||||
}
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
#pragma section(".CRT$XCU", read)
|
|
||||||
|
|
||||||
#include "../build.c"
|
|
||||||
#include "../tests/runner.h"
|
|
||||||
|
|
||||||
#include "../tests/arena_tests.c"
|
|
||||||
#include "../tests/core_tests.c"
|
|
||||||
#include "../tests/net_tests.c"
|
|
||||||
#include "../tests/os_tests.c"
|
|
||||||
// #include "../tests/parsers_tests.c"
|
|
||||||
// #include "../tests/pretty_print_tests.c"
|
|
||||||
#include "../tests/str_tests.c"
|
|
||||||
|
|
||||||
unit_test_t *test_head = NULL;
|
|
||||||
unit_test_t *test_tail = NULL;
|
|
||||||
const char *last_fail_reason = NULL;
|
|
||||||
bool last_failed = false;
|
|
||||||
|
|
||||||
void ut_register(const char *file, const char *name, void (*fn)(void)) {
|
|
||||||
strview_t fname;
|
|
||||||
os_file_split_path(strv(file), NULL, &fname, NULL);
|
|
||||||
|
|
||||||
fname = strv_remove_suffix(fname, arrlen("_tests") - 1);
|
|
||||||
|
|
||||||
unit_test_t *test = calloc(1, sizeof(unit_test_t));
|
|
||||||
test->name = strv(name);
|
|
||||||
test->fn = fn;
|
|
||||||
test->fname = fname;
|
|
||||||
|
|
||||||
olist_push(test_head, test_tail, test);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
colla_init(COLLA_ALL);
|
|
||||||
arena_t arena = arena_make(ARENA_VIRTUAL, GB(1));
|
|
||||||
|
|
||||||
strview_t last_file = STRV_EMPTY;
|
|
||||||
int success = 0;
|
|
||||||
int failed = 0;
|
|
||||||
int total = 0;
|
|
||||||
|
|
||||||
unit_test_t *test = test_head;
|
|
||||||
while (test) {
|
|
||||||
if (!strv_equals(test->fname, last_file)) {
|
|
||||||
last_file = test->fname;
|
|
||||||
pretty_print(arena, "<blue>> %v</>\n", test->fname);
|
|
||||||
}
|
|
||||||
|
|
||||||
test->fn();
|
|
||||||
|
|
||||||
total++;
|
|
||||||
|
|
||||||
if (last_failed) {
|
|
||||||
pretty_print(arena, "%4s<red>[X]</> %v: %s\n", "", test->name, last_fail_reason);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
pretty_print(arena, "%4s<green>[V]</> %v\n", "", test->name);
|
|
||||||
success++;
|
|
||||||
}
|
|
||||||
|
|
||||||
last_failed = false;
|
|
||||||
|
|
||||||
test = test->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
print("\n");
|
|
||||||
|
|
||||||
strview_t colors[] = {
|
|
||||||
cstrv("red"),
|
|
||||||
cstrv("light_red"),
|
|
||||||
cstrv("yellow"),
|
|
||||||
cstrv("light_yellow"),
|
|
||||||
cstrv("light_green"),
|
|
||||||
cstrv("green"),
|
|
||||||
};
|
|
||||||
|
|
||||||
usize col = success * (arrlen(colors) - 1) / total;
|
|
||||||
|
|
||||||
pretty_print(arena, "<%v>%d</>/<blue>%d</> tests passed\n", colors[col], success, total);
|
|
||||||
}
|
|
||||||
353
win/net_win32.c
353
win/net_win32.c
|
|
@ -1,353 +0,0 @@
|
||||||
#include "../net.h"
|
|
||||||
#include "../os.h"
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
#if !COLLA_TCC
|
|
||||||
#include <wininet.h>
|
|
||||||
#include <winsock2.h>
|
|
||||||
#include <ws2tcpip.h>
|
|
||||||
#else
|
|
||||||
#include "../tcc/colla_tcc.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if COLLA_CMT_LIB
|
|
||||||
#pragma comment(lib, "Wininet")
|
|
||||||
#pragma comment(lib, "Ws2_32")
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct {
|
|
||||||
HINTERNET internet;
|
|
||||||
} http_win = {0};
|
|
||||||
|
|
||||||
void net_init(void) {
|
|
||||||
if (http_win.internet) return;
|
|
||||||
http_win.internet = InternetOpen(
|
|
||||||
TEXT("COLLA_WININET"),
|
|
||||||
INTERNET_OPEN_TYPE_PRECONFIG,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
WSADATA wsdata = {0};
|
|
||||||
if (WSAStartup(0x0202, &wsdata)) {
|
|
||||||
fatal("couldn't startup sockets: %v", os_get_error_string(WSAGetLastError()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void net_cleanup(void) {
|
|
||||||
if (!http_win.internet) return;
|
|
||||||
InternetCloseHandle(http_win.internet);
|
|
||||||
http_win.internet = NULL;
|
|
||||||
WSACleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
iptr net_get_last_error(void) {
|
|
||||||
return WSAGetLastError();
|
|
||||||
}
|
|
||||||
|
|
||||||
http_res_t http_request(http_request_desc_t *req) {
|
|
||||||
return http_request_cb(req, NULL, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
http_res_t http_request_cb(http_request_desc_t *req, http_request_callback_fn callback, void *userdata) {
|
|
||||||
HINTERNET connection = NULL;
|
|
||||||
HINTERNET request = NULL;
|
|
||||||
BOOL result = FALSE;
|
|
||||||
bool success = false;
|
|
||||||
http_res_t res = {0};
|
|
||||||
arena_t arena_before = *req->arena;
|
|
||||||
|
|
||||||
if (!http_win.internet) {
|
|
||||||
err("net_init has not been called");
|
|
||||||
goto failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
http_url_t split = http_split_url(req->url);
|
|
||||||
strview_t server = split.host;
|
|
||||||
strview_t page = split.uri;
|
|
||||||
|
|
||||||
if (strv_starts_with_view(server, strv("http://"))) {
|
|
||||||
server = strv_remove_prefix(server, 7);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strv_starts_with_view(server, strv("https://"))) {
|
|
||||||
server = strv_remove_prefix(server, 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
arena_t scratch = *req->arena;
|
|
||||||
|
|
||||||
if (req->version.major == 0) req->version.major = 1;
|
|
||||||
if (req->version.minor == 0) req->version.minor = 1;
|
|
||||||
|
|
||||||
const TCHAR *accepted_types[] = { TEXT("*/*"), NULL };
|
|
||||||
const char *method = http_get_method_string(req->request_type);
|
|
||||||
str_t http_ver = str_fmt(&scratch, "HTTP/%u.%u", req->version.major, req->version.minor);
|
|
||||||
|
|
||||||
tstr_t tserver = strv_to_tstr(&scratch, server);
|
|
||||||
tstr_t tpage = strv_to_tstr(&scratch, page);
|
|
||||||
tstr_t tmethod = strv_to_tstr(&scratch, strv(method));
|
|
||||||
tstr_t thttp_ver = strv_to_tstr(&scratch, strv(http_ver));
|
|
||||||
|
|
||||||
connection = InternetConnect(
|
|
||||||
http_win.internet,
|
|
||||||
tserver.buf,
|
|
||||||
INTERNET_DEFAULT_HTTPS_PORT,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
INTERNET_SERVICE_HTTP,
|
|
||||||
0,
|
|
||||||
(DWORD_PTR)NULL // userdata
|
|
||||||
);
|
|
||||||
if (!connection) {
|
|
||||||
err("call to InternetConnect failed: %u", os_get_error_string(os_get_last_error()));
|
|
||||||
goto failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
request = HttpOpenRequest(
|
|
||||||
connection,
|
|
||||||
tmethod.buf,
|
|
||||||
tpage.buf,
|
|
||||||
thttp_ver.buf,
|
|
||||||
NULL,
|
|
||||||
accepted_types,
|
|
||||||
INTERNET_FLAG_SECURE,
|
|
||||||
(DWORD_PTR)NULL // userdata
|
|
||||||
);
|
|
||||||
if (!request) {
|
|
||||||
err("call to HttpOpenRequest failed: %v", os_get_error_string(os_get_last_error()));
|
|
||||||
goto failed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < req->header_count; ++i) {
|
|
||||||
http_header_t *h = &req->headers[i];
|
|
||||||
arena_t scratch = *req->arena;
|
|
||||||
|
|
||||||
str_t header = str_fmt(&scratch, "%v: %v\r\n", h->key, h->value);
|
|
||||||
tstr_t theader = strv_to_tstr(&scratch, strv(header));
|
|
||||||
HttpAddRequestHeaders(request, theader.buf, (DWORD)theader.len, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
result = HttpSendRequest(
|
|
||||||
request,
|
|
||||||
NULL,
|
|
||||||
0,
|
|
||||||
(void *)req->body.buf,
|
|
||||||
(DWORD)req->body.len
|
|
||||||
);
|
|
||||||
if (!result) {
|
|
||||||
iptr error = os_get_last_error();
|
|
||||||
if (error == ERROR_INTERNET_NAME_NOT_RESOLVED) {
|
|
||||||
err("invalid url: %v", req->url);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
err("call to HttpSendRequest failed: %lld", error);
|
|
||||||
// os_get_error_string(error));
|
|
||||||
}
|
|
||||||
goto failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
u8 smallbuf[KB(5)];
|
|
||||||
DWORD bufsize = sizeof(smallbuf);
|
|
||||||
|
|
||||||
u8 *buffer = smallbuf;
|
|
||||||
|
|
||||||
// try and read it into a static buffer
|
|
||||||
result = HttpQueryInfo(request, HTTP_QUERY_RAW_HEADERS_CRLF, smallbuf, &bufsize, NULL);
|
|
||||||
|
|
||||||
// buffer is not big enough, allocate one with the arena instead
|
|
||||||
if (!result && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
|
|
||||||
info("buffer is too small");
|
|
||||||
buffer = alloc(req->arena, u8, bufsize + 1);
|
|
||||||
result = HttpQueryInfo(request, HTTP_QUERY_RAW_HEADERS_CRLF, buffer, &bufsize, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
err("couldn't get headers");
|
|
||||||
goto failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
tstr_t theaders = { (TCHAR *)buffer, bufsize };
|
|
||||||
str_t headers = str_from_tstr(req->arena, theaders);
|
|
||||||
|
|
||||||
res.headers = http_parse_headers(req->arena, strv(headers));
|
|
||||||
res.version = req->version;
|
|
||||||
|
|
||||||
DWORD status_code = 0;
|
|
||||||
DWORD status_code_len = sizeof(status_code);
|
|
||||||
result = HttpQueryInfo(request, HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_STATUS_CODE, &status_code, &status_code_len, 0);
|
|
||||||
if (!result) {
|
|
||||||
err("couldn't get status code");
|
|
||||||
goto failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status_code = status_code;
|
|
||||||
|
|
||||||
outstream_t body = ostr_init(req->arena);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
DWORD read = 0;
|
|
||||||
char read_buffer[4096];
|
|
||||||
BOOL read_success = InternetReadFile(
|
|
||||||
request, read_buffer, sizeof(read_buffer), &read
|
|
||||||
);
|
|
||||||
if (!read_success || read == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
strview_t chunk = strv(read_buffer, read);
|
|
||||||
if (callback) {
|
|
||||||
callback(chunk, userdata);
|
|
||||||
}
|
|
||||||
ostr_puts(&body, chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.body = strv(ostr_to_str(&body));
|
|
||||||
|
|
||||||
success = true;
|
|
||||||
|
|
||||||
failed:
|
|
||||||
if (request) InternetCloseHandle(request);
|
|
||||||
if (connection) InternetCloseHandle(connection);
|
|
||||||
if (!success) *req->arena = arena_before;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SOCKETS //////////////////////////
|
|
||||||
|
|
||||||
SOCKADDR_IN sk__addrin_in(const char *ip, u16 port) {
|
|
||||||
SOCKADDR_IN sk_addr = {
|
|
||||||
.sin_family = AF_INET,
|
|
||||||
.sin_port = htons(port),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!inet_pton(AF_INET, ip, &sk_addr.sin_addr)) {
|
|
||||||
err("inet_pton failed: %v", os_get_error_string(net_get_last_error()));
|
|
||||||
return (SOCKADDR_IN){0};
|
|
||||||
}
|
|
||||||
|
|
||||||
return sk_addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
socket_t sk_open(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
return socket(AF_INET, sock_type, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
socket_t sk_open_protocol(const char *protocol) {
|
|
||||||
struct protoent *proto = getprotobyname(protocol);
|
|
||||||
if(!proto) {
|
|
||||||
return INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
return socket(AF_INET, SOCK_STREAM, proto->p_proto);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool sk_is_valid(socket_t sock) {
|
|
||||||
return sock != INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool sk_close(socket_t sock) {
|
|
||||||
return closesocket(sock) != SOCKET_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool sk_bind(socket_t sock, const char *ip, u16 port) {
|
|
||||||
SOCKADDR_IN sk_addr = sk__addrin_in(ip, port);
|
|
||||||
if (sk_addr.sin_family == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return bind(sock, (SOCKADDR*)&sk_addr, sizeof(sk_addr)) != SOCKET_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool sk_listen(socket_t sock, int backlog) {
|
|
||||||
return listen(sock, backlog) != SOCKET_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
socket_t sk_accept(socket_t sock) {
|
|
||||||
SOCKADDR_IN addr = {0};
|
|
||||||
int addr_size = sizeof(addr);
|
|
||||||
return accept(sock, (SOCKADDR*)&addr, &addr_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool sk_connect(socket_t sock, const char *server, u16 server_port) {
|
|
||||||
u8 tmpbuf[1024] = {0};
|
|
||||||
arena_t scratch = arena_make(ARENA_STATIC, sizeof(tmpbuf), tmpbuf);
|
|
||||||
|
|
||||||
str16_t wserver = strv_to_str16(&scratch, strv(server));
|
|
||||||
|
|
||||||
ADDRINFOW *addrinfo = NULL;
|
|
||||||
int result = GetAddrInfoW(wserver.buf, NULL, NULL, &addrinfo);
|
|
||||||
if (result) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
char ip_str[1024] = {0};
|
|
||||||
inet_ntop(addrinfo->ai_family, addrinfo->ai_addr, ip_str, sizeof(ip_str));
|
|
||||||
|
|
||||||
SOCKADDR_IN sk_addr = sk__addrin_in(ip_str, server_port);
|
|
||||||
if (sk_addr.sin_family == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return connect(sock, (SOCKADDR*)&sk_addr, sizeof(sk_addr)) != SOCKET_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sk_send(socket_t sock, const void *buf, int len) {
|
|
||||||
return send(sock, (const char *)buf, len, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sk_recv(socket_t sock, void *buf, int len) {
|
|
||||||
return recv(sock, (char *)buf, len, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sk_poll(skpoll_t *to_poll, int num_to_poll, int timeout) {
|
|
||||||
return WSAPoll((WSAPOLLFD*)to_poll, (ULONG)num_to_poll, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
oshandle_t sk_bind_event(socket_t sock, skevent_e event) {
|
|
||||||
if (event == SOCK_EVENT_NONE) {
|
|
||||||
return os_handle_zero();
|
|
||||||
}
|
|
||||||
|
|
||||||
HANDLE handle = WSACreateEvent();
|
|
||||||
if (handle == WSA_INVALID_EVENT) {
|
|
||||||
return os_handle_zero();
|
|
||||||
}
|
|
||||||
|
|
||||||
int wsa_event = 0;
|
|
||||||
if (event & SOCK_EVENT_READ) wsa_event |= FD_READ;
|
|
||||||
if (event & SOCK_EVENT_WRITE) wsa_event |= FD_WRITE;
|
|
||||||
if (event & SOCK_EVENT_ACCEPT) wsa_event |= FD_ACCEPT;
|
|
||||||
if (event & SOCK_EVENT_CONNECT) wsa_event |= FD_CONNECT;
|
|
||||||
if (event & SOCK_EVENT_CLOSE) wsa_event |= FD_CLOSE;
|
|
||||||
|
|
||||||
if (WSAEventSelect(sock, handle, wsa_event) != 0) {
|
|
||||||
WSACloseEvent(handle);
|
|
||||||
return os_handle_zero();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (oshandle_t){ .data = (uptr)handle };
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_reset_event(oshandle_t handle) {
|
|
||||||
if (!os_handle_valid(handle)) {
|
|
||||||
warn("invalid handle");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
WSAResetEvent((HANDLE)handle.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_destroy_event(oshandle_t handle) {
|
|
||||||
if (!os_handle_valid(handle)) return;
|
|
||||||
WSACloseEvent((HANDLE)handle.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
#include "../str.h"
|
|
||||||
#include "../arena.h"
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
#if COLLA_TCC
|
|
||||||
#include "../tcc/colla_tcc.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
str_t str_os_from_str16(arena_t *arena, str16_t src) {
|
|
||||||
str_t out = {0};
|
|
||||||
|
|
||||||
int outlen = WideCharToMultiByte(
|
|
||||||
CP_UTF8, 0,
|
|
||||||
src.buf, (int)src.len,
|
|
||||||
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.buf);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
err("couldn't translate wide string (%S) to utf8, %v", src.buf, os_get_error_string(os_get_last_error()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return STR_EMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
out.buf = alloc(arena, char, outlen + 1);
|
|
||||||
WideCharToMultiByte(
|
|
||||||
CP_UTF8, 0,
|
|
||||||
src.buf, (int)src.len,
|
|
||||||
out.buf, outlen,
|
|
||||||
NULL, NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
out.len = outlen;
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
str16_t strv_os_to_str16(arena_t *arena, strview_t src) {
|
|
||||||
str16_t out = {0};
|
|
||||||
|
|
||||||
if (strv_is_empty(src)) {
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
int len = MultiByteToWideChar(
|
|
||||||
CP_UTF8, 0,
|
|
||||||
src.buf, (int)src.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", src);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
err("couldn't translate string (%v) to a wide string, %v", src, os_get_error_string(os_get_last_error()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
out.buf = alloc(arena, wchar_t, len + 1);
|
|
||||||
|
|
||||||
MultiByteToWideChar(
|
|
||||||
CP_UTF8, 0,
|
|
||||||
src.buf, (int)src.len,
|
|
||||||
out.buf, len
|
|
||||||
);
|
|
||||||
|
|
||||||
out.len = len;
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue