This commit is contained in:
snarmph 2025-03-21 19:09:14 +01:00
parent 01f4ad7f62
commit 6d36aa4442
100 changed files with 5138 additions and 13015 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.vscode/

196
arena.c
View file

@ -1,39 +1,44 @@
#include "arena.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "vmem.h"
#include "tracelog.h"
#include "os.h"
static uintptr_t arena__align(uintptr_t ptr, usize align) {
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(byte *buf, usize len);
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 arenaInit(const arena_desc_t *desc) {
arena_t malloc_arena = {
.type = ARENA_MALLOC_ALWAYS,
};
arena_t arena_init(const arena_desc_t *desc) {
arena_t out = {0};
if (desc) {
switch (desc->type) {
case ARENA_VIRTUAL: out = arena__make_virtual(desc->allocation); break;
case ARENA_MALLOC: out = arena__make_malloc(desc->allocation); break;
case ARENA_STATIC: out = arena__make_static(desc->static_buffer, desc->allocation); break;
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;
default: break;
}
}
return out;
}
void arenaCleanup(arena_t *arena) {
void arena_cleanup(arena_t *arena) {
if (!arena) {
return;
}
@ -42,147 +47,184 @@ void arenaCleanup(arena_t *arena) {
case ARENA_VIRTUAL: arena__free_virtual(arena); break;
case ARENA_MALLOC: arena__free_malloc(arena); break;
// ARENA_STATIC does not need to be freed
case ARENA_STATIC: break;
default: break;
}
arena->start = NULL;
arena->current = NULL;
arena->end = NULL;
arena->type = 0;
memset(arena, 0, sizeof(arena_t));
}
arena_t arenaScratch(arena_t *arena, usize size) {
uint8 *buffer = alloc(arena, uint8, size, ALLOC_SOFT_FAIL | ALLOC_NOZERO);
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 *arenaAlloc(const arena_alloc_desc_t *desc) {
void *arena_alloc(const arena_alloc_desc_t *desc) {
if (!desc || !desc->arena || desc->arena->type == ARENA_TYPE_NONE) {
return NULL;
}
usize total = desc->size * desc->count;
arena_t *arena = desc->arena;
arena->current = (byte *)arena__align((uintptr_t)arena->current, desc->align);
u8 *ptr = NULL;
if (total > arenaRemaining(arena)) {
if (desc->flags & ALLOC_SOFT_FAIL) {
return NULL;
}
fatal("finished space in arena, tried to allocate %_$$$dB out of %_$$$dB\n", total, arenaRemaining(arena));
abort();
switch (arena->type) {
case ARENA_MALLOC_ALWAYS:
ptr = arena__alloc_malloc_always(desc);
break;
default:
ptr = arena__alloc_common(desc);
break;
}
if (arena->type == ARENA_VIRTUAL) {
usize allocated = arenaTell(arena);
usize page_end = vmemPadToPage(allocated);
usize new_cur = allocated + total;
if (new_cur > page_end) {
usize extra_mem = vmemPadToPage(new_cur - page_end);
usize page_size = vmemGetPageSize();
// TODO is this really correct?
usize num_of_pages = (extra_mem / page_size) + 1;
assert(num_of_pages > 0);
if (!vmemCommit(arena->current, num_of_pages + 1)) {
if (desc->flags & ALLOC_SOFT_FAIL) {
return NULL;
}
printf("failed to commit memory for virtual arena, tried to commit %zu pages\n", num_of_pages);
exit(1);
}
}
}
byte *ptr = arena->current;
arena->current += total;
usize total = desc->size * desc->count;
return desc->flags & ALLOC_NOZERO ? ptr : memset(ptr, 0, total);
}
usize arenaTell(arena_t *arena) {
return arena ? arena->current - arena->start : 0;
usize arena_tell(arena_t *arena) {
return arena ? arena->cur - arena->beg : 0;
}
usize arenaRemaining(arena_t *arena) {
return arena && (arena->current < arena->end) ? arena->end - arena->current : 0;
usize arena_remaining(arena_t *arena) {
return arena && (arena->cur < arena->end) ? arena->end - arena->cur : 0;
}
void arenaRewind(arena_t *arena, usize from_start) {
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;
}
assert(arenaTell(arena) >= from_start);
assert(arena_tell(arena) >= from_start);
arena->current = arena->start + from_start;
arena->cur = arena->beg + from_start;
}
void arenaPop(arena_t *arena, usize amount) {
void arena_pop(arena_t *arena, usize amount) {
if (!arena) {
return;
}
usize position = arenaTell(arena);
usize position = arena_tell(arena);
if (!position) {
return;
}
arenaRewind(arena, position - amount);
arena_rewind(arena, position - amount);
}
// == VIRTUAL ARENA ====================================================================================================
static arena_t arena__make_virtual(usize size) {
usize alloc_size = 0;
byte *ptr = vmemInit(size, &alloc_size);
if (!vmemCommit(ptr, 1)) {
vmemRelease(ptr);
u8 *ptr = os_reserve(size, &alloc_size);
if (!os_commit(ptr, 1)) {
os_release(ptr, alloc_size);
ptr = NULL;
}
return (arena_t){
.start = ptr,
.current = ptr,
.beg = ptr,
.cur = ptr,
.end = ptr ? ptr + alloc_size : NULL,
.type = ARENA_VIRTUAL,
};
}
static void arena__free_virtual(arena_t *arena) {
if (!arena->start) {
if (!arena->beg) {
return;
}
bool success = vmemRelease(arena->start);
bool success = os_release(arena->beg, arena_capacity(arena));
assert(success && "Failed arena free");
}
// == MALLOC ARENA =====================================================================================================
extern void *malloc(usize size);
extern void free(void *ptr);
static arena_t arena__make_malloc(usize size) {
byte *ptr = malloc(size);
u8 *ptr = malloc(size);
assert(ptr);
return (arena_t) {
.start = ptr,
.current = ptr,
.beg = ptr,
.cur = ptr,
.end = ptr ? ptr + size : NULL,
.type = ARENA_MALLOC,
};
}
static void arena__free_malloc(arena_t *arena) {
free(arena->start);
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) {
fatal("finished space in arena, tried to allocate %_$$$dB out of %_$$$dB\n", total, arena_remaining(arena));
}
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;
assert(num_of_pages > 0);
if (!os_commit(arena->cur, num_of_pages + 1)) {
if (!soft_fail) {
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;
return ptr;
}
static void *arena__alloc_malloc_always(const arena_alloc_desc_t *desc) {
usize total = desc->size * desc->count;
// TODO: alignment?
u8 *ptr = malloc(total);
if (!ptr && !(desc->flags & ALLOC_SOFT_FAIL)) {
fatal("malloc call failed for %_$$$dB", total);
}
return ptr;
}
// == STATIC ARENA =====================================================================================================
static arena_t arena__make_static(byte *buf, usize len) {
static arena_t arena__make_static(u8 *buf, usize len) {
return (arena_t) {
.start = buf,
.current = buf,
.beg = buf,
.cur = buf,
.end = buf ? buf + len : NULL,
.type = ARENA_STATIC,
};
}

77
arena.h
View file

@ -1,66 +1,73 @@
#pragma once
#ifndef COLLA_ARENA_H
#define COLLA_ARENA_H
#include "collatypes.h"
#include "core.h"
#if COLLA_TCC
#define alignof __alignof__
#else
#define alignof _Alignof
#if COLLA_WIN && !COLLA_TCC
#define alignof __alignof
#endif
typedef enum {
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 {
typedef enum alloc_flags_e {
ALLOC_FLAGS_NONE = 0,
ALLOC_NOZERO = 1 << 0,
ALLOC_SOFT_FAIL = 1 << 1,
} alloc_flags_e;
typedef struct arena_t {
uint8 *start;
uint8 *current;
uint8 *end;
typedef struct arena_t arena_t;
struct arena_t {
u8 *beg;
u8 *cur;
u8 *end;
arena_type_e type;
} arena_t;
};
typedef struct {
typedef struct arena_desc_t arena_desc_t;
struct arena_desc_t {
arena_type_e type;
usize allocation;
byte *static_buffer;
} arena_desc_t;
usize size;
u8 *static_buffer;
};
typedef struct {
typedef struct arena_alloc_desc_t arena_alloc_desc_t;
struct arena_alloc_desc_t {
arena_t *arena;
usize count;
alloc_flags_e flags;
usize align;
usize size;
} arena_alloc_desc_t;
#ifndef ARENA_NO_SIZE_HELPERS
#define KB(count) ( (count) * 1024)
#define MB(count) (KB(count) * 1024)
#define GB(count) (MB(count) * 1024)
#endif
};
// arena_type_e type, usize allocation, [ byte *static_buffer ]
#define arenaMake(...) arenaInit(&(arena_desc_t){ __VA_ARGS__ })
#define arena_make(...) arena_init(&(arena_desc_t){ __VA_ARGS__ })
// arena_t *arena, T type, [ usize count, alloc_flags_e flags, usize align, usize size ]
#define alloc(arenaptr, type, ...) arenaAlloc(&(arena_alloc_desc_t){ .size = sizeof(type), .count = 1, .align = alignof(type), .arena = arenaptr, __VA_ARGS__ })
#define alloc(arenaptr, type, ...) arena_alloc(&(arena_alloc_desc_t){ .size = sizeof(type), .count = 1, .align = alignof(type), .arena = arenaptr, __VA_ARGS__ })
arena_t arenaInit(const arena_desc_t *desc);
void arenaCleanup(arena_t *arena);
// 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 arenaScratch(arena_t *arena, usize size);
arena_t arena_init(const arena_desc_t *desc);
void arena_cleanup(arena_t *arena);
void *arenaAlloc(const arena_alloc_desc_t *desc);
usize arenaTell(arena_t *arena);
usize arenaRemaining(arena_t *arena);
void arenaRewind(arena_t *arena, usize from_start);
void arenaPop(arena_t *arena, usize amount);
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

100
base64.c
View file

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

View file

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

54
bits.h
View file

@ -1,54 +0,0 @@
#pragma once
#include "collatypes.h"
uint32 bitsCtz(uint32 v);
uint32 bitsNextPow2(uint32 v);
// == INLINE IMPLEMENTATION ==============================================================
#if COLLA_MSVC
#define BITS_WIN 1
#define BITS_LIN 0
#include <intrin.h>
#elif COLLA_GCC || COLLA_CLANG || COLLA_EMC
#define BITS_WIN 0
#define BITS_LIN 1
#elif COLLA_TCC
#define BITS_WIN 0
#define BITS_LIN 0
#else
#error "bits header not supported on this compiler"
#endif
#include "tracelog.h"
inline uint32 bitsCtz(uint32 v) {
#if BITS_LIN
return v ? __builtin_ctz(v) : 0;
#elif BITS_WIN
uint32 trailing = 0;
return _BitScanForward((unsigned long *)&trailing, v) ? trailing : 32;
#else
if (v == 0) return 0;
for (uint32 i = 0; i < 32; ++i) {
if (v & (1 << i)) return i;
}
return 32;
#endif
}
inline uint32 bitsNextPow2(uint32 v) {
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
return v;
}
#undef BITS_WIN
#undef BITS_LIN

57
build.c
View file

@ -1,52 +1,13 @@
#if COLLA_ONLYCORE
#define COLLA_NOTHREADS 1
#define COLLA_NOSOCKETS 1
#define COLLA_NOHTTP 1
#define COLLA_NOSERVER 1
#define COLLA_NOHOTRELOAD 1
#endif
#if COLLA_NOSOCKETS
#undef COLLA_NOHTTP
#undef COLLA_NOSERVER
#define COLLA_NOHTTP 1
#define COLLA_NOSERVER 1
#include "core.h"
#if COLLA_TCC
#define COLLA_NO_CONDITION_VARIABLE 1
#endif
#include "core.c"
#include "os.c"
#include "arena.c"
#include "base64.c"
#include "file.c"
#include "format.c"
#include "ini.c"
#include "json.c"
#include "str.c"
#include "strstream.c"
#include "tracelog.c"
#include "utf8.c"
#include "vmem.c"
#include "xml.c"
#include "sha1.c"
#include "markdown.c"
#include "highlight.c"
#include "dir.c"
#if !COLLA_NOTHREADS
#include "cthreads.c"
#endif
#if !COLLA_NOSOCKETS
#include "socket.c"
#include "websocket.c"
#endif
#if !COLLA_NOHTTP
#include "http.c"
#endif
#if !COLLA_NOSERVER
#include "server.c"
#endif
#if !COLLA_NOHOTRELOAD
#include "hot_reload.c"
#endif
#include "parsers.c"
#include "net.c"
#include "darr.h"

View file

@ -1,115 +0,0 @@
#pragma once
#define arrlen(a) (sizeof(a) / sizeof((a)[0]))
#define for_each(it, list) for (typeof(list) it = list; it; it = it->next)
#if defined(_DEBUG) || !defined(NDEBUG)
#define COLLA_DEBUG 1
#define COLLA_RELEASE 0
#else
#define COLLA_DEBUG 0
#define COLLA_RELEASE 1
#endif
#if defined(_WIN32)
#define COLLA_WIN 1
#define COLLA_OSX 0
#define COLLA_LIN 0
#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_WIN
#undef NOMINMAX
#undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#endif
#undef min
#undef max
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))

View file

@ -1,37 +0,0 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include "colladefines.h"
typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned int uint;
typedef uint8_t uint8;
typedef uint16_t uint16;
typedef uint32_t uint32;
typedef uint64_t uint64;
typedef int8_t int8;
typedef int16_t int16;
typedef int32_t int32;
typedef int64_t int64;
typedef size_t usize;
typedef ptrdiff_t isize;
typedef uint8 byte;
typedef struct {
uint8 *data;
usize len;
} buffer_t;
#if COLLA_WIN && defined(UNICODE)
typedef wchar_t TCHAR;
#else
typedef char TCHAR;
#endif

75
core.c Normal file
View file

@ -0,0 +1,75 @@
#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 net_init(void);
extern void os_cleanup(void);
extern void net_cleanup(void);
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 (modules & COLLA_NET) {
net_init();
}
}
void colla_cleanup(void) {
colla_modules_e modules = colla__initialised_modules;
if (modules & COLLA_OS) {
os_cleanup();
}
if (modules & COLLA_NET) {
net_cleanup();
}
}
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);
}

199
core.h Normal file
View file

@ -0,0 +1,199 @@
#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);
/////////////////////////////////////////////////
// USEFUL MACROS ////////////////////////////////
#define arrlen(a) (sizeof(a) / sizeof((a)[0]))
#define COLLA_UNUSED(v) (void)(v)
#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)
/////////////////////////////////////////////////
// LINKED LISTS /////////////////////////////////
#define list_push_n(list, item, next) ((item)->next=(list), (list)=(item))
#define list_pop_n(list, next) ((list) = (list) ? (list)->next : NULL)
#define list_push(list, item) list_push_n(list, item, next)
#define list_pop(list) list_pop_n(list, next)
#define dlist_push_pn(list, item, next, prev) if (item) (item)->next = (list); if (list) (list)->prev = (item); (list) = (item)
#define dlist_pop_pn(list, item, next, prev) 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)
#define dlist_push(list, item) dlist_push_pn(list, item, next, prev)
#define dlist_pop(list, item) dlist_pop_pn(list, item, next, prev)
#define for_each(it, list) for (typeof(list) it = list; it; it = it->next)
/////////////////////////////////////////////////
// OS AND COMPILER MACROS ///////////////////////
#if defined(_DEBUG) || !defined(NDEBUG)
#define COLLA_DEBUG 1
#define COLLA_RELEASE 0
#else
#define COLLA_DEBUG 0
#define COLLA_RELEASE 1
#endif
#if defined(_WIN32)
#define COLLA_WIN 1
#define COLLA_OSX 0
#define COLLA_LIN 0
#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
/////////////////////////////////////////////////
// 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

View file

@ -1,282 +0,0 @@
#include "cthreads.h"
#include <stdlib.h>
// TODO: swap calloc for arenas
typedef struct {
cthread_func_t func;
void *arg;
} _thr_internal_t;
#if COLLA_WIN
#include <windows.h>
// == THREAD ===========================================
static DWORD WINAPI _thrFuncInternal(void *arg) {
_thr_internal_t *params = (_thr_internal_t *)arg;
cthread_func_t func = params->func;
void *argument = params->arg;
free(params);
return (DWORD)func(argument);
}
cthread_t thrCreate(cthread_func_t func, void *arg) {
HANDLE thread = INVALID_HANDLE_VALUE;
_thr_internal_t *params = calloc(1, sizeof(_thr_internal_t));
if(params) {
params->func = func;
params->arg = arg;
thread = CreateThread(NULL, 0, _thrFuncInternal, params, 0, NULL);
}
return (cthread_t)thread;
}
bool thrValid(cthread_t ctx) {
return (HANDLE)ctx != INVALID_HANDLE_VALUE;
}
bool thrDetach(cthread_t ctx) {
return CloseHandle((HANDLE)ctx);
}
cthread_t thrCurrent(void) {
return (cthread_t)GetCurrentThread();
}
int thrCurrentId(void) {
return GetCurrentThreadId();
}
int thrGetId(cthread_t ctx) {
#if COLLA_TCC
return 0;
#endif
return GetThreadId((HANDLE)ctx);
}
void thrExit(int code) {
ExitThread(code);
}
bool thrJoin(cthread_t ctx, int *code) {
if(!ctx) return false;
int return_code = WaitForSingleObject((HANDLE)ctx, INFINITE);
if(code) *code = return_code;
BOOL success = CloseHandle((HANDLE)ctx);
return return_code != WAIT_FAILED && success;
}
// == MUTEX ============================================
cmutex_t mtxInit(void) {
CRITICAL_SECTION *crit_sec = calloc(1, sizeof(CRITICAL_SECTION));
if(crit_sec) {
InitializeCriticalSection(crit_sec);
}
return (cmutex_t)crit_sec;
}
void mtxFree(cmutex_t ctx) {
DeleteCriticalSection((CRITICAL_SECTION *)ctx);
free((CRITICAL_SECTION *)ctx);
}
bool mtxValid(cmutex_t ctx) {
return (void *)ctx != NULL;
}
bool mtxLock(cmutex_t ctx) {
EnterCriticalSection((CRITICAL_SECTION *)ctx);
return true;
}
bool mtxTryLock(cmutex_t ctx) {
return TryEnterCriticalSection((CRITICAL_SECTION *)ctx);
}
bool mtxUnlock(cmutex_t ctx) {
LeaveCriticalSection((CRITICAL_SECTION *)ctx);
return true;
}
#if !COLLA_NO_CONDITION_VAR
// == CONDITION VARIABLE ===============================
#include "tracelog.h"
condvar_t condInit(void) {
CONDITION_VARIABLE *cond = calloc(1, sizeof(CONDITION_VARIABLE));
InitializeConditionVariable(cond);
return (condvar_t)cond;
}
void condFree(condvar_t cond) {
free((CONDITION_VARIABLE *)cond);
}
void condWake(condvar_t cond) {
WakeConditionVariable((CONDITION_VARIABLE *)cond);
}
void condWakeAll(condvar_t cond) {
WakeAllConditionVariable((CONDITION_VARIABLE *)cond);
}
void condWait(condvar_t cond, cmutex_t mtx) {
SleepConditionVariableCS((CONDITION_VARIABLE *)cond, (CRITICAL_SECTION *)mtx, INFINITE);
}
void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) {
SleepConditionVariableCS((CONDITION_VARIABLE *)cond, (CRITICAL_SECTION *)mtx, milliseconds);
}
#endif
#else
#include <pthread.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
// == THREAD ===========================================
#define INT_TO_VOIDP(a) ((void *)((uintptr_t)(a)))
static void *_thrFuncInternal(void *arg) {
_thr_internal_t *params = (_thr_internal_t *)arg;
cthread_func_t func = params->func;
void *argument = params->arg;
free(params);
return INT_TO_VOIDP(func(argument));
}
cthread_t thrCreate(cthread_func_t func, void *arg) {
pthread_t handle = (pthread_t)NULL;
_thr_internal_t *params = calloc(1, sizeof(_thr_internal_t));
if(params) {
params->func = func;
params->arg = arg;
int result = pthread_create(&handle, NULL, _thrFuncInternal, params);
if(result) handle = (pthread_t)NULL;
}
return (cthread_t)handle;
}
bool thrValid(cthread_t ctx) {
return (void *)ctx != NULL;
}
bool thrDetach(cthread_t ctx) {
return pthread_detach((pthread_t)ctx) == 0;
}
cthread_t thrCurrent(void) {
return (cthread_t)pthread_self();
}
int thrCurrentId(void) {
return (int)pthread_self();
}
int thrGetId(cthread_t ctx) {
return (int)ctx;
}
void thrExit(int code) {
pthread_exit(INT_TO_VOIDP(code));
}
bool thrJoin(cthread_t ctx, int *code) {
void *result = code;
return pthread_join((pthread_t)ctx, &result) != 0;
}
// == MUTEX ============================================
cmutex_t mtxInit(void) {
pthread_mutex_t *mutex = calloc(1, sizeof(pthread_mutex_t));
if(mutex) {
if(pthread_mutex_init(mutex, NULL)) {
free(mutex);
mutex = NULL;
}
}
return (cmutex_t)mutex;
}
void mtxFree(cmutex_t ctx) {
pthread_mutex_destroy((pthread_mutex_t *)ctx);
}
bool mtxValid(cmutex_t ctx) {
return (void *)ctx != NULL;
}
bool mtxLock(cmutex_t ctx) {
return pthread_mutex_lock((pthread_mutex_t *)ctx) == 0;
}
bool mtxTryLock(cmutex_t ctx) {
return pthread_mutex_trylock((pthread_mutex_t *)ctx) == 0;
}
bool mtxUnlock(cmutex_t ctx) {
return pthread_mutex_unlock((pthread_mutex_t *)ctx) == 0;
}
#if !COLLA_NO_CONDITION_VAR
// == CONDITION VARIABLE ===============================
condvar_t condInit(void) {
pthread_cond_t *cond = calloc(1, sizeof(pthread_cond_t));
if(cond) {
if(pthread_cond_init(cond, NULL)) {
free(cond);
cond = NULL;
}
}
return (condvar_t)cond;
}
void condFree(condvar_t cond) {
if (!cond) return;
pthread_cond_destroy((pthread_cond_t *)cond);
free((pthread_cond_t *)cond);
}
void condWake(condvar_t cond) {
pthread_cond_signal((pthread_cond_t *)cond);
}
void condWakeAll(condvar_t cond) {
pthread_cond_broadcast((pthread_cond_t *)cond);
}
void condWait(condvar_t cond, cmutex_t mtx) {
pthread_cond_wait((pthread_cond_t *)cond, (pthread_mutex_t *)mtx);
}
void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) {
struct timespec timeout;
time(&timeout.tv_sec);
timeout.tv_nsec += milliseconds * 1000000;
pthread_cond_timedwait((pthread_cond_t *)cond, (pthread_mutex_t *)mtx, &timeout);
}
#endif
#endif

View file

@ -1,57 +0,0 @@
#pragma once
#include "collatypes.h"
#if COLLA_TCC
#define COLLA_NO_CONDITION_VAR 1
#endif
typedef struct arena_t arena_t;
// == THREAD ===========================================
typedef uintptr_t cthread_t;
typedef int (*cthread_func_t)(void *);
cthread_t thrCreate(cthread_func_t func, void *arg);
bool thrValid(cthread_t ctx);
bool thrDetach(cthread_t ctx);
cthread_t thrCurrent(void);
int thrCurrentId(void);
int thrGetId(cthread_t ctx);
void thrExit(int code);
bool thrJoin(cthread_t ctx, int *code);
// == MUTEX ============================================
typedef uintptr_t cmutex_t;
cmutex_t mtxInit(void);
void mtxFree(cmutex_t ctx);
bool mtxValid(cmutex_t ctx);
bool mtxLock(cmutex_t ctx);
bool mtxTryLock(cmutex_t ctx);
bool mtxUnlock(cmutex_t ctx);
#if !COLLA_NO_CONDITION_VAR
// == CONDITION VARIABLE ===============================
typedef uintptr_t condvar_t;
#define COND_WAIT_INFINITE 0xFFFFFFFF
condvar_t condInit(void);
void condFree(condvar_t cond);
void condWake(condvar_t cond);
void condWakeAll(condvar_t cond);
void condWait(condvar_t cond, cmutex_t mtx);
void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds);
#endif

82
darr.h Normal file
View file

@ -0,0 +1,82 @@
#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

163
dir.c
View file

@ -1,163 +0,0 @@
#include "dir.h"
#if COLLA_WIN
#include <windows.h>
typedef struct dir_t {
WIN32_FIND_DATA find_data;
HANDLE handle;
dir_entry_t cur_entry;
dir_entry_t next_entry;
} dir_t;
static dir_entry_t dir__entry_from_find_data(arena_t *arena, WIN32_FIND_DATA *fd) {
dir_entry_t out = {0};
out.name = str(arena, fd->cFileName);
if (fd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
out.type = DIRTYPE_DIR;
}
else {
LARGE_INTEGER filesize = {
.LowPart = fd->nFileSizeLow,
.HighPart = fd->nFileSizeHigh,
};
out.filesize = filesize.QuadPart;
}
return out;
}
dir_t *dirOpen(arena_t *arena, strview_t path) {
uint8 tmpbuf[1024] = {0};
arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf);
TCHAR *winpath = strvToTChar(&scratch, path);
// get a little extra leeway
TCHAR fullpath[MAX_PATH + 16] = {0};
DWORD pathlen = GetFullPathName(winpath, MAX_PATH, fullpath, NULL);
// add asterisk at the end of the path
if (fullpath[pathlen ] != '\\' && fullpath[pathlen] != '/') {
fullpath[pathlen++] = '\\';
}
fullpath[pathlen++] = '*';
fullpath[pathlen++] = '\0';
dir_t *ctx = alloc(arena, dir_t);
ctx->handle = FindFirstFile(fullpath, &ctx->find_data);
if (ctx->handle == INVALID_HANDLE_VALUE) {
arenaPop(arena, sizeof(dir_t));
return NULL;
}
ctx->next_entry = dir__entry_from_find_data(arena, &ctx->find_data);
return ctx;
}
void dirClose(dir_t *ctx) {
FindClose(ctx->handle);
ctx->handle = INVALID_HANDLE_VALUE;
}
bool dirIsValid(dir_t *ctx) {
return ctx && ctx->handle != INVALID_HANDLE_VALUE;
}
dir_entry_t *dirNext(arena_t *arena, dir_t *ctx) {
if (!dirIsValid(ctx)) {
return NULL;
}
ctx->cur_entry = ctx->next_entry;
ctx->next_entry = (dir_entry_t){0};
if (FindNextFile(ctx->handle, &ctx->find_data)) {
ctx->next_entry = dir__entry_from_find_data(arena, &ctx->find_data);
}
else {
dirClose(ctx);
}
return &ctx->cur_entry;
}
#elif COLLA_POSIX
#include <dirent.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
// taken from https://sites.uclouvain.be/SystInfo/usr/include/dirent.h.html
// hopefully shouldn't be needed
#ifndef DT_DIR
#define DT_DIR 4
#endif
#ifndef DT_REG
#define DT_REG 8
#endif
typedef struct dir_t {
DIR *dir;
dir_entry_t next;
} dir_t;
dir_t *dirOpen(arena_t *arena, strview_t path) {
if (strvIsEmpty(path)) {
err("path cannot be null");
return NULL;
}
uint8 tmpbuf[1024];
arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf);
str_t dirpath = str(&scratch, path);
DIR *dir = opendir(dirpath.buf);
if (!dir) {
err("could not open dir (%v)", path);
return NULL;
}
dir_t *ctx = alloc(arena, dir_t);
ctx->dir = dir;
return ctx;
}
void dirClose(dir_t *ctx) {
if (ctx) {
closedir(ctx->dir);
}
}
bool dirIsValid(dir_t *ctx) {
return ctx && ctx->dir;
}
dir_entry_t *dirNext(arena_t *arena, dir_t *ctx) {
if (!ctx) return NULL;
struct dirent *dp = readdir(ctx->dir);
if (!dp) {
dirClose(ctx);
return NULL;
}
ctx->next.name = (str_t){ dp->d_name, strlen(dp->d_name) };
ctx->next.type = dp->d_type == DT_DIR ? DIRTYPE_DIR : DIRTYPE_FILE;
ctx->next.filesize = 0;
if (dp->d_type == DT_REG) {
struct stat file_info = {0};
stat(dp->d_name, &file_info);
ctx->next.filesize = file_info.st_size;
}
return &ctx->next;
}
#endif

25
dir.h
View file

@ -1,25 +0,0 @@
#pragma once
#include "str.h"
#include "arena.h"
typedef struct dir_t dir_t;
typedef enum {
DIRTYPE_FILE,
DIRTYPE_DIR,
} dir_type_e;
typedef struct {
str_t name;
dir_type_e type;
usize filesize;
} dir_entry_t;
dir_t *dirOpen(arena_t *arena, strview_t path);
// optional, only call this if you want to return before dirNext returns NULL
void dirClose(dir_t *ctx);
bool dirIsValid(dir_t *ctx);
dir_entry_t *dirNext(arena_t *arena, dir_t *ctx);

View file

@ -1,128 +0,0 @@
# Arena
-----------
An arena is a bump allocator, under the hood it can use one of 3 strategies to allocate its data:
* `Virtual`: allocates with virtual memory, meaning that it reserves the data upfront, but only allocates one page at a time (usually 64 Kb). This is the preferred way to use the arena as it can freely allocate gigabytes of memory for free
* `Malloc`: allocates the memory upfront using malloc
* `Static`: uses a buffer passed by the user instead of allocating
To help with allocating big chunks of data, `arena.h` defines the macros `KB`, `MB`, and `GB`. If you don't need them you can define `ARENA_NO_SIZE_HELPERS`
To create an arena use the macro `arenaMake`:
```c
arena_t varena = arenaMake(ARENA_VIRTUAL, GB(1));
uint8 buffer[1024];
arena_t sarena = arenaMake(ARENA_STATIC, sizeof(buffer), buffer);
```
To allocate use the `alloc` macro. The parameters to allocate are:
* A pointer to the arena
* The type to allocate
* (optional) the number of items to allocate
* (optional) flags:
* `ALLOC_NOZERO`: by default the arena sets the memory to zero before returning, use this if you want to skip it
* `ALLOC_SOFT_FAIL`: by default the arena panics when an allocation fails, meaning that it never returns `NULL`.
* (automatic) the align of the type
* (automatic) the size of the type
Example usage:
```c
// allocate 15 strings
str_t *string_list = alloc(arena, str_t, 15);
// allocate a 1024 bytes buffer
uint8 *buffer = alloc(arena, uint8, 1024);
// allocate a structure
game_t *game = alloc(arena, game_t);
```
The strength of the arena is that it makes it much easier to reason about lifetimes, for instance:
```c
// pass a pointer to the arena for the data that we need to return
WCHAR *win32_get_full_path(arena_t *arena, const char *rel_path) {
// we need a small scratch arena for allocations that
// will be thrown away at the end of this function
uint8 scratch_buf[1024];
arena_t scratch = arenaMake(ARENA_STATIC, sizeof(scratch_buf, scratch_buf));
WCHAR *win32_rel_path = str_to_wchar(&scratch, rel_path);
DWORD pathlen = GetFullPathName(win32_rel_path, 0, NULL, NULL);
WCHAR *fullpath = alloc(arena, WCHAR, pathlen + 1);
GetFullPath(win32_rel_path, pathlen + 1, fullpath, NULL);
// notice how we don't need to free anything at the end
return fullpath;
}
```
There are a few helper functions:
* `arenaScratch`: sub allocate an arena from another arena
* `arenaTell`: returns the number of bytes allocated
* `arenaRemaining`: returns the number of bytes that can still be allocated
* `arenaRewind`: rewinds the arena to N bytes from the start (effectively "freeing" that memory)
* `arenaPop`: pops N bytes from the arena (effectively "freeing" that memory)
Here is an example usage of a full program:
```c
typedef struct {
char *buf;
usize len;
} str_t;
str_t read_file(arena_t *arena, const char *filename) {
str_t out = {0};
FILE *fp = fopen(filename, "rb");
if (!fp) goto error;
fseek(fp, 0, SEEK_END);
out.len = ftell(fp);
fseek(fp, 0, SEEK_SET);
out.buf = alloc(arena, char, out.len + 1);
fread(out.buf, 1, out.len, fp);
error:
return out;
}
void write_file(const char *filename, str_t data) {
FILE *fp = fopen(filename, "wb");
if (!fp) return;
fwrite(data.buf, 1, data.len, fp);
}
int main() {
arena_t arena = arenaMake(ARENA_VIRTUAL, GB(1));
str_t title = {0};
{
uint8 tmpbuf[KB(5)];
arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf);
str_t file = read_file(&scratch, "file.json");
json_t json = json_parse(&scratch, file);
title = str_dup(arena, json_get(json, "title"));
}
{
// copying an arena by value effectively makes it a scratch arena,
// as long as you don't use the original inside the same scope!
arena_t scratch = arena;
str_t to_write = str_fmt(
&scratch,
"{ \"title\": \"%s\" }", title.buf
);
write_file("out.json", to_write);
}
// cleanup all allocations at once
arenaCleanup(&arena);
}
```

View file

@ -1,29 +0,0 @@
# Base 64
----------
The `base64.h` header has only two functions, one to encode and one to decode to/from base 64.
Example usage:
```c
char *recv_callback(arena_t *arena, buffer_t msg) {
buffer_t decoded = base64Decode(arena, msg);
alloc(arena, char); // allocate an extra char for the null pointer
return (char *)decoded.data;
}
buffer_t send_callback(arena_t *arena) {
char msg[] = "Hello World!";
buffer_t buf = {
.data = msg,
.len = arrlen(msg) - 1, // don't include the null pointer
};
buffer_t encoded = base64Encode(arena, buf);
return encoded;
}
int main() {
arena_t arena = arenaMake(ARENA_VIRTUAL, GB(1));
run_server(&arena, 8080, recv_callback, send_callback);
arenaCleanup(&arena);
}
```

View file

@ -1,46 +0,0 @@
# Threads
----------
Cross platform threads, mutexes and conditional variables.
The api is very similar to pthreads, here is a small example program that uses threads and mutexes.
```c
struct {
bool exit;
cmutex_t mtx;
} state = {0};
int f1(void *) {
mtxLock(state.mtx);
state.exit = true;
mtxUnlock(state.mtx);
return 0;
}
int f2(void *) {
while (true) {
bool exit = false;
if (mtxTryLock(state.mtx)) {
exit = state.exit;
mtxUnlock(state.mtx);
}
if (exit) {
break;
}
}
return 0;
}
int main() {
state.mtx = mtxInit();
cthread_t t1 = thrCreate(f1, NULL);
thrDetach(t1);
cthread_t t2 = thrCreate(f2, NULL);
thrJoin(t2, NULL);
mtxFree(state.mtx);
}
```

View file

@ -1,49 +0,0 @@
# Dir
----------
This header provides a simple directory walker, here is an example usage:
```c
typedef struct source_t {
str_t filename;
struct source_t *next;
} source_t;
sources_t get_sources(arena_t *arena, strview_t path) {
uint8 tmpbuf[KB(5)] = {0};
arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf);
dir_t *dir = dirOpen(&scratch, path);
dir_entry_t *entry = NULL;
source_t *sources = NULL;
while ((entry = dirNext(&scratch, dir))) {
if (entry->type != DIRTYPE_FILE) {
continue;
}
strview_t ext = fileGetExtension(strv(entry->name));
if (!strvEquals(ext, strv(".c"))) {
continue;
}
source_t *new_source = alloc(arena, source_t);
new_source->filename = strFmt(arena, "%v/%v", path, entry->name);
new_source->next = sources;
sources = new_source;
}
return sources;
}
int main() {
arena_t arena = arenaMake(ARENA_VIRTUAL, GB(1));
source_t *sources = get_sources(&arena, strv("src/colla"));
while (sources) {
info("> %v", sources->filename);
sources = sources->next;
}
arenaCleanup(&arena);
}
```

Binary file not shown.

View file

@ -1,38 +0,0 @@
# File
----------
This header provides cross platform file functionality.
It has all the basics that you can expect which work exactly like the stdio counterparts:
* `fileOpen`
* `fileClose`
* `fileIsValid`
* `fileRead`
* `fileWrite`
* `fileSeekEnd`
* `fileRewind`
* `fileTell`
Then there are a few helpers functions for reading / writing:
* Writing:
* `filePutc`
* `filePuts`
* `filePrintf`
* `fileWriteWhole`
* Reading:
* `fileReadWhole`
* `fileReadWholeStr`
There are also some functions to get info about a file without having to open it:
* `fileExists`
* `fileSize`
* `fileGetTime`
* `fileHasChanged`
And finally, there are some helper functions:
* `fileGetFullPath` (for windows)
* `fileSplitPath` / `fileGetFilename` / `fileGetExtension`
* `fileDelete`

View file

@ -1,25 +0,0 @@
# Format
----------
Small formatting utility, it has 2 functions (and the `va_list` alternatives):
* `fmtPrint`: equivalent to
* `fmtBuffer`
It uses [stb_sprintf](https://github.com/nothings/stb/blob/master/stb_sprintf.h) under the hood, but it also has support for printing buffers using `%v` (structures that are a pair of pointer/size)
This means it can print strings and [string views](/str).
In
Usage example:
```c
int main() {
strview_t v = strv("world");
char buffer[1024] = {0};
fmtPrint("Hello %v!", v);
fmtBuffer(buffer, sizeof(buffer), "Hello %v!", v);
}
```

View file

@ -1,34 +0,0 @@
# Highlight
----------
This header provides an highlighter for c-like languages (mostly c).
The usage is simple, first create a highlight context using hlInit, for which you need an hl_config_t. The only mandatory argument is colors, which are the strings put before the keywords in the highlighted text.
You can also pass some flags:
* `HL_FLAG_HTML`: escapes html characters
If you're using the offline documentation, this is the code used to highlight inside the markdown code blocks (simplified to remove actual markdown parsing):
```c
str_t highlight_code_block(arena_t *arena, strview_t code) {
uint8 tmpbuf[KB(5)];
arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf);
hl_ctx_t *hl = hlInit(&scratch, &(hl_config_t){
.colors = {
[HL_COLOR_NORMAL] = strv("</span>"),
[HL_COLOR_PREPROC] = strv("<span class=\"c-preproc\">"),
[HL_COLOR_TYPES] = strv("<span class=\"c-types\">"),
[HL_COLOR_CUSTOM_TYPES] = strv("<span class=\"c-custom-types\">"),
[HL_COLOR_KEYWORDS] = strv("<span class=\"c-kwrds\">"),
[HL_COLOR_NUMBER] = strv("<span class=\"c-num\">"),
[HL_COLOR_STRING] = strv("<span class=\"c-str\">"),
[HL_COLOR_COMMENT] = strv("<span class=\"c-cmnt\">"),
[HL_COLOR_FUNC] = strv("<span class=\"c-func\">"),
[HL_COLOR_SYMBOL] = strv("<span class=\"c-sym\">"),
[HL_COLOR_MACRO] = strv("<span class=\"c-macro\">"),
},
.flags = HL_FLAG_HTML,
});
}
```

View file

@ -1,76 +0,0 @@
# Hot Reload
----------
This header provides cross-platform library hot-reloading. To use it you need to have to entry points, one for the host and one for the client.
In the client you can then implement these functions:
* `hr_init`: called when the library is loaded (or reloaded)
* `hr_loop`: called every "tick" (or whenever the host decides)
* `hr_close`: called when the host finishes
In the client you need to call these functions:
* `hrOpen`: load the library and call `hr_init`
* `hrStep`: call `hr_loop`
* `hrReload`: check if the library has changed, and if so reload it and call `hr_init` again
* `hrClose`: call `hr_close` and cleanup
Example usage:
### Client
```c
int hr_init(hr_t *ctx) {
uint8 tmpbuf[KB(5)];
arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf);
state_t *state = ctx->userdata;
uint64 timestamp = fileGetTime(scratch, strv("sprite.png"));
if (timestamp > state->last_write) {
state->last_write = timestamp;
destroy_image(state->sprite);
state->sprite = load_image(strv("sprite.png"));
}
}
int hr_loop(hr_t *ctx) {
state_t *state = ctx->userdata;
draw_image(state->sprite, state->posx, state->posy);
}
int hr_close(hr_t *ctx) {
state_t *state = ctx->userdata;
destroy_image(state->sprite);
}
```
### Host
```c
typedef struct {
hr_t hr;
image_t sprite;
int posx;
int posy;
uint64 last_write;
} state_t;
int main() {
arena_t arena = arenaMake(ARENA_VIRTUAL, GB(1));
state_t state = {0};
state.hr.userdata = &state;
if (!hrOpen(&state.hr, strv("bin/client.dll"))) {
return 1;
}
while (game_poll()) {
hrReload(&state.hr);
hrStep(&state.hr);
}
hrClose(&state.hr, true);
}
```

View file

@ -1,42 +0,0 @@
# HTML
----------
This header provides an easy (although debatably sane) way to generate html in c.
There are three types of tags:
* One and done tags, like `<img>` or `<hr>` which only have an opening tag
* Basic tags which follow this format: `<tag>content</tag>`
* Tags where the content is probably other tags
You can open and close any tags using `tagBeg` and `tagEnd`, you can also set attributes like this:
```c
tagBeg("div", .class="content", .id="main");
```
Finally, you can use the `htmlRaw` macro to write raw html.
Example code:
```c
str_t generate_page(arena_t *arena, page_t *data) {
str_t out = STR_EMPTY;
htmlBeg(arena, &out);
headBeg();
title(data->title);
htmlRaw(<script src="script.js"></script>)
style(data->css);
headEnd();
bodyBeg();
divBeg(.id="main");
h1("Hello World!");
hr();
p("Some content blah blah");
img(.src="image.png");
divEnd();
bodyEnd();
htmlEnd();
return out;
}
```

View file

@ -1,2 +0,0 @@
# HTTP
----------

View file

@ -1,2 +0,0 @@
# Ini
----------

View file

@ -1,2 +0,0 @@
# Json
----------

View file

@ -1,2 +0,0 @@
# Markdown
----------

View file

@ -1,4 +0,0 @@
# Colla
----------
Colla is a library that I use personally for all my C projects. It doesn't have one specific purpose, but more a collection of useful things that I've written.

View file

@ -1,2 +0,0 @@
# Server
----------

View file

@ -1,2 +0,0 @@
# SHA-1
----------

View file

@ -1,2 +0,0 @@
# Socket
----------

View file

@ -1,2 +0,0 @@
# Str
----------

View file

@ -1,2 +0,0 @@
# StrStream
----------

View file

@ -1,2 +0,0 @@
# Tracelog
----------

View file

@ -1,2 +0,0 @@
# UTF-8
----------

View file

@ -1,2 +0,0 @@
# Vec
----------

View file

@ -1,2 +0,0 @@
# VMem
----------

View file

@ -1,2 +0,0 @@
# WebSocket
----------

View file

@ -1,2 +0,0 @@
# Xml
----------

398
file.c
View file

@ -1,398 +0,0 @@
#include "file.h"
#include "warnings/colla_warn_beg.h"
#include "tracelog.h"
#include "format.h"
#define FILE_MAKE_SCRATCH() \
uint8 tmpbuf[KB(1)]; \
arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf)
#if COLLA_WIN
#include <windows.h>
#undef FILE_BEGIN
#undef FILE_CURRENT
#undef FILE_END
#define FILE_BEGIN 0
#define FILE_CURRENT 1
#define FILE_END 2
#if COLLA_TCC
#include "tcc/colla_tcc.h"
#endif
static DWORD file__mode_to_access(filemode_e mode) {
if (mode & FILE_APPEND) return FILE_APPEND_DATA;
DWORD out = 0;
if (mode & FILE_READ) out |= GENERIC_READ;
if (mode & FILE_WRITE) out |= GENERIC_WRITE;
return out;
}
static DWORD file__mode_to_creation(filemode_e mode) {
if (mode == FILE_READ) return OPEN_EXISTING;
if (mode == FILE_WRITE) return CREATE_ALWAYS;
return OPEN_ALWAYS;
}
bool fileExists(strview_t path) {
FILE_MAKE_SCRATCH();
str_t name = str(&scratch, path);
return GetFileAttributesA(name.buf) != INVALID_FILE_ATTRIBUTES;
}
TCHAR *fileGetFullPath(arena_t *arena, strview_t filename) {
FILE_MAKE_SCRATCH();
TCHAR long_path_prefix[] = TEXT("\\\\?\\");
const usize prefix_len = arrlen(long_path_prefix) - 1;
TCHAR *rel_path = strvToTChar(&scratch, filename);
DWORD pathlen = GetFullPathName(rel_path, 0, NULL, NULL);
TCHAR *full_path = alloc(arena, TCHAR, pathlen + prefix_len + 1);
memcpy(full_path, long_path_prefix, prefix_len * sizeof(TCHAR));
GetFullPathName(rel_path, pathlen + 1, full_path + prefix_len, NULL);
return full_path;
}
bool fileDelete(strview_t filename) {
FILE_MAKE_SCRATCH();
wchar_t *wfname = strvToWChar(&scratch, filename, NULL);
return DeleteFileW(wfname);
}
file_t fileOpen(strview_t name, filemode_e mode) {
FILE_MAKE_SCRATCH();
TCHAR *full_path = fileGetFullPath(&scratch, name);
HANDLE handle = CreateFile(
full_path,
file__mode_to_access(mode),
FILE_SHARE_READ,
NULL,
file__mode_to_creation(mode),
FILE_ATTRIBUTE_NORMAL,
NULL
);
return (file_t){
.handle = (uintptr_t)handle,
};
}
void fileClose(file_t ctx) {
if (!fileIsValid(ctx)) return;
CloseHandle((HANDLE)ctx.handle);
}
bool fileIsValid(file_t ctx) {
return (HANDLE)ctx.handle != 0 &&
(HANDLE)ctx.handle != INVALID_HANDLE_VALUE;
}
usize fileRead(file_t ctx, void *buf, usize len) {
if (!fileIsValid(ctx)) return 0;
DWORD read = 0;
ReadFile((HANDLE)ctx.handle, buf, (DWORD)len, &read, NULL);
return (usize)read;
}
usize fileWrite(file_t ctx, const void *buf, usize len) {
if (!fileIsValid(ctx)) return 0;
DWORD written = 0;
WriteFile((HANDLE)ctx.handle, buf, (DWORD)len, &written, NULL);
return (usize)written;
}
bool fileSeek(file_t ctx, usize pos) {
if (!fileIsValid(ctx)) return false;
LARGE_INTEGER offset = {
.QuadPart = pos,
};
DWORD result = SetFilePointer((HANDLE)ctx.handle, offset.LowPart, &offset.HighPart, FILE_BEGIN);
return result != INVALID_SET_FILE_POINTER;
}
bool fileSeekEnd(file_t ctx) {
if (!fileIsValid(ctx)) return false;
DWORD result = SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_END);
return result != INVALID_SET_FILE_POINTER;
}
void fileRewind(file_t ctx) {
if (!fileIsValid(ctx)) return;
SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_BEGIN);
}
usize fileTell(file_t ctx) {
if (!fileIsValid(ctx)) return 0;
LARGE_INTEGER tell = {0};
BOOL result = SetFilePointerEx((HANDLE)ctx.handle, (LARGE_INTEGER){0}, &tell, FILE_CURRENT);
return result == TRUE ? (usize)tell.QuadPart : 0;
}
usize fileSize(file_t ctx) {
if (!fileIsValid(ctx)) return 0;
LARGE_INTEGER size = {0};
BOOL result = GetFileSizeEx((HANDLE)ctx.handle, &size);
return result == TRUE ? (usize)size.QuadPart : 0;
}
uint64 fileGetTimeFP(file_t ctx) {
if (!fileIsValid(ctx)) return 0;
FILETIME time = {0};
GetFileTime((HANDLE)ctx.handle, NULL, NULL, &time);
ULARGE_INTEGER utime = {
.HighPart = time.dwHighDateTime,
.LowPart = time.dwLowDateTime,
};
return (uint64)utime.QuadPart;
}
#else
#include <stdio.h>
static const char *file__mode_to_stdio(filemode_e mode) {
if (mode == FILE_READ) return "rb";
if (mode == FILE_WRITE) return "wb";
if (mode == FILE_APPEND) return "ab";
if (mode == (FILE_READ | FILE_WRITE)) return "rb+";
return "ab+";
}
bool fileExists(strview_t path) {
FILE_MAKE_SCRATCH();
str_t name = str(&scratch, path);
FILE *fp = fopen(name.buf, "rb");
bool exists = fp != NULL;
fclose(fp);
return exists;
}
bool fileDelete(strview_t filename) {
FILE_MAKE_SCRATCH();
str_t name = str(&scratch, filename);
return remove(name.buf) == 0;
}
file_t fileOpen(strview_t name, filemode_e mode) {
FILE_MAKE_SCRATCH();
str_t filename = str(&scratch, name);
return (file_t) {
.handle = (uintptr_t)fopen(filename.buf, file__mode_to_stdio(mode))
};
}
void fileClose(file_t ctx) {
FILE *fp = (FILE *)ctx.handle;
if (fp) {
fclose(fp);
}
}
bool fileIsValid(file_t ctx) {
bool is_valid = (FILE *)ctx.handle != NULL;
if (!is_valid) warn("file not valid");
return is_valid;
}
usize fileRead(file_t ctx, void *buf, usize len) {
if (!fileIsValid(ctx)) return 0;
return fread(buf, 1, len, (FILE *)ctx.handle);
}
usize fileWrite(file_t ctx, const void *buf, usize len) {
if (!fileIsValid(ctx)) return 0;
return fwrite(buf, 1, len, (FILE *)ctx.handle);
}
bool fileSeek(file_t ctx, usize pos) {
assert(pos < INT32_MAX);
if (!fileIsValid(ctx)) return false;
return fseek((FILE *)ctx.handle, (long)pos, SEEK_SET) == 0;
}
bool fileSeekEnd(file_t ctx) {
if (!fileIsValid(ctx)) return false;
return fseek((FILE *)ctx.handle, 0, SEEK_END) == 0;
}
void fileRewind(file_t ctx) {
if (!fileIsValid(ctx)) return;
fseek((FILE *)ctx.handle, 0, SEEK_SET);
}
usize fileTell(file_t ctx) {
if (!fileIsValid(ctx)) return 0;
return ftell((FILE *)ctx.handle);
}
usize fileSize(file_t ctx) {
if (!fileIsValid(ctx)) return 0;
FILE *fp = (FILE *)ctx.handle;
fseek(fp, 0, SEEK_END);
long len = ftell(fp);
fseek(fp, 0, SEEK_SET);
return (usize)len;
}
uint64 fileGetTimeFP(file_t ctx) {
#if COLLA_LIN
return 0;
#else
fatal("fileGetTime not implemented yet outside of linux and windows");
return 0;
#endif
}
#endif
strview_t fileGetFilename(strview_t path) {
usize last_lin = strvRFind(path, '/', 0);
usize last_win = strvRFind(path, '\\', 0);
last_lin = last_lin != SIZE_MAX ? last_lin : 0;
last_win = last_win != SIZE_MAX ? last_win : 0;
usize last = max(last_lin, last_win);
return strvSub(path, last ? last + 1 : last, SIZE_MAX);
}
strview_t fileGetExtension(strview_t path) {
usize ext_pos = strvRFind(path, '.', 0);
return strvSub(path, ext_pos, SIZE_MAX);
}
void fileSplitPath(strview_t path, strview_t *dir, strview_t *name, strview_t *ext) {
usize dir_lin = strvRFind(path, '/', 0);
usize dir_win = strvRFind(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 = strvRFind(path, '.', 0);
if (dir) {
*dir = strvSub(path, 0, dir_pos);
}
if (name) {
*name = strvSub(path, dir_pos ? dir_pos + 1 : dir_pos, ext_pos);
}
if (ext) {
*ext = strvSub(path, ext_pos, SIZE_MAX);
}
}
bool filePutc(file_t ctx, char c) {
return fileWrite(ctx, &c, 1) == 1;
}
bool filePuts(file_t ctx, strview_t v) {
return fileWrite(ctx, v.buf, v.len) == v.len;
}
bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
bool result = filePrintfv(scratch, ctx, fmt, args);
va_end(args);
return result;
}
bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args) {
str_t string = strFmtv(&scratch, fmt, args);
return fileWrite(ctx, string.buf, string.len) == string.len;
}
buffer_t fileReadWhole(arena_t *arena, strview_t name) {
file_t fp = fileOpen(name, FILE_READ);
if (!fileIsValid(fp)) {
err("could not open file: %v", name);
return (buffer_t){0};
}
buffer_t out = fileReadWholeFP(arena, fp);
fileClose(fp);
return out;
}
buffer_t fileReadWholeFP(arena_t *arena, file_t ctx) {
if (!fileIsValid(ctx)) return (buffer_t){0};
buffer_t out = {0};
out.len = fileSize(ctx);
out.data = alloc(arena, uint8, out.len);
usize read = fileRead(ctx, out.data, out.len);
if (read != out.len) {
err("fileReadWholeFP: fileRead failed, should be %zu but is %zu", out.len, read);
arenaPop(arena, out.len);
return (buffer_t){0};
}
return out;
}
str_t fileReadWholeStr(arena_t *arena, strview_t name) {
file_t fp = fileOpen(name, FILE_READ);
if (!fileIsValid(fp)) {
warn("could not open file (%v)", name);
}
str_t out = fileReadWholeStrFP(arena, fp);
fileClose(fp);
return out;
}
str_t fileReadWholeStrFP(arena_t *arena, file_t ctx) {
if (!fileIsValid(ctx)) return STR_EMPTY;
str_t out = {0};
out.len = fileSize(ctx);
out.buf = alloc(arena, uint8, out.len + 1);
usize read = fileRead(ctx, out.buf, out.len);
if (read != out.len) {
err("fileReadWholeStrFP: fileRead failed, should be %zu but is %zu", out.len, read);
arenaPop(arena, out.len + 1);
return STR_EMPTY;
}
return out;
}
bool fileWriteWhole(strview_t name, const void *buf, usize len) {
file_t fp = fileOpen(name, FILE_WRITE);
if (!fileIsValid(fp)) {
return false;
}
usize written = fileWrite(fp, buf, len);
fileClose(fp);
return written == len;
}
uint64 fileGetTime(strview_t name) {
file_t fp = fileOpen(name, FILE_READ);
uint64 result = fileGetTimeFP(fp);
fileClose(fp);
return result;
}
bool fileHasChanged(strview_t name, uint64 last_timestamp) {
uint64 timestamp = fileGetTime(name);
return timestamp > last_timestamp;
}
#include "warnings/colla_warn_end.h"
#undef FILE_MAKE_SCRATCH

56
file.h
View file

@ -1,56 +0,0 @@
#pragma once
#include <stdarg.h>
#include "collatypes.h"
#include "str.h"
#include "arena.h"
typedef enum {
FILE_READ = 1 << 0,
FILE_WRITE = 1 << 1,
FILE_APPEND = 1 << 2,
} filemode_e;
typedef struct {
uintptr_t handle;
} file_t;
bool fileExists(strview_t path);
TCHAR *fileGetFullPath(arena_t *arena, strview_t filename);
strview_t fileGetFilename(strview_t path);
strview_t fileGetExtension(strview_t path);
void fileSplitPath(strview_t path, strview_t *dir, strview_t *name, strview_t *ext);
bool fileDelete(strview_t filename);
file_t fileOpen(strview_t name, filemode_e mode);
void fileClose(file_t ctx);
bool fileIsValid(file_t ctx);
bool filePutc(file_t ctx, char c);
bool filePuts(file_t ctx, strview_t v);
bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...);
bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args);
usize fileRead(file_t ctx, void *buf, usize len);
usize fileWrite(file_t ctx, const void *buf, usize len);
bool fileSeek(file_t ctx, usize pos);
bool fileSeekEnd(file_t ctx);
void fileRewind(file_t ctx);
usize fileTell(file_t ctx);
usize fileSize(file_t ctx);
buffer_t fileReadWhole(arena_t *arena, strview_t name);
buffer_t fileReadWholeFP(arena_t *arena, file_t ctx);
str_t fileReadWholeStr(arena_t *arena, strview_t name);
str_t fileReadWholeStrFP(arena_t *arena, file_t ctx);
bool fileWriteWhole(strview_t name, const void *buf, usize len);
uint64 fileGetTime(strview_t name);
uint64 fileGetTimeFP(file_t ctx);
bool fileHasChanged(strview_t name, uint64 last_timestamp);

View file

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

View file

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

View file

@ -1,621 +0,0 @@
#include "highlight.h"
// based on https://github.com/Theldus/kat
#include <ctype.h>
#include "arena.h"
#include "tracelog.h"
#include "strstream.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 uint64 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_default_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", TYPES), KW("uint8", TYPES),
KW("int16", TYPES), KW("uint16", TYPES),
KW("int32", TYPES), KW("uint32", TYPES),
KW("int64", TYPES), KW("uint64", 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 *hlInit(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_default_kwrds);
out->kw_htable = hl_htable_init(arena, 8);
for (int i = 0; i < kw_count; ++i) {
hl_keyword_t *kw = &hl_default_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 = istrGet(in);
bool is_last = istrIsFinished(*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) && !isdigit(cur)) {
ctx->kw_beg = istrTell(*in);
ctx->state = HL_STATE_KEYWORD;
}
// potential number
else if (isdigit(cur)) {
ctx->kw_beg = istrTell(*in);
ctx->state = HL_STATE_NUMBER;
}
// potential char
else if (cur == '\'') {
ctx->kw_beg = istrTell(*in);
ctx->state = HL_STATE_CHAR;
}
// potential string
else if (cur == '"') {
ctx->kw_beg = istrTell(*in);
ctx->state = HL_STATE_STRING;
}
// line or multiline comment
else if (cur == '/') {
// single line comment
if (istrPeek(in) == '/') {
// rewind before comment begins
istrRewindN(in, 1);
// comment until the end of line
hl_print_keyword(ctx, istrGetLine(in), HL_COLOR_COMMENT);
}
// multiline comment
else if (istrPeek(in) == '*') {
ctx->state = HL_STATE_COMMENT_MULTI;
ctx->kw_beg = istrTell(*in);
istrSkip(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 = istrTell(*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)tolower(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 (!isdigit(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 == '\'' && istrPeek(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 == '"' && istrPrevPrev(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 == '*' && istrPeek(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 = strvInitLen(&(str[1]), sizeof(str) - 2); \
strview_t b = istrGetViewLen(&temp, a.len); \
if (strvEquals(a, b)) { \
*in = temp; \
hl_print_keyword(ctx, strvInitLen(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 = istrTell(*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 hlHighlight(arena_t *arena, hl_ctx_t *ctx, strview_t data) {
ctx->ostr = ostrInit(arena);
ctx->state = HL_STATE_DEFAULT;
ctx->kw_beg = 0;
instream_t in = istrInitLen(data.buf, data.len);
while (!istrIsFinished(in)) {
hl_next_char(ctx, &in);
}
hl_next_char(ctx, &in);
return ostrAsStr(&ctx->ostr);
}
void hlSetSymbolInTable(hl_ctx_t *ctx, char symbol, bool value) {
if (!ctx) return;
ctx->symbol_table[(unsigned char)symbol] = value;
}
void hlAddKeyword(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 hashmap is being used: %d/%d", table->used, table->count);
}
uint64 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 (strvEquals(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->used == 0) {
return NULL;
}
uint64 hash = hl_htable_hash(key.buf, key.len);
usize index = hash & (table->count - 1);
hl_node_t *bucket = table->buckets[index];
while (bucket) {
if (strvEquals(bucket->key, key)) {
return bucket;
}
bucket = bucket->next;
}
return NULL;
}
// uses the sdbm algorithm
static uint64 hl_htable_hash(const void *bytes, usize count) {
const uint8 *data = bytes;
uint64 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 '&':
ostrPuts(out, strv("&amp"));
break;
case '<':
ostrPuts(out, strv("&lt"));
break;
case '>':
ostrPuts(out, strv("&gt"));
break;
default:
ostrPutc(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 {
ostrPutc(&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 {
ostrPuts(&ctx->ostr, v);
}
}
static bool hl_is_char_keyword(char c) {
return isalpha(c) || isdigit(c) || c == '_';
}
static bool hl_highlight_symbol(hl_ctx_t *ctx, char c) {
if (!ctx->symbol_table[(unsigned char)c]) {
return false;
}
ostrPuts(&ctx->ostr, ctx->colors[HL_COLOR_SYMBOL]);
hl_write_char(ctx, c);
ostrPuts(&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 (strvEndsWithView(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 (!isdigit(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 = istrTell(*in) - 1;
return strv(in->start + beg, end - beg);
}
static void hl_print_keyword(hl_ctx_t *ctx, strview_t keyword, hl_color_e color) {
ostrPuts(&ctx->ostr, ctx->colors[color]);
hl_write(ctx, keyword);
ostrPuts(&ctx->ostr, ctx->colors[HL_COLOR_NORMAL]);
}

View file

@ -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 *hlInit(arena_t *arena, hl_config_t *config);
str_t hlHighlight(arena_t *arena, hl_ctx_t *ctx, strview_t str);
void hlSetSymbolInTable(hl_ctx_t *ctx, char symbol, bool value);
void hlAddKeyword(arena_t *arena, hl_ctx_t *ctx, hl_keyword_t *keyword);

View file

@ -1,227 +0,0 @@
#include "hot_reload.h"
#include "arena.h"
#include "file.h"
#include "tracelog.h"
// todo linux support?
#if COLLA_WIN
#include <windows.h>
#else
// patch stuff up for cross platform for now, the actual program should not really call anything for now
#define HMODULE void*
#endif
typedef int (*hr_f)(hr_t *ctx);
typedef struct {
arena_t arena;
str_t path;
uint64 last_timestamp;
HMODULE handle;
hr_f hr_init;
hr_f hr_loop;
hr_f hr_close;
} hr_internal_t;
static bool hr__os_reload(hr_internal_t *hr, str_t libpath);
static void hr__os_free(hr_internal_t *hr);
static bool hr__file_copy(arena_t scratch, strview_t src, strview_t dst) {
buffer_t srcbuf = fileReadWhole(&scratch, src);
if (srcbuf.data == NULL || srcbuf.len == 0) {
err("fileReadWhole(%v) returned an empty buffer", src);
return false;
}
if (!fileWriteWhole(dst, srcbuf.data, srcbuf.len)) {
err("fileWriteWhole failed");
return false;
}
return true;
}
static bool hr__reload(hr_t *ctx) {
#ifdef HR_DISABLE
return true;
#endif
hr_internal_t *hr = ctx->p;
arena_t scratch = hr->arena;
if (!fileExists(strv(hr->path))) {
err("dll file %v does not exist anymore!", hr->path);
return false;
}
uint64 now = fileGetTime(strv(hr->path));
if (now <= hr->last_timestamp) {
return false;
}
ctx->version = ctx->last_working_version + 1;
// can't copy the dll directly, make a temporary one based on the version
strview_t dir, name, ext;
fileSplitPath(strv(hr->path), &dir, &name, &ext);
str_t libpath = strFmt(&scratch, "%v/%v-%d%v", dir, name, ctx->version, ext);
if (!hr__file_copy(scratch, strv(hr->path), strv(libpath))) {
err("failed to copy %v to %v", hr->path, libpath);
return false;
}
info("loading library: %v", libpath);
bool success = hr__os_reload(hr, libpath);
if (success) {
info("Reloaded, version: %d", ctx->version);
ctx->last_working_version = ctx->version;
hr->last_timestamp = now;
hr->hr_init(ctx);
}
return success;
}
bool hrOpen(hr_t *ctx, strview_t path) {
#ifdef HR_DISABLE
cr_init(ctx);
return true;
#endif
if (!fileExists(path)) {
err("dll file: %v does not exist", path);
return false;
}
arena_t arena = arenaMake(ARENA_VIRTUAL, MB(1));
hr_internal_t *hr = alloc(&arena, hr_internal_t);
hr->arena = arena;
hr->path = str(&arena, path);
ctx->p = hr;
ctx->last_working_version = 0;
return hr__reload(ctx);
}
void hrClose(hr_t *ctx, bool clean_temp_files) {
#ifdef HR_DISABLE
hr_close(ctx);
return;
#endif
hr_internal_t *hr = ctx->p;
if (hr->hr_close) {
hr->hr_close(ctx);
}
hr__os_free(hr);
hr->handle = NULL;
hr->hr_init = hr->hr_loop = hr->hr_close = NULL;
if (clean_temp_files) {
arena_t scratch = hr->arena;
strview_t dir, name, ext;
fileSplitPath(strv(hr->path), &dir, &name, &ext);
for (int i = 0; i < ctx->last_working_version; ++i) {
str_t fname = strFmt(&scratch, "%v/%v-%d%v", dir, name, i + 1, ext);
if (!fileDelete(strv(fname))) {
err("couldn't delete %v", fname);
}
}
}
arena_t arena = hr->arena;
arenaCleanup(&arena);
ctx->p = NULL;
}
int hrStep(hr_t *ctx) {
#ifdef CR_DISABLE
hr_loop(ctx);
return 0;
#endif
hr_internal_t *hr = ctx->p;
int result = -1;
if (hr->hr_loop) {
result = hr->hr_loop(ctx);
}
return result;
}
bool hrReload(hr_t *ctx) {
return hr__reload(ctx);
}
//// OS SPECIFIC ////////////////////////////////////////
#if COLLA_WIN
static bool hr__os_reload(hr_internal_t *hr, str_t libpath) {
if (hr->handle) {
FreeLibrary(hr->handle);
}
hr->handle = LoadLibraryA(libpath.buf);
if (!hr->handle) {
err("couldn't load %v: %u", libpath, GetLastError());
return true;
}
hr->hr_init = (hr_f)GetProcAddress(hr->handle, "hr_init");
DWORD init_err = GetLastError();
hr->hr_loop = (hr_f)GetProcAddress(hr->handle, "hr_loop");
DWORD loop_err = GetLastError();
hr->hr_close = (hr_f)GetProcAddress(hr->handle, "hr_close");
DWORD close_err = GetLastError();
if (!hr->hr_init) {
err("couldn't load address for hr_init: %u", init_err);
goto error;
}
if (!hr->hr_loop) {
err("couldn't load address for hr_loop: %u", loop_err);
goto error;
}
if (!hr->hr_close) {
err("couldn't load address for hr_close: %u", close_err);
goto error;
}
return true;
error:
if (hr->handle) FreeLibrary(hr->handle);
hr->handle = NULL;
hr->hr_init = hr->hr_loop = hr->hr_close = NULL;
return false;
}
static void hr__os_free(hr_internal_t *hr) {
if (hr->handle) {
FreeLibrary(hr->handle);
}
}
#elif COLLA_LIN
static bool hr__os_reload(hr_internal_t *hr, str_t libpath) {
fatal("todo: linux hot reload not implemented yet");
return true;
}
static void hr__os_free(hr_internal_t *hr) {
fatal("todo: linux hot reload not implemented yet");
}
#endif

View file

@ -1,31 +0,0 @@
#pragma once
// api functions:
// int hr_init(hr_t *ctx)
// int hr_loop(hr_t *ctx)
// int hr_close(hr_t *ctx)
// you can turn off hot reloading and run the program
// "as normal" by defining
// HR_DISABLE
#include "str.h"
#if COLLA_WIN
#define HR_EXPORT __declspec(dllexport)
#else
// todo linux support?
#define HR_EXPORT
#endif
typedef struct hr_t {
void *p;
void *userdata;
int version;
int last_working_version;
} hr_t;
bool hrOpen(hr_t *ctx, strview_t path);
void hrClose(hr_t *ctx, bool clean_temp_files);
int hrStep(hr_t *ctx);
bool hrReload(hr_t *ctx);

77
html.h
View file

@ -1,77 +0,0 @@
#pragma once
#include "strstream.h"
typedef struct {
outstream_t stream;
str_t *output;
} html_context_t;
strview_t html__strv_copy(strview_t src) { return src; }
#define html__str_or_strv(str) _Generic(str, \
strview_t: html__strv_copy, \
str_t: strvInitStr, \
const char *: strvInit, \
char *: strvInit \
)(str)
#define htmlPrintf(...) ostrPrintf(&__ctx.stream, __VA_ARGS__)
#define htmlPuts(str) ostrPuts(&__ctx.stream, html__str_or_strv(str))
#define htmlBeg(arena_ptr, str_ptr) { \
html_context_t __ctx = { .stream = ostrInit(arena_ptr), .output = str_ptr }; \
htmlPrintf("<!DOCTYPE html>\n<html>");
#define htmlEnd() htmlPrintf("</html>"); *__ctx.output = ostrAsStr(&__ctx.stream); }
#define html__args() \
X(class) \
X(id) \
X(style) \
X(onclick) \
X(href) \
X(src) \
typedef struct {
#define X(name) const char *name;
html__args()
#undef X
} html_tag_t;
static void html__tag(html_context_t *ctx, const char *tag, html_tag_t *args) {
ostrPrintf(&ctx->stream, "<%s", tag);
#define X(name, ...) if (args->name) { ostrPrintf(&ctx->stream, " " #name "=\"%s\"", args->name); }
html__args()
#undef X
ostrPutc(&ctx->stream, '>');
}
#define tagBeg(tag, ...) do { html_tag_t args = {0, __VA_ARGS__}; html__tag(&__ctx, tag, &args); } while (0)
#define tagEnd(tag) htmlPrintf("</"tag">")
#define html__strv_or_str(s) _Generic(s, str_t: NULL)
#define html__simple_tag(tag, text, ...) do { tagBeg(tag, __VA_ARGS__); htmlPuts(text); tagEnd(tag); } while (0)
#define headBeg(...) tagBeg("head", __VA_ARGS__)
#define headEnd() tagEnd("head")
#define bodyBeg(...) tagBeg("body", __VA_ARGS__)
#define bodyEnd() tagEnd("body")
#define divBeg(...) tagBeg("div", __VA_ARGS__)
#define divEnd() tagEnd("div")
#define htmlRaw(data) ostrPuts(&__ctx.stream, strv(#data))
#define title(text, ...) html__simple_tag("title", text, __VA_ARGS__)
#define h1(text, ...) html__simple_tag("h1", text, __VA_ARGS__)
#define p(text, ...) html__simple_tag("p", text, __VA_ARGS__)
#define span(text, ...) html__simple_tag("span", text, __VA_ARGS__)
#define a(text, ...) html__simple_tag("a", text, __VA_ARGS__)
#define img(...) tagBeg("img", __VA_ARGS__)
#define style(text, ...) html__simple_tag("style", text, __VA_ARGS__)
#define hr() htmlPuts("<hr>")

556
http.c
View file

@ -1,556 +0,0 @@
#include "http.h"
#include "warnings/colla_warn_beg.h"
#include <assert.h>
#include <stdio.h>
#include "arena.h"
#include "str.h"
#include "strstream.h"
#include "format.h"
#include "socket.h"
#include "tracelog.h"
#if COLLA_WIN
#if COLLA_CMT_LIB
#pragma comment(lib, "Wininet")
#endif
#include <windows.h>
#if !COLLA_TCC
#include <wininet.h>
#endif
#endif
static const TCHAR *https__get_method_str(http_method_e method);
static http_header_t *http__parse_headers(arena_t *arena, instream_t *in) {
http_header_t *head = NULL;
strview_t line = STRV_EMPTY;
do {
line = istrGetView(in, '\r');
usize pos = strvFind(line, ':', 0);
if (pos != STR_NONE) {
http_header_t *h = alloc(arena, http_header_t);
h->key = strvSub(line, 0, pos);
h->value = strvSub(line, pos + 2, SIZE_MAX);
h->next = head;
head = h;
}
istrSkip(in, 2); // skip \r\n
} while (line.len > 2); // while line != "\r\n"
return head;
}
const char *httpGetStatusString(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";
}
int httpVerNumber(http_version_t ver) {
return (ver.major * 10) + ver.minor;
}
http_req_t httpParseReq(arena_t *arena, strview_t request) {
http_req_t req = {0};
instream_t in = istrInitLen(request.buf, request.len);
strview_t method = strvTrim(istrGetView(&in, '/'));
istrSkip(&in, 1); // skip /
req.url = strvTrim(istrGetView(&in, ' '));
strview_t http = strvTrim(istrGetView(&in, '\n'));
istrSkip(&in, 1); // skip \n
req.headers = http__parse_headers(arena, &in);
req.body = strvTrim(istrGetViewLen(&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 (strvEquals(method, methods[i])) {
req.method = (http_method_e)i;
break;
}
}
in = istrInitLen(http.buf, http.len);
istrIgnoreAndSkip(&in, '/'); // skip HTTP/
istrGetU8(&in, &req.version.major);
istrSkip(&in, 1); // skip .
istrGetU8(&in, &req.version.minor);
return req;
}
http_res_t httpParseRes(arena_t *arena, strview_t response) {
http_res_t res = {0};
instream_t in = istrInitLen(response.buf, response.len);
strview_t http = istrGetViewLen(&in, 5);
if (!strvEquals(http, strv("HTTP"))) {
err("response doesn't start with 'HTTP', instead with %v", http);
return (http_res_t){0};
}
istrSkip(&in, 1); // skip /
istrGetU8(&in, &res.version.major);
istrSkip(&in, 1); // skip .
istrGetU8(&in, &res.version.minor);
istrGetI32(&in, (int32*)&res.status_code);
istrIgnore(&in, '\n');
istrSkip(&in, 1); // skip \n
res.headers = http__parse_headers(arena, &in);
strview_t encoding = httpGetHeader(res.headers, strv("transfer-encoding"));
if (!strvEquals(encoding, strv("chunked"))) {
res.body = istrGetViewLen(&in, SIZE_MAX);
}
else {
err("chunked encoding not implemented yet! body ignored");
}
return res;
}
str_t httpReqToStr(arena_t *arena, http_req_t *req) {
outstream_t out = ostrInit(arena);
const char *method = NULL;
switch (req->method) {
case HTTP_GET: method = "GET"; break;
case HTTP_POST: method = "POST"; break;
case HTTP_HEAD: method = "HEAD"; break;
case HTTP_PUT: method = "PUT"; break;
case HTTP_DELETE: method = "DELETE"; break;
default: err("unrecognised method: %d", method); return STR_EMPTY;
}
ostrPrintf(
&out,
"%s /%v HTTP/%hhu.%hhu\r\n",
method, req->url, req->version.major, req->version.minor
);
http_header_t *h = req->headers;
while (h) {
ostrPrintf(&out, "%v: %v\r\n", h->key, h->value);
h = h->next;
}
ostrPuts(&out, strv("\r\n"));
ostrPuts(&out, req->body);
return ostrAsStr(&out);
}
str_t httpResToStr(arena_t *arena, http_res_t *res) {
outstream_t out = ostrInit(arena);
ostrPrintf(
&out,
"HTTP/%hhu.%hhu %d %s\r\n",
res->version.major,
res->version.minor,
res->status_code,
httpGetStatusString(res->status_code)
);
ostrPuts(&out, strv("\r\n"));
ostrPuts(&out, res->body);
return ostrAsStr(&out);
}
bool httpHasHeader(http_header_t *headers, strview_t key) {
http_header_t *h = headers;
while (h) {
if (strvEquals(h->key, key)) {
return true;
}
h = h->next;
}
return false;
}
void httpSetHeader(http_header_t *headers, strview_t key, strview_t value) {
http_header_t *h = headers;
while (h) {
if (strvEquals(h->key, key)) {
h->value = value;
break;
}
h = h->next;
}
}
strview_t httpGetHeader(http_header_t *headers, strview_t key) {
http_header_t *h = headers;
while (h) {
if (strvEquals(h->key, key)) {
return h->value;
}
h = h->next;
}
return STRV_EMPTY;
}
str_t httpMakeUrlSafe(arena_t *arena, strview_t string) {
strview_t chars = strv(" !\"#$%%&'()*+,/:;=?@[]");
usize final_len = string.len;
// find final string length first
for (usize i = 0; i < string.len; ++i) {
if (strvContains(chars, string.buf[i])) {
final_len += 2;
}
}
str_t out = {
.buf = alloc(arena, char, final_len + 1),
.len = final_len
};
usize cur = 0;
// substitute characters
for (usize i = 0; i < string.len; ++i) {
if (strvContains(chars, string.buf[i])) {
fmtBuffer(out.buf + cur, 4, "%%%X", string.buf[i]);
cur += 3;
}
else {
out.buf[cur++] = string.buf[i];
}
}
return out;
}
str_t httpDecodeUrlSafe(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;
}
}
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 httpSplitUrl(strview_t url) {
http_url_t out = {0};
if (strvStartsWithView(url, strv("https://"))) {
url = strvRemovePrefix(url, 8);
}
else if (strvStartsWithView(url, strv("http://"))) {
url = strvRemovePrefix(url, 7);
}
out.host = strvSub(url, 0, strvFind(url, '/', 0));
out.uri = strvSub(url, out.host.len, SIZE_MAX);
return out;
}
http_res_t httpRequest(http_request_desc_t *request) {
usize arena_begin = arenaTell(request->arena);
http_req_t req = {
.version = (http_version_t){ 1, 1 },
.url = request->url,
.body = request->body,
.method = request->request_type,
};
http_header_t *h = NULL;
for (int i = 0; i < request->header_count; ++i) {
http_header_t *header = request->headers + i;
header->next = h;
h = header;
}
req.headers = h;
http_url_t url = httpSplitUrl(req.url);
if (strvEndsWith(url.host, '/')) {
url.host = strvRemoveSuffix(url.host, 1);
}
if (!httpHasHeader(req.headers, strv("Host"))) {
httpSetHeader(req.headers, strv("Host"), url.host);
}
if (!httpHasHeader(req.headers, strv("Content-Length"))) {
char tmp[16] = {0};
fmtBuffer(tmp, arrlen(tmp), "%zu", req.body.len);
httpSetHeader(req.headers, strv("Content-Length"), strv(tmp));
}
if (req.method == HTTP_POST && !httpHasHeader(req.headers, strv("Content-Type"))) {
httpSetHeader(req.headers, strv("Content-Type"), strv("application/x-www-form-urlencoded"));
}
if (!httpHasHeader(req.headers, strv("Connection"))) {
httpSetHeader(req.headers, strv("Connection"), strv("close"));
}
if (!skInit()) {
err("couldn't initialise sockets: %s", skGetErrorString());
goto error;
}
socket_t sock = skOpen(SOCK_TCP);
if (!skIsValid(sock)) {
err("couldn't open socket: %s", skGetErrorString());
goto error;
}
char hostname[64] = {0};
assert(url.host.len < arrlen(hostname));
memcpy(hostname, url.host.buf, url.host.len);
const uint16 DEFAULT_HTTP_PORT = 80;
if (!skConnect(sock, hostname, DEFAULT_HTTP_PORT)) {
err("Couldn't connect to host %s: %s", hostname, skGetErrorString());
goto error;
}
str_t reqstr = httpReqToStr(request->arena, &req);
if (strIsEmpty(reqstr)) {
err("couldn't get string from request");
goto error;
}
if (skSend(sock, reqstr.buf, (int)reqstr.len) == SOCKET_ERROR) {
err("couldn't send request to socket: %s", skGetErrorString());
goto error;
}
outstream_t response = ostrInit(request->arena);
char buffer[4096];
int read = 0;
do {
read = skReceive(sock, buffer, arrlen(buffer));
if (read == SOCKET_ERROR) {
err("couldn't get the data from the server: %s", skGetErrorString());
goto error;
}
ostrPuts(&response, strv(buffer, read));
} while (read != 0);
if (!skClose(sock)) {
err("couldn't close socket: %s", skGetErrorString());
}
if (!skCleanup()) {
err("couldn't clean up sockets: %s", skGetErrorString());
}
return httpParseRes(request->arena, ostrAsView(&response));
error:
arenaRewind(request->arena, arena_begin);
skCleanup();
return (http_res_t){0};
}
#if COLLA_WIN
buffer_t httpsRequest(http_request_desc_t *req) {
HINTERNET internet = InternetOpen(
TEXT("COLLA"),
INTERNET_OPEN_TYPE_PRECONFIG,
NULL,
NULL,
0
);
if (!internet) {
fatal("call to InternetOpen failed: %u", GetLastError());
}
http_url_t split = httpSplitUrl(req->url);
strview_t server = split.host;
strview_t page = split.uri;
if (strvStartsWithView(server, strv("http://"))) {
server = strvRemovePrefix(server, 7);
}
if (strvStartsWithView(server, strv("https://"))) {
server = strvRemovePrefix(server, 8);
}
arena_t scratch = *req->arena;
const TCHAR *tserver = strvToTChar(&scratch, server);
const TCHAR *tpage = strvToTChar(&scratch, page);
HINTERNET connection = InternetConnect(
internet,
tserver,
INTERNET_DEFAULT_HTTPS_PORT,
NULL,
NULL,
INTERNET_SERVICE_HTTP,
0,
(DWORD_PTR)NULL // userdata
);
if (!connection) {
fatal("call to InternetConnect failed: %u", GetLastError());
}
const TCHAR *accepted_types[] = { TEXT("*/*"), NULL };
HINTERNET request = HttpOpenRequest(
connection,
https__get_method_str(req->request_type),
tpage,
TEXT("HTTP/1.1"),
NULL,
accepted_types,
INTERNET_FLAG_SECURE,
(DWORD_PTR)NULL // userdata
);
if (!request) {
fatal("call to HttpOpenRequest failed: %u", GetLastError());
}
outstream_t header = ostrInit(&scratch);
for (int i = 0; i < req->header_count; ++i) {
http_header_t *h = &req->headers[i];
ostrClear(&header);
ostrPrintf(
&header,
"%.*s: %.*s\r\n",
h->key.len, h->key.buf,
h->value.len, h->value.buf
);
str_t header_str = ostrAsStr(&header);
HttpAddRequestHeadersA(
request,
header_str.buf,
(DWORD)header_str.len,
0
);
}
BOOL request_sent = HttpSendRequest(
request,
NULL,
0,
(void *)req->body.buf,
(DWORD)req->body.len
);
if (!request_sent) {
fatal("call to HttpSendRequest failed: %u", GetLastError());
}
outstream_t out = ostrInit(req->arena);
while (true) {
DWORD bytes_read = 0;
char buffer[4096];
BOOL read = InternetReadFile(
request,
buffer,
sizeof(buffer),
&bytes_read
);
if (!read || bytes_read == 0) {
break;
}
ostrPuts(&out, strv(buffer, bytes_read));
}
InternetCloseHandle(request);
InternetCloseHandle(connection);
InternetCloseHandle(internet);
str_t outstr = ostrAsStr(&out);
return (buffer_t) {
.data = (uint8 *)outstr.buf,
.len = outstr.len
};
}
static const TCHAR *https__get_method_str(http_method_e method) {
switch (method) {
case HTTP_GET: return TEXT("GET");
case HTTP_POST: return TEXT("POST");
case HTTP_HEAD: return TEXT("HEAD");
case HTTP_PUT: return TEXT("PUT");
case HTTP_DELETE: return TEXT("DELETE");
}
// default GET
return NULL;
}
#endif
#include "warnings/colla_warn_end.h"

83
http.h
View file

@ -1,83 +0,0 @@
#pragma once
#include "collatypes.h"
#include "str.h"
typedef struct arena_t arena_t;
typedef uintptr_t socket_t;
typedef enum {
HTTP_GET,
HTTP_POST,
HTTP_HEAD,
HTTP_PUT,
HTTP_DELETE
} http_method_e;
const char *httpGetStatusString(int status);
typedef struct {
uint8 major;
uint8 minor;
} http_version_t;
// translates a http_version_t to a single readable number (e.g. 1.1 -> 11, 1.0 -> 10, etc)
int httpVerNumber(http_version_t ver);
typedef struct http_header_t {
strview_t key;
strview_t value;
struct http_header_t *next;
} http_header_t;
typedef struct {
http_method_e method;
http_version_t version;
http_header_t *headers;
strview_t url;
strview_t body;
} http_req_t;
typedef struct {
int status_code;
http_version_t version;
http_header_t *headers;
strview_t body;
} http_res_t;
// strview_t request needs to be valid for http_req_t to be valid!
http_req_t httpParseReq(arena_t *arena, strview_t request);
http_res_t httpParseRes(arena_t *arena, strview_t response);
str_t httpReqToStr(arena_t *arena, http_req_t *req);
str_t httpResToStr(arena_t *arena, http_res_t *res);
bool httpHasHeader(http_header_t *headers, strview_t key);
void httpSetHeader(http_header_t *headers, strview_t key, strview_t value);
strview_t httpGetHeader(http_header_t *headers, strview_t key);
str_t httpMakeUrlSafe(arena_t *arena, strview_t string);
str_t httpDecodeUrlSafe(arena_t *arena, strview_t string);
typedef struct {
strview_t host;
strview_t uri;
} http_url_t;
http_url_t httpSplitUrl(strview_t url);
typedef struct {
arena_t *arena;
strview_t url;
http_method_e request_type;
http_header_t *headers;
int header_count;
strview_t body;
} http_request_desc_t;
// arena_t *arena, strview_t url, [ http_header_t *headers, int header_count, strview_t body ]
#define httpGet(arena, url, ...) httpRequest(&(http_request_desc_t){ arena, url, .request_type = HTTP_GET, __VA_ARGS__ })
#define httpsGet(arena, url, ...) httpsRequest(&(http_request_desc_t){ arena, url, .request_type = HTTP_GET, __VA_ARGS__ })
http_res_t httpRequest(http_request_desc_t *request);
buffer_t httpsRequest(http_request_desc_t *request);

279
ini.c
View file

@ -1,279 +0,0 @@
#include "ini.h"
#include "warnings/colla_warn_beg.h"
#include <assert.h>
#include "strstream.h"
static void ini__parse(arena_t *arena, ini_t *ini, const iniopts_t *options);
ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options) {
file_t fp = fileOpen(filename, FILE_READ);
ini_t out = iniParseFile(arena, fp, options);
fileClose(fp);
return out;
}
ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options) {
str_t data = fileReadWholeStrFP(arena, file);
return iniParseStr(arena, strv(data), options);
}
ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options) {
ini_t out = {
.text = str,
.tables = NULL,
};
ini__parse(arena, &out, options);
return out;
}
bool iniIsValid(ini_t *ctx) {
return ctx && !strvIsEmpty(ctx->text);
}
initable_t *iniGetTable(ini_t *ctx, strview_t name) {
initable_t *t = ctx ? ctx->tables : NULL;
while (t) {
if (strvEquals(t->name, name)) {
return t;
}
t = t->next;
}
return NULL;
}
inivalue_t *iniGet(initable_t *ctx, strview_t key) {
inivalue_t *v = ctx ? ctx->values : NULL;
while (v) {
if (strvEquals(v->key, key)) {
return v;
}
v = v->next;
}
return NULL;
}
iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim) {
strview_t v = value ? value->value : STRV_EMPTY;
if (!delim) delim = ' ';
strview_t *beg = (strview_t *)arena->current;
usize count = 0;
usize start = 0;
for (usize i = 0; i < v.len; ++i) {
if (v.buf[i] == delim) {
strview_t arrval = strvTrim(strvSub(v, start, i));
if (!strvIsEmpty(arrval)) {
strview_t *newval = alloc(arena, strview_t);
*newval = arrval;
++count;
}
start = i + 1;
}
}
strview_t last = strvTrim(strvSub(v, start, SIZE_MAX));
if (!strvIsEmpty(last)) {
strview_t *newval = alloc(arena, strview_t);
*newval = last;
++count;
}
return (iniarray_t){
.values = beg,
.count = count,
};
}
uint64 iniAsUInt(inivalue_t *value) {
strview_t v = value ? value->value : STRV_EMPTY;
instream_t in = istrInitLen(v.buf, v.len);
uint64 out = 0;
if (!istrGetU64(&in, &out)) {
out = 0;
}
return out;
}
int64 iniAsInt(inivalue_t *value) {
strview_t v = value ? value->value : STRV_EMPTY;
instream_t in = istrInitLen(v.buf, v.len);
int64 out = 0;
if (!istrGetI64(&in, &out)) {
out = 0;
}
return out;
}
double iniAsNum(inivalue_t *value) {
strview_t v = value ? value->value : STRV_EMPTY;
instream_t in = istrInitLen(v.buf, v.len);
double out = 0;
if (!istrGetDouble(&in, &out)) {
out = 0.0;
}
return out;
}
bool iniAsBool(inivalue_t *value) {
strview_t v = value ? value->value : STRV_EMPTY;
instream_t in = istrInitLen(v.buf, v.len);
bool out = 0;
if (!istrGetBool(&in, &out)) {
out = false;
}
return out;
}
// == PRIVATE FUNCTIONS ==============================================================================
#define INIPUSH(head, tail, val) \
do { \
if (!head) { \
head = val; \
tail = val; \
} \
else { \
tail->next = val; \
val = tail; \
} \
} while (0)
static iniopts_t ini__get_options(const iniopts_t *options) {
iniopts_t out = {
.key_value_divider = '=',
};
#define SETOPT(v) out.v = options->v ? options->v : out.v
if (options) {
SETOPT(key_value_divider);
SETOPT(merge_duplicate_keys);
SETOPT(merge_duplicate_tables);
}
#undef SETOPT
return out;
}
static void ini__add_value(arena_t *arena, initable_t *table, instream_t *in, iniopts_t *opts) {
assert(table);
strview_t key = strvTrim(istrGetView(in, opts->key_value_divider));
istrSkip(in, 1);
strview_t value = strvTrim(istrGetViewEither(in, strv("\n#;")));
istrSkip(in, 1);
inivalue_t *newval = NULL;
if (opts->merge_duplicate_keys) {
newval = table->values;
while (newval) {
if (strvEquals(newval->key, key)) {
break;
}
newval = newval->next;
}
}
if (newval) {
newval->value = value;
}
else {
newval = alloc(arena, inivalue_t);
newval->key = key;
newval->value = value;
if (!table->values) {
table->values = newval;
}
else {
table->tail->next = newval;
}
table->tail = newval;
}
}
static void ini__add_table(arena_t *arena, ini_t *ctx, instream_t *in, iniopts_t *options) {
istrSkip(in, 1); // skip [
strview_t name = istrGetView(in, ']');
istrSkip(in, 1); // skip ]
initable_t *table = NULL;
if (options->merge_duplicate_tables) {
table = ctx->tables;
while (table) {
if (strvEquals(table->name, name)) {
break;
}
table = table->next;
}
}
if (!table) {
table = alloc(arena, initable_t);
table->name = name;
if (!ctx->tables) {
ctx->tables = table;
}
else {
ctx->tail->next = table;
}
ctx->tail = table;
}
istrIgnoreAndSkip(in, '\n');
while (!istrIsFinished(*in)) {
switch (istrPeek(in)) {
case '\n': // fallthrough
case '\r':
return;
case '#': // fallthrough
case ';':
istrIgnoreAndSkip(in, '\n');
break;
default:
ini__add_value(arena, table, in, options);
break;
}
}
}
static void ini__parse(arena_t *arena, ini_t *ini, const iniopts_t *options) {
iniopts_t opts = ini__get_options(options);
initable_t *root = alloc(arena, initable_t);
root->name = INI_ROOT;
ini->tables = root;
ini->tail = root;
instream_t in = istrInitLen(ini->text.buf, ini->text.len);
while (!istrIsFinished(in)) {
istrSkipWhitespace(&in);
switch (istrPeek(&in)) {
case '[':
ini__add_table(arena, ini, &in, &opts);
break;
case '#': // fallthrough
case ';':
istrIgnoreAndSkip(&in, '\n');
break;
default:
ini__add_value(arena, ini->tables, &in, &opts);
break;
}
}
}
#undef INIPUSH
#include "warnings/colla_warn_end.h"

54
ini.h
View file

@ -1,54 +0,0 @@
#pragma once
#include "collatypes.h"
#include "str.h"
#include "file.h"
typedef struct arena_t arena_t;
typedef struct inivalue_t {
strview_t key;
strview_t value;
struct inivalue_t *next;
} inivalue_t;
typedef struct initable_t {
strview_t name;
inivalue_t *values;
inivalue_t *tail;
struct initable_t *next;
} initable_t;
typedef struct ini_t {
strview_t text;
initable_t *tables;
initable_t *tail;
} ini_t;
typedef struct {
bool merge_duplicate_tables; // default false
bool merge_duplicate_keys; // default false
char key_value_divider; // default =
} iniopts_t;
ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options);
ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options);
ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options);
bool iniIsValid(ini_t *ctx);
#define INI_ROOT strv("__ROOT__")
initable_t *iniGetTable(ini_t *ctx, strview_t name);
inivalue_t *iniGet(initable_t *ctx, strview_t key);
typedef struct {
strview_t *values;
usize count;
} iniarray_t;
iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim);
uint64 iniAsUInt(inivalue_t *value);
int64 iniAsInt(inivalue_t *value);
double iniAsNum(inivalue_t *value);
bool iniAsBool(inivalue_t *value);

386
json.c
View file

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

47
json.h
View file

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

2829
lz4/lz4.c

File diff suppressed because it is too large Load diff

884
lz4/lz4.h
View file

@ -1,884 +0,0 @@
/*
* LZ4 - Fast LZ compression algorithm
* Header File
* Copyright (C) 2011-2023, Yann Collet.
BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
You can contact the author at :
- LZ4 homepage : http://www.lz4.org
- LZ4 source repository : https://github.com/lz4/lz4
*/
#if defined (__cplusplus)
extern "C" {
#endif
#ifndef LZ4_H_2983827168210
#define LZ4_H_2983827168210
/* --- Dependency --- */
#include <stddef.h> /* size_t */
/**
Introduction
LZ4 is lossless compression algorithm, providing compression speed >500 MB/s per core,
scalable with multi-cores CPU. It features an extremely fast decoder, with speed in
multiple GB/s per core, typically reaching RAM speed limits on multi-core systems.
The LZ4 compression library provides in-memory compression and decompression functions.
It gives full buffer control to user.
Compression can be done in:
- a single step (described as Simple Functions)
- a single step, reusing a context (described in Advanced Functions)
- unbounded multiple steps (described as Streaming compression)
lz4.h generates and decodes LZ4-compressed blocks (doc/lz4_Block_format.md).
Decompressing such a compressed block requires additional metadata.
Exact metadata depends on exact decompression function.
For the typical case of LZ4_decompress_safe(),
metadata includes block's compressed size, and maximum bound of decompressed size.
Each application is free to encode and pass such metadata in whichever way it wants.
lz4.h only handle blocks, it can not generate Frames.
Blocks are different from Frames (doc/lz4_Frame_format.md).
Frames bundle both blocks and metadata in a specified manner.
Embedding metadata is required for compressed data to be self-contained and portable.
Frame format is delivered through a companion API, declared in lz4frame.h.
The `lz4` CLI can only manage frames.
*/
/*^***************************************************************
* Export parameters
*****************************************************************/
/*
* LZ4_DLL_EXPORT :
* Enable exporting of functions when building a Windows DLL
* LZ4LIB_VISIBILITY :
* Control library symbols visibility.
*/
#ifndef LZ4LIB_VISIBILITY
# if defined(__GNUC__) && (__GNUC__ >= 4)
# define LZ4LIB_VISIBILITY __attribute__ ((visibility ("default")))
# else
# define LZ4LIB_VISIBILITY
# endif
#endif
#if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1)
# define LZ4LIB_API __declspec(dllexport) LZ4LIB_VISIBILITY
#elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1)
# define LZ4LIB_API __declspec(dllimport) LZ4LIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/
#else
# define LZ4LIB_API LZ4LIB_VISIBILITY
#endif
/*! LZ4_FREESTANDING :
* When this macro is set to 1, it enables "freestanding mode" that is
* suitable for typical freestanding environment which doesn't support
* standard C library.
*
* - LZ4_FREESTANDING is a compile-time switch.
* - It requires the following macros to be defined:
* LZ4_memcpy, LZ4_memmove, LZ4_memset.
* - It only enables LZ4/HC functions which don't use heap.
* All LZ4F_* functions are not supported.
* - See tests/freestanding.c to check its basic setup.
*/
#if defined(LZ4_FREESTANDING) && (LZ4_FREESTANDING == 1)
# define LZ4_HEAPMODE 0
# define LZ4HC_HEAPMODE 0
# define LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION 1
# if !defined(LZ4_memcpy)
# error "LZ4_FREESTANDING requires macro 'LZ4_memcpy'."
# endif
# if !defined(LZ4_memset)
# error "LZ4_FREESTANDING requires macro 'LZ4_memset'."
# endif
# if !defined(LZ4_memmove)
# error "LZ4_FREESTANDING requires macro 'LZ4_memmove'."
# endif
#elif ! defined(LZ4_FREESTANDING)
# define LZ4_FREESTANDING 0
#endif
/*------ Version ------*/
#define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */
#define LZ4_VERSION_MINOR 10 /* for new (non-breaking) interface capabilities */
#define LZ4_VERSION_RELEASE 0 /* for tweaks, bug-fixes, or development */
#define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE)
#define LZ4_LIB_VERSION LZ4_VERSION_MAJOR.LZ4_VERSION_MINOR.LZ4_VERSION_RELEASE
#define LZ4_QUOTE(str) #str
#define LZ4_EXPAND_AND_QUOTE(str) LZ4_QUOTE(str)
#define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION) /* requires v1.7.3+ */
LZ4LIB_API int LZ4_versionNumber (void); /**< library version number; useful to check dll version; requires v1.3.0+ */
LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; useful to check dll version; requires v1.7.5+ */
/*-************************************
* Tuning memory usage
**************************************/
/*!
* LZ4_MEMORY_USAGE :
* Can be selected at compile time, by setting LZ4_MEMORY_USAGE.
* Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB)
* Increasing memory usage improves compression ratio, generally at the cost of speed.
* Reduced memory usage may improve speed at the cost of ratio, thanks to better cache locality.
* Default value is 14, for 16KB, which nicely fits into most L1 caches.
*/
#ifndef LZ4_MEMORY_USAGE
# define LZ4_MEMORY_USAGE LZ4_MEMORY_USAGE_DEFAULT
#endif
/* These are absolute limits, they should not be changed by users */
#define LZ4_MEMORY_USAGE_MIN 10
#define LZ4_MEMORY_USAGE_DEFAULT 14
#define LZ4_MEMORY_USAGE_MAX 20
#if (LZ4_MEMORY_USAGE < LZ4_MEMORY_USAGE_MIN)
# error "LZ4_MEMORY_USAGE is too small !"
#endif
#if (LZ4_MEMORY_USAGE > LZ4_MEMORY_USAGE_MAX)
# error "LZ4_MEMORY_USAGE is too large !"
#endif
/*-************************************
* Simple Functions
**************************************/
/*! LZ4_compress_default() :
* Compresses 'srcSize' bytes from buffer 'src'
* into already allocated 'dst' buffer of size 'dstCapacity'.
* Compression is guaranteed to succeed if 'dstCapacity' >= LZ4_compressBound(srcSize).
* It also runs faster, so it's a recommended setting.
* If the function cannot compress 'src' into a more limited 'dst' budget,
* compression stops *immediately*, and the function result is zero.
* In which case, 'dst' content is undefined (invalid).
* srcSize : max supported value is LZ4_MAX_INPUT_SIZE.
* dstCapacity : size of buffer 'dst' (which must be already allocated)
* @return : the number of bytes written into buffer 'dst' (necessarily <= dstCapacity)
* or 0 if compression fails
* Note : This function is protected against buffer overflow scenarios (never writes outside 'dst' buffer, nor read outside 'source' buffer).
*/
LZ4LIB_API int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity);
/*! LZ4_decompress_safe() :
* @compressedSize : is the exact complete size of the compressed block.
* @dstCapacity : is the size of destination buffer (which must be already allocated),
* presumed an upper bound of decompressed size.
* @return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity)
* If destination buffer is not large enough, decoding will stop and output an error code (negative value).
* If the source stream is detected malformed, the function will stop decoding and return a negative result.
* Note 1 : This function is protected against malicious data packets :
* it will never writes outside 'dst' buffer, nor read outside 'source' buffer,
* even if the compressed block is maliciously modified to order the decoder to do these actions.
* In such case, the decoder stops immediately, and considers the compressed block malformed.
* Note 2 : compressedSize and dstCapacity must be provided to the function, the compressed block does not contain them.
* The implementation is free to send / store / derive this information in whichever way is most beneficial.
* If there is a need for a different format which bundles together both compressed data and its metadata, consider looking at lz4frame.h instead.
*/
LZ4LIB_API int LZ4_decompress_safe (const char* src, char* dst, int compressedSize, int dstCapacity);
/*-************************************
* Advanced Functions
**************************************/
#define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */
#define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16)
/*! LZ4_compressBound() :
Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible)
This function is primarily useful for memory allocation purposes (destination buffer size).
Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example).
Note that LZ4_compress_default() compresses faster when dstCapacity is >= LZ4_compressBound(srcSize)
inputSize : max supported value is LZ4_MAX_INPUT_SIZE
return : maximum output size in a "worst case" scenario
or 0, if input size is incorrect (too large or negative)
*/
LZ4LIB_API int LZ4_compressBound(int inputSize);
/*! LZ4_compress_fast() :
Same as LZ4_compress_default(), but allows selection of "acceleration" factor.
The larger the acceleration value, the faster the algorithm, but also the lesser the compression.
It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed.
An acceleration value of "1" is the same as regular LZ4_compress_default()
Values <= 0 will be replaced by LZ4_ACCELERATION_DEFAULT (currently == 1, see lz4.c).
Values > LZ4_ACCELERATION_MAX will be replaced by LZ4_ACCELERATION_MAX (currently == 65537, see lz4.c).
*/
LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
/*! LZ4_compress_fast_extState() :
* Same as LZ4_compress_fast(), using an externally allocated memory space for its state.
* Use LZ4_sizeofState() to know how much memory must be allocated,
* and allocate it on 8-bytes boundaries (using `malloc()` typically).
* Then, provide this buffer as `void* state` to compression function.
*/
LZ4LIB_API int LZ4_sizeofState(void);
LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
/*! LZ4_compress_destSize() :
* Reverse the logic : compresses as much data as possible from 'src' buffer
* into already allocated buffer 'dst', of size >= 'dstCapacity'.
* This function either compresses the entire 'src' content into 'dst' if it's large enough,
* or fill 'dst' buffer completely with as much data as possible from 'src'.
* note: acceleration parameter is fixed to "default".
*
* *srcSizePtr : in+out parameter. Initially contains size of input.
* Will be modified to indicate how many bytes where read from 'src' to fill 'dst'.
* New value is necessarily <= input value.
* @return : Nb bytes written into 'dst' (necessarily <= dstCapacity)
* or 0 if compression fails.
*
* Note : from v1.8.2 to v1.9.1, this function had a bug (fixed in v1.9.2+):
* the produced compressed content could, in specific circumstances,
* require to be decompressed into a destination buffer larger
* by at least 1 byte than the content to decompress.
* If an application uses `LZ4_compress_destSize()`,
* it's highly recommended to update liblz4 to v1.9.2 or better.
* If this can't be done or ensured,
* the receiving decompression function should provide
* a dstCapacity which is > decompressedSize, by at least 1 byte.
* See https://github.com/lz4/lz4/issues/859 for details
*/
LZ4LIB_API int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targetDstSize);
/*! LZ4_decompress_safe_partial() :
* Decompress an LZ4 compressed block, of size 'srcSize' at position 'src',
* into destination buffer 'dst' of size 'dstCapacity'.
* Up to 'targetOutputSize' bytes will be decoded.
* The function stops decoding on reaching this objective.
* This can be useful to boost performance
* whenever only the beginning of a block is required.
*
* @return : the number of bytes decoded in `dst` (necessarily <= targetOutputSize)
* If source stream is detected malformed, function returns a negative result.
*
* Note 1 : @return can be < targetOutputSize, if compressed block contains less data.
*
* Note 2 : targetOutputSize must be <= dstCapacity
*
* Note 3 : this function effectively stops decoding on reaching targetOutputSize,
* so dstCapacity is kind of redundant.
* This is because in older versions of this function,
* decoding operation would still write complete sequences.
* Therefore, there was no guarantee that it would stop writing at exactly targetOutputSize,
* it could write more bytes, though only up to dstCapacity.
* Some "margin" used to be required for this operation to work properly.
* Thankfully, this is no longer necessary.
* The function nonetheless keeps the same signature, in an effort to preserve API compatibility.
*
* Note 4 : If srcSize is the exact size of the block,
* then targetOutputSize can be any value,
* including larger than the block's decompressed size.
* The function will, at most, generate block's decompressed size.
*
* Note 5 : If srcSize is _larger_ than block's compressed size,
* then targetOutputSize **MUST** be <= block's decompressed size.
* Otherwise, *silent corruption will occur*.
*/
LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity);
/*-*********************************************
* Streaming Compression Functions
***********************************************/
typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */
/*!
Note about RC_INVOKED
- RC_INVOKED is predefined symbol of rc.exe (the resource compiler which is part of MSVC/Visual Studio).
https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros
- Since rc.exe is a legacy compiler, it truncates long symbol (> 30 chars)
and reports warning "RC4011: identifier truncated".
- To eliminate the warning, we surround long preprocessor symbol with
"#if !defined(RC_INVOKED) ... #endif" block that means
"skip this block when rc.exe is trying to read it".
*/
#if !defined(RC_INVOKED) /* https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros */
#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION)
LZ4LIB_API LZ4_stream_t* LZ4_createStream(void);
LZ4LIB_API int LZ4_freeStream (LZ4_stream_t* streamPtr);
#endif /* !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) */
#endif
/*! LZ4_resetStream_fast() : v1.9.0+
* Use this to prepare an LZ4_stream_t for a new chain of dependent blocks
* (e.g., LZ4_compress_fast_continue()).
*
* An LZ4_stream_t must be initialized once before usage.
* This is automatically done when created by LZ4_createStream().
* However, should the LZ4_stream_t be simply declared on stack (for example),
* it's necessary to initialize it first, using LZ4_initStream().
*
* After init, start any new stream with LZ4_resetStream_fast().
* A same LZ4_stream_t can be re-used multiple times consecutively
* and compress multiple streams,
* provided that it starts each new stream with LZ4_resetStream_fast().
*
* LZ4_resetStream_fast() is much faster than LZ4_initStream(),
* but is not compatible with memory regions containing garbage data.
*
* Note: it's only useful to call LZ4_resetStream_fast()
* in the context of streaming compression.
* The *extState* functions perform their own resets.
* Invoking LZ4_resetStream_fast() before is redundant, and even counterproductive.
*/
LZ4LIB_API void LZ4_resetStream_fast (LZ4_stream_t* streamPtr);
/*! LZ4_loadDict() :
* Use this function to reference a static dictionary into LZ4_stream_t.
* The dictionary must remain available during compression.
* LZ4_loadDict() triggers a reset, so any previous data will be forgotten.
* The same dictionary will have to be loaded on decompression side for successful decoding.
* Dictionary are useful for better compression of small data (KB range).
* While LZ4 itself accepts any input as dictionary, dictionary efficiency is also a topic.
* When in doubt, employ the Zstandard's Dictionary Builder.
* Loading a size of 0 is allowed, and is the same as reset.
* @return : loaded dictionary size, in bytes (note: only the last 64 KB are loaded)
*/
LZ4LIB_API int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, int dictSize);
/*! LZ4_loadDictSlow() : v1.10.0+
* Same as LZ4_loadDict(),
* but uses a bit more cpu to reference the dictionary content more thoroughly.
* This is expected to slightly improve compression ratio.
* The extra-cpu cost is likely worth it if the dictionary is re-used across multiple sessions.
* @return : loaded dictionary size, in bytes (note: only the last 64 KB are loaded)
*/
LZ4LIB_API int LZ4_loadDictSlow(LZ4_stream_t* streamPtr, const char* dictionary, int dictSize);
/*! LZ4_attach_dictionary() : stable since v1.10.0
*
* This allows efficient re-use of a static dictionary multiple times.
*
* Rather than re-loading the dictionary buffer into a working context before
* each compression, or copying a pre-loaded dictionary's LZ4_stream_t into a
* working LZ4_stream_t, this function introduces a no-copy setup mechanism,
* in which the working stream references @dictionaryStream in-place.
*
* Several assumptions are made about the state of @dictionaryStream.
* Currently, only states which have been prepared by LZ4_loadDict() or
* LZ4_loadDictSlow() should be expected to work.
*
* Alternatively, the provided @dictionaryStream may be NULL,
* in which case any existing dictionary stream is unset.
*
* If a dictionary is provided, it replaces any pre-existing stream history.
* The dictionary contents are the only history that can be referenced and
* logically immediately precede the data compressed in the first subsequent
* compression call.
*
* The dictionary will only remain attached to the working stream through the
* first compression call, at the end of which it is cleared.
* @dictionaryStream stream (and source buffer) must remain in-place / accessible / unchanged
* through the completion of the compression session.
*
* Note: there is no equivalent LZ4_attach_*() method on the decompression side
* because there is no initialization cost, hence no need to share the cost across multiple sessions.
* To decompress LZ4 blocks using dictionary, attached or not,
* just employ the regular LZ4_setStreamDecode() for streaming,
* or the stateless LZ4_decompress_safe_usingDict() for one-shot decompression.
*/
LZ4LIB_API void
LZ4_attach_dictionary(LZ4_stream_t* workingStream,
const LZ4_stream_t* dictionaryStream);
/*! LZ4_compress_fast_continue() :
* Compress 'src' content using data from previously compressed blocks, for better compression ratio.
* 'dst' buffer must be already allocated.
* If dstCapacity >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster.
*
* @return : size of compressed block
* or 0 if there is an error (typically, cannot fit into 'dst').
*
* Note 1 : Each invocation to LZ4_compress_fast_continue() generates a new block.
* Each block has precise boundaries.
* Each block must be decompressed separately, calling LZ4_decompress_*() with relevant metadata.
* It's not possible to append blocks together and expect a single invocation of LZ4_decompress_*() to decompress them together.
*
* Note 2 : The previous 64KB of source data is __assumed__ to remain present, unmodified, at same address in memory !
*
* Note 3 : When input is structured as a double-buffer, each buffer can have any size, including < 64 KB.
* Make sure that buffers are separated, by at least one byte.
* This construction ensures that each block only depends on previous block.
*
* Note 4 : If input buffer is a ring-buffer, it can have any size, including < 64 KB.
*
* Note 5 : After an error, the stream status is undefined (invalid), it can only be reset or freed.
*/
LZ4LIB_API int LZ4_compress_fast_continue (LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
/*! LZ4_saveDict() :
* If last 64KB data cannot be guaranteed to remain available at its current memory location,
* save it into a safer place (char* safeBuffer).
* This is schematically equivalent to a memcpy() followed by LZ4_loadDict(),
* but is much faster, because LZ4_saveDict() doesn't need to rebuild tables.
* @return : saved dictionary size in bytes (necessarily <= maxDictSize), or 0 if error.
*/
LZ4LIB_API int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int maxDictSize);
/*-**********************************************
* Streaming Decompression Functions
* Bufferless synchronous API
************************************************/
typedef union LZ4_streamDecode_u LZ4_streamDecode_t; /* tracking context */
/*! LZ4_createStreamDecode() and LZ4_freeStreamDecode() :
* creation / destruction of streaming decompression tracking context.
* A tracking context can be re-used multiple times.
*/
#if !defined(RC_INVOKED) /* https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros */
#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION)
LZ4LIB_API LZ4_streamDecode_t* LZ4_createStreamDecode(void);
LZ4LIB_API int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream);
#endif /* !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) */
#endif
/*! LZ4_setStreamDecode() :
* An LZ4_streamDecode_t context can be allocated once and re-used multiple times.
* Use this function to start decompression of a new stream of blocks.
* A dictionary can optionally be set. Use NULL or size 0 for a reset order.
* Dictionary is presumed stable : it must remain accessible and unmodified during next decompression.
* @return : 1 if OK, 0 if error
*/
LZ4LIB_API int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize);
/*! LZ4_decoderRingBufferSize() : v1.8.2+
* Note : in a ring buffer scenario (optional),
* blocks are presumed decompressed next to each other
* up to the moment there is not enough remaining space for next block (remainingSize < maxBlockSize),
* at which stage it resumes from beginning of ring buffer.
* When setting such a ring buffer for streaming decompression,
* provides the minimum size of this ring buffer
* to be compatible with any source respecting maxBlockSize condition.
* @return : minimum ring buffer size,
* or 0 if there is an error (invalid maxBlockSize).
*/
LZ4LIB_API int LZ4_decoderRingBufferSize(int maxBlockSize);
#define LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize) (65536 + 14 + (maxBlockSize)) /* for static allocation; maxBlockSize presumed valid */
/*! LZ4_decompress_safe_continue() :
* This decoding function allows decompression of consecutive blocks in "streaming" mode.
* The difference with the usual independent blocks is that
* new blocks are allowed to find references into former blocks.
* A block is an unsplittable entity, and must be presented entirely to the decompression function.
* LZ4_decompress_safe_continue() only accepts one block at a time.
* It's modeled after `LZ4_decompress_safe()` and behaves similarly.
*
* @LZ4_streamDecode : decompression state, tracking the position in memory of past data
* @compressedSize : exact complete size of one compressed block.
* @dstCapacity : size of destination buffer (which must be already allocated),
* must be an upper bound of decompressed size.
* @return : number of bytes decompressed into destination buffer (necessarily <= dstCapacity)
* If destination buffer is not large enough, decoding will stop and output an error code (negative value).
* If the source stream is detected malformed, the function will stop decoding and return a negative result.
*
* The last 64KB of previously decoded data *must* remain available and unmodified
* at the memory position where they were previously decoded.
* If less than 64KB of data has been decoded, all the data must be present.
*
* Special : if decompression side sets a ring buffer, it must respect one of the following conditions :
* - Decompression buffer size is _at least_ LZ4_decoderRingBufferSize(maxBlockSize).
* maxBlockSize is the maximum size of any single block. It can have any value > 16 bytes.
* In which case, encoding and decoding buffers do not need to be synchronized.
* Actually, data can be produced by any source compliant with LZ4 format specification, and respecting maxBlockSize.
* - Synchronized mode :
* Decompression buffer size is _exactly_ the same as compression buffer size,
* and follows exactly same update rule (block boundaries at same positions),
* and decoding function is provided with exact decompressed size of each block (exception for last block of the stream),
* _then_ decoding & encoding ring buffer can have any size, including small ones ( < 64 KB).
* - Decompression buffer is larger than encoding buffer, by a minimum of maxBlockSize more bytes.
* In which case, encoding and decoding buffers do not need to be synchronized,
* and encoding ring buffer can have any size, including small ones ( < 64 KB).
*
* Whenever these conditions are not possible,
* save the last 64KB of decoded data into a safe buffer where it can't be modified during decompression,
* then indicate where this data is saved using LZ4_setStreamDecode(), before decompressing next block.
*/
LZ4LIB_API int
LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode,
const char* src, char* dst,
int srcSize, int dstCapacity);
/*! LZ4_decompress_safe_usingDict() :
* Works the same as
* a combination of LZ4_setStreamDecode() followed by LZ4_decompress_safe_continue()
* However, it's stateless: it doesn't need any LZ4_streamDecode_t state.
* Dictionary is presumed stable : it must remain accessible and unmodified during decompression.
* Performance tip : Decompression speed can be substantially increased
* when dst == dictStart + dictSize.
*/
LZ4LIB_API int
LZ4_decompress_safe_usingDict(const char* src, char* dst,
int srcSize, int dstCapacity,
const char* dictStart, int dictSize);
/*! LZ4_decompress_safe_partial_usingDict() :
* Behaves the same as LZ4_decompress_safe_partial()
* with the added ability to specify a memory segment for past data.
* Performance tip : Decompression speed can be substantially increased
* when dst == dictStart + dictSize.
*/
LZ4LIB_API int
LZ4_decompress_safe_partial_usingDict(const char* src, char* dst,
int compressedSize,
int targetOutputSize, int maxOutputSize,
const char* dictStart, int dictSize);
#endif /* LZ4_H_2983827168210 */
/*^*************************************
* !!!!!! STATIC LINKING ONLY !!!!!!
***************************************/
/*-****************************************************************************
* Experimental section
*
* Symbols declared in this section must be considered unstable. Their
* signatures or semantics may change, or they may be removed altogether in the
* future. They are therefore only safe to depend on when the caller is
* statically linked against the library.
*
* To protect against unsafe usage, not only are the declarations guarded,
* the definitions are hidden by default
* when building LZ4 as a shared/dynamic library.
*
* In order to access these declarations,
* define LZ4_STATIC_LINKING_ONLY in your application
* before including LZ4's headers.
*
* In order to make their implementations accessible dynamically, you must
* define LZ4_PUBLISH_STATIC_FUNCTIONS when building the LZ4 library.
******************************************************************************/
#ifdef LZ4_STATIC_LINKING_ONLY
#ifndef LZ4_STATIC_3504398509
#define LZ4_STATIC_3504398509
#ifdef LZ4_PUBLISH_STATIC_FUNCTIONS
# define LZ4LIB_STATIC_API LZ4LIB_API
#else
# define LZ4LIB_STATIC_API
#endif
/*! LZ4_compress_fast_extState_fastReset() :
* A variant of LZ4_compress_fast_extState().
*
* Using this variant avoids an expensive initialization step.
* It is only safe to call if the state buffer is known to be correctly initialized already
* (see above comment on LZ4_resetStream_fast() for a definition of "correctly initialized").
* From a high level, the difference is that
* this function initializes the provided state with a call to something like LZ4_resetStream_fast()
* while LZ4_compress_fast_extState() starts with a call to LZ4_resetStream().
*/
LZ4LIB_STATIC_API int LZ4_compress_fast_extState_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
/*! LZ4_compress_destSize_extState() : introduced in v1.10.0
* Same as LZ4_compress_destSize(), but using an externally allocated state.
* Also: exposes @acceleration
*/
int LZ4_compress_destSize_extState(void* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize, int acceleration);
/*! In-place compression and decompression
*
* It's possible to have input and output sharing the same buffer,
* for highly constrained memory environments.
* In both cases, it requires input to lay at the end of the buffer,
* and decompression to start at beginning of the buffer.
* Buffer size must feature some margin, hence be larger than final size.
*
* |<------------------------buffer--------------------------------->|
* |<-----------compressed data--------->|
* |<-----------decompressed size------------------>|
* |<----margin---->|
*
* This technique is more useful for decompression,
* since decompressed size is typically larger,
* and margin is short.
*
* In-place decompression will work inside any buffer
* which size is >= LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize).
* This presumes that decompressedSize > compressedSize.
* Otherwise, it means compression actually expanded data,
* and it would be more efficient to store such data with a flag indicating it's not compressed.
* This can happen when data is not compressible (already compressed, or encrypted).
*
* For in-place compression, margin is larger, as it must be able to cope with both
* history preservation, requiring input data to remain unmodified up to LZ4_DISTANCE_MAX,
* and data expansion, which can happen when input is not compressible.
* As a consequence, buffer size requirements are much higher,
* and memory savings offered by in-place compression are more limited.
*
* There are ways to limit this cost for compression :
* - Reduce history size, by modifying LZ4_DISTANCE_MAX.
* Note that it is a compile-time constant, so all compressions will apply this limit.
* Lower values will reduce compression ratio, except when input_size < LZ4_DISTANCE_MAX,
* so it's a reasonable trick when inputs are known to be small.
* - Require the compressor to deliver a "maximum compressed size".
* This is the `dstCapacity` parameter in `LZ4_compress*()`.
* When this size is < LZ4_COMPRESSBOUND(inputSize), then compression can fail,
* in which case, the return code will be 0 (zero).
* The caller must be ready for these cases to happen,
* and typically design a backup scheme to send data uncompressed.
* The combination of both techniques can significantly reduce
* the amount of margin required for in-place compression.
*
* In-place compression can work in any buffer
* which size is >= (maxCompressedSize)
* with maxCompressedSize == LZ4_COMPRESSBOUND(srcSize) for guaranteed compression success.
* LZ4_COMPRESS_INPLACE_BUFFER_SIZE() depends on both maxCompressedSize and LZ4_DISTANCE_MAX,
* so it's possible to reduce memory requirements by playing with them.
*/
#define LZ4_DECOMPRESS_INPLACE_MARGIN(compressedSize) (((compressedSize) >> 8) + 32)
#define LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize) ((decompressedSize) + LZ4_DECOMPRESS_INPLACE_MARGIN(decompressedSize)) /**< note: presumes that compressedSize < decompressedSize. note2: margin is overestimated a bit, since it could use compressedSize instead */
#ifndef LZ4_DISTANCE_MAX /* history window size; can be user-defined at compile time */
# define LZ4_DISTANCE_MAX 65535 /* set to maximum value by default */
#endif
#define LZ4_COMPRESS_INPLACE_MARGIN (LZ4_DISTANCE_MAX + 32) /* LZ4_DISTANCE_MAX can be safely replaced by srcSize when it's smaller */
#define LZ4_COMPRESS_INPLACE_BUFFER_SIZE(maxCompressedSize) ((maxCompressedSize) + LZ4_COMPRESS_INPLACE_MARGIN) /**< maxCompressedSize is generally LZ4_COMPRESSBOUND(inputSize), but can be set to any lower value, with the risk that compression can fail (return code 0(zero)) */
#endif /* LZ4_STATIC_3504398509 */
#endif /* LZ4_STATIC_LINKING_ONLY */
#ifndef LZ4_H_98237428734687
#define LZ4_H_98237428734687
/*-************************************************************
* Private Definitions
**************************************************************
* Do not use these definitions directly.
* They are only exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`.
* Accessing members will expose user code to API and/or ABI break in future versions of the library.
**************************************************************/
#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2)
#define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE)
#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */
#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */)
# include <stdint.h>
typedef int8_t LZ4_i8;
typedef uint8_t LZ4_byte;
typedef uint16_t LZ4_u16;
typedef uint32_t LZ4_u32;
#else
typedef signed char LZ4_i8;
typedef unsigned char LZ4_byte;
typedef unsigned short LZ4_u16;
typedef unsigned int LZ4_u32;
#endif
/*! LZ4_stream_t :
* Never ever use below internal definitions directly !
* These definitions are not API/ABI safe, and may change in future versions.
* If you need static allocation, declare or allocate an LZ4_stream_t object.
**/
typedef struct LZ4_stream_t_internal LZ4_stream_t_internal;
struct LZ4_stream_t_internal {
LZ4_u32 hashTable[LZ4_HASH_SIZE_U32];
const LZ4_byte* dictionary;
const LZ4_stream_t_internal* dictCtx;
LZ4_u32 currentOffset;
LZ4_u32 tableType;
LZ4_u32 dictSize;
/* Implicit padding to ensure structure is aligned */
};
#define LZ4_STREAM_MINSIZE ((1UL << (LZ4_MEMORY_USAGE)) + 32) /* static size, for inter-version compatibility */
union LZ4_stream_u {
char minStateSize[LZ4_STREAM_MINSIZE];
LZ4_stream_t_internal internal_donotuse;
}; /* previously typedef'd to LZ4_stream_t */
/*! LZ4_initStream() : v1.9.0+
* An LZ4_stream_t structure must be initialized at least once.
* This is automatically done when invoking LZ4_createStream(),
* but it's not when the structure is simply declared on stack (for example).
*
* Use LZ4_initStream() to properly initialize a newly declared LZ4_stream_t.
* It can also initialize any arbitrary buffer of sufficient size,
* and will @return a pointer of proper type upon initialization.
*
* Note : initialization fails if size and alignment conditions are not respected.
* In which case, the function will @return NULL.
* Note2: An LZ4_stream_t structure guarantees correct alignment and size.
* Note3: Before v1.9.0, use LZ4_resetStream() instead
**/
LZ4LIB_API LZ4_stream_t* LZ4_initStream (void* stateBuffer, size_t size);
/*! LZ4_streamDecode_t :
* Never ever use below internal definitions directly !
* These definitions are not API/ABI safe, and may change in future versions.
* If you need static allocation, declare or allocate an LZ4_streamDecode_t object.
**/
typedef struct {
const LZ4_byte* externalDict;
const LZ4_byte* prefixEnd;
size_t extDictSize;
size_t prefixSize;
} LZ4_streamDecode_t_internal;
#define LZ4_STREAMDECODE_MINSIZE 32
union LZ4_streamDecode_u {
char minStateSize[LZ4_STREAMDECODE_MINSIZE];
LZ4_streamDecode_t_internal internal_donotuse;
} ; /* previously typedef'd to LZ4_streamDecode_t */
/*-************************************
* Obsolete Functions
**************************************/
/*! Deprecation warnings
*
* Deprecated functions make the compiler generate a warning when invoked.
* This is meant to invite users to update their source code.
* Should deprecation warnings be a problem, it is generally possible to disable them,
* typically with -Wno-deprecated-declarations for gcc
* or _CRT_SECURE_NO_WARNINGS in Visual.
*
* Another method is to define LZ4_DISABLE_DEPRECATE_WARNINGS
* before including the header file.
*/
#ifdef LZ4_DISABLE_DEPRECATE_WARNINGS
# define LZ4_DEPRECATED(message) /* disable deprecation warnings */
#else
# if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */
# define LZ4_DEPRECATED(message) [[deprecated(message)]]
# elif defined(_MSC_VER)
# define LZ4_DEPRECATED(message) __declspec(deprecated(message))
# elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 45))
# define LZ4_DEPRECATED(message) __attribute__((deprecated(message)))
# elif defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 31)
# define LZ4_DEPRECATED(message) __attribute__((deprecated))
# else
# pragma message("WARNING: LZ4_DEPRECATED needs custom implementation for this compiler")
# define LZ4_DEPRECATED(message) /* disabled */
# endif
#endif /* LZ4_DISABLE_DEPRECATE_WARNINGS */
/*! Obsolete compression functions (since v1.7.3) */
LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress (const char* src, char* dest, int srcSize);
LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress_limitedOutput (const char* src, char* dest, int srcSize, int maxOutputSize);
LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_withState (void* state, const char* source, char* dest, int inputSize);
LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_limitedOutput_withState (void* state, const char* source, char* dest, int inputSize, int maxOutputSize);
LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize);
LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize);
/*! Obsolete decompression functions (since v1.8.0) */
LZ4_DEPRECATED("use LZ4_decompress_fast() instead") LZ4LIB_API int LZ4_uncompress (const char* source, char* dest, int outputSize);
LZ4_DEPRECATED("use LZ4_decompress_safe() instead") LZ4LIB_API int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize);
/* Obsolete streaming functions (since v1.7.0)
* degraded functionality; do not use!
*
* In order to perform streaming compression, these functions depended on data
* that is no longer tracked in the state. They have been preserved as well as
* possible: using them will still produce a correct output. However, they don't
* actually retain any history between compression calls. The compression ratio
* achieved will therefore be no better than compressing each chunk
* independently.
*/
LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API void* LZ4_create (char* inputBuffer);
LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API int LZ4_sizeofStreamState(void);
LZ4_DEPRECATED("Use LZ4_resetStream() instead") LZ4LIB_API int LZ4_resetStreamState(void* state, char* inputBuffer);
LZ4_DEPRECATED("Use LZ4_saveDict() instead") LZ4LIB_API char* LZ4_slideInputBuffer (void* state);
/*! Obsolete streaming decoding functions (since v1.7.0) */
LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") LZ4LIB_API int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize);
LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize);
/*! Obsolete LZ4_decompress_fast variants (since v1.9.0) :
* These functions used to be faster than LZ4_decompress_safe(),
* but this is no longer the case. They are now slower.
* This is because LZ4_decompress_fast() doesn't know the input size,
* and therefore must progress more cautiously into the input buffer to not read beyond the end of block.
* On top of that `LZ4_decompress_fast()` is not protected vs malformed or malicious inputs, making it a security liability.
* As a consequence, LZ4_decompress_fast() is strongly discouraged, and deprecated.
*
* The last remaining LZ4_decompress_fast() specificity is that
* it can decompress a block without knowing its compressed size.
* Such functionality can be achieved in a more secure manner
* by employing LZ4_decompress_safe_partial().
*
* Parameters:
* originalSize : is the uncompressed size to regenerate.
* `dst` must be already allocated, its size must be >= 'originalSize' bytes.
* @return : number of bytes read from source buffer (== compressed size).
* The function expects to finish at block's end exactly.
* If the source stream is detected malformed, the function stops decoding and returns a negative result.
* note : LZ4_decompress_fast*() requires originalSize. Thanks to this information, it never writes past the output buffer.
* However, since it doesn't know its 'src' size, it may read an unknown amount of input, past input buffer bounds.
* Also, since match offsets are not validated, match reads from 'src' may underflow too.
* These issues never happen if input (compressed) data is correct.
* But they may happen if input data is invalid (error or intentional tampering).
* As a consequence, use these functions in trusted environments with trusted data **only**.
*/
LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_partial() instead")
LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize);
LZ4_DEPRECATED("This function is deprecated and unsafe. Consider migrating towards LZ4_decompress_safe_continue() instead. "
"Note that the contract will change (requires block's compressed size, instead of decompressed size)")
LZ4LIB_API int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int originalSize);
LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_partial_usingDict() instead")
LZ4LIB_API int LZ4_decompress_fast_usingDict (const char* src, char* dst, int originalSize, const char* dictStart, int dictSize);
/*! LZ4_resetStream() :
* An LZ4_stream_t structure must be initialized at least once.
* This is done with LZ4_initStream(), or LZ4_resetStream().
* Consider switching to LZ4_initStream(),
* invoking LZ4_resetStream() will trigger deprecation warnings in the future.
*/
LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr);
#endif /* LZ4_H_98237428734687 */
#if defined (__cplusplus)
}
#endif

View file

@ -1,504 +0,0 @@
#include "markdown.h"
#include "arena.h"
#include "str.h"
#include "strstream.h"
#include "file.h"
#include "ini.h"
#include "tracelog.h"
#ifndef MD_LIST_MAX_DEPTH
#define MD_LIST_MAX_DEPTH 8
#endif
typedef struct {
struct {
int indent;
int count;
bool list_is_ordered[MD_LIST_MAX_DEPTH];
} list;
struct {
bool is_in_block;
strview_t lang;
} code;
bool is_bold;
bool is_italic;
bool is_in_paragraph;
strview_t raw_line;
md_options_t *options;
md_parser_t *curparser;
} markdown_ctx_t;
static void markdown__parse_config(arena_t *arena, instream_t *in, ini_t *out);
static int markdown__count_chars(strview_t *line, char c);
static void markdown__parse_line(markdown_ctx_t *md, strview_t line, outstream_t *out, bool add_newline, bool is_line_start);
static strview_t markdown__parse_header(markdown_ctx_t *md, strview_t line, outstream_t *out);
static strview_t markdown__parse_ulist_or_line(markdown_ctx_t *md, strview_t line, outstream_t *out);
static strview_t markdown__parse_olist(markdown_ctx_t *md, strview_t line, outstream_t *out);
static strview_t markdown__parse_code_block(markdown_ctx_t *md, strview_t line, outstream_t *out);
static bool markdown__try_parse_url(instream_t *in, strview_t *out_url, strview_t *out_text);
static void markdown__empty_line(markdown_ctx_t *md, outstream_t *out);
static void markdown__close_list(markdown_ctx_t *md, outstream_t *out);
static void markdown__escape(strview_t view, outstream_t *out);
str_t markdown(arena_t *arena, arena_t scratch, strview_t filename, md_options_t *options) {
str_t text = fileReadWholeStr(&scratch, filename);
return markdownStr(arena, strv(text), options);
}
str_t markdownStr(arena_t *arena, strview_t markdown_str, md_options_t *options) {
instream_t in = istrInitLen(markdown_str.buf, markdown_str.len);
markdown__parse_config(arena, &in, options ? options->out_config : NULL);
outstream_t out = ostrInit(arena);
markdown_ctx_t md = {
.list = {
.indent = -1,
},
.options = options,
};
while (!istrIsFinished(in)) {
md.raw_line = istrGetLine(&in);
markdown__parse_line(&md, strvTrimLeft(md.raw_line), &out, true, true);
}
markdown__empty_line(&md, &out);
return ostrAsStr(&out);
}
// == PRIVATE FUNCTIONS ==================================================
static void markdown__parse_config(arena_t *arena, instream_t *in, ini_t *out) {
strview_t first_line = strvTrim(istrGetLine(in));
if (!strvEquals(first_line, strv("---"))) {
istrRewind(in);
return;
}
strview_t ini_data = strvInitLen(in->cur, 0);
usize data_beg = istrTell(*in);
while (!istrIsFinished(*in)) {
strview_t line = istrGetViewEither(in, strv("\r\n"));
if (strvEquals(strvTrim(line), strv("---"))) {
break;
}
istrSkipWhitespace(in);
}
usize data_end = istrTell(*in);
ini_data.len = data_end - data_beg - 3;
if (out) {
// allocate the string as ini_t only as a copy
str_t ini_str = str(arena, ini_data);
*out = iniParseStr(arena, strv(ini_str), NULL);
}
}
static int markdown__count_chars(strview_t *line, char c) {
strview_t temp = *line;
int n = 0;
while (strvFront(temp) == c) {
n++;
temp = strvRemovePrefix(temp, 1);
}
*line = temp;
return n;
}
static strview_t markdown__parse_header(markdown_ctx_t* md, strview_t line, outstream_t *out) {
int n = markdown__count_chars(&line, '#');
line = strvTrimLeft(line);
ostrPrintf(out, "<h%d>", n);
markdown__parse_line(md, line, out, false, false);
ostrPrintf(out, "</h%d>", n);
return STRV_EMPTY;
}
static strview_t markdown__parse_ulist_or_line(markdown_ctx_t *md, strview_t line, outstream_t *out) {
// check if there is anything before this character, if there is
// it means we're in the middle of a line and we should ignore
strview_t prev = strvSub(md->raw_line, 0, line.buf - md->raw_line.buf);
int space_count;
for (space_count = 0; space_count < prev.len; ++space_count) {
if (prev.buf[space_count] != ' ') break;
}
if (space_count < prev.len) {
return line;
}
// if its only * or -, this is a list
if (line.len > 1 && line.buf[1] == ' ') {
strview_t raw_line = md->raw_line;
int cur_indent = markdown__count_chars(&raw_line, ' ');
// start of list
if (md->list.indent < cur_indent) {
if (md->list.count >= MD_LIST_MAX_DEPTH) {
fatal("markdown: too many list levels, max is %d, define MD_LIST_MAX_DEPTH to an higher number", MD_LIST_MAX_DEPTH);
}
md->list.list_is_ordered[md->list.count++] = false;
ostrPuts(out, strv("<ul>\n"));
}
else if (md->list.indent > cur_indent) {
markdown__close_list(md, out);
}
md->list.indent = cur_indent;
ostrPuts(out, strv("<li>"));
markdown__parse_line(md, strvRemovePrefix(line, 2), out, false, false);
ostrPuts(out, strv("</li>"));
goto read_whole_line;
}
// check if it is an <hr>
char hr_char = strvFront(line);
strview_t hr = strvTrim(line);
bool is_hr = true;
for (usize i = 0; i < hr.len; ++i) {
if (hr.buf[i] != hr_char) {
is_hr = false;
break;
}
}
if (is_hr) {
ostrPuts(out, strv("<hr>"));
goto read_whole_line;
}
else {
strview_t to_print = line;
int n = markdown__count_chars(&line, strvFront(line));
to_print = strvSub(to_print, 0, n);
line = strvSub(line, n, SIZE_MAX);
ostrPuts(out, to_print);
}
return line;
read_whole_line:
return STRV_EMPTY;
}
static strview_t markdown__parse_olist(markdown_ctx_t *md, strview_t line, outstream_t *out) {
instream_t in = istrInitLen(line.buf, line.len);
int32 number = 0;
if (!istrGetI32(&in, &number)) {
return line;
}
if (istrPeek(&in) != '.') {
return line;
}
istrSkip(&in, 1);
if (istrPeek(&in) != ' ') {
return line;
}
istrSkip(&in, 1);
strview_t raw_line = md->raw_line;
int cur_indent = markdown__count_chars(&raw_line, ' ');
// start of list
if (md->list.indent < cur_indent) {
if (md->list.count >= MD_LIST_MAX_DEPTH) {
fatal("markdown: too many list levels, max is %d, define MD_LIST_MAX_DEPTH to an higher number", MD_LIST_MAX_DEPTH);
}
md->list.list_is_ordered[md->list.count++] = true;
ostrPuts(out, strv("<ol>\n"));
}
else if (md->list.indent > cur_indent) {
markdown__close_list(md, out);
}
md->list.indent = cur_indent;
ostrPuts(out, strv("<li>"));
markdown__parse_line(md, strvRemovePrefix(line, istrTell(in)), out, false, false);
ostrPuts(out, strv("</li>"));
return STRV_EMPTY;
}
static strview_t markdown__parse_code_block(markdown_ctx_t *md, strview_t line, outstream_t *out) {
strview_t line_copy = line;
int ticks = markdown__count_chars(&line_copy, '`');
if (ticks != 3) {
goto finish;
}
if (md->code.is_in_block) {
md->code.is_in_block = false;
if (md->curparser) {
md_parser_t *p = md->curparser;
if (p->finish) {
p->finish(p->userdata);
}
}
ostrPuts(out, strv("</ol></code></pre>\n"));
line = line_copy;
goto finish;
}
instream_t in = istrInitLen(line_copy.buf, line_copy.len);
strview_t lang = istrGetLine(&in);
if (!strvIsEmpty(lang)) {
md->curparser = NULL;
md_options_t *opt = md->options;
if (opt) {
for (int i = 0; i < opt->parsers_count; ++i) {
if (strvEquals(lang, opt->parsers->lang)) {
md->curparser = &opt->parsers[i];
break;
}
}
}
if (!md->curparser) {
warn("markdown: no parser found for code block in language (%v)", lang);
}
else {
md_parser_t *p = md->curparser;
if (p->init) {
p->init(p->userdata);
}
}
}
ostrPuts(out, strv("<pre><code><ol>"));
md->code.is_in_block = true;
return STRV_EMPTY;
finish:
return line;
}
static bool markdown__try_parse_url(instream_t *in, strview_t *out_url, strview_t *out_text) {
istrSkip(in, 1); // skip [
strview_t text = istrGetView(in, ']');
istrSkip(in, 1); // skip ]
strview_t url = STRV_EMPTY;
if (istrPeek(in) == '(') {
istrSkip(in, 1); // skip (
url = istrGetView(in, ')');
istrSkip(in, 1); // skip )
}
bool success = !strvIsEmpty(url);
if (success) {
*out_url = url;
*out_text = text;
}
return success;
}
static void markdown__parse_line(markdown_ctx_t *md, strview_t line, outstream_t *out, bool add_newline, bool is_line_start) {
if (md->code.is_in_block && strvFront(line) != '`') {
md_parser_t *p = md->curparser;
if (p && p->callback) {
p->callback(md->raw_line, out, p->userdata);
}
else {
ostrPrintf(out, "%v\n", md->raw_line);
}
return;
}
if (strvIsEmpty(line)) {
markdown__empty_line(md, out);
return;
}
switch (strvFront(line)) {
// header
case '#':
line = markdown__parse_header(md, line, out);
break;
// unordered list or <hr>
case '-': case '*': case '_':
line = markdown__parse_ulist_or_line(md, line, out);
break;
// ordered list
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
line = markdown__parse_olist(md, line, out);
break;
// code block
case '`':
line = markdown__parse_code_block(md, line, out);
break;
default:
break;
}
if (!strvIsEmpty(line) && is_line_start && !md->is_in_paragraph) {
md->is_in_paragraph = true;
ostrPuts(out, strv("<p>\n"));
}
for (usize i = 0; i < line.len; ++i) {
switch (line.buf[i]) {
// escape next character
case '\\':
if (++i < line.len) {
ostrPutc(out, line.buf[i]);
}
break;
// bold or italic
case '*':
{
strview_t sub = strvSub(line, i, SIZE_MAX);
int n = markdown__count_chars(&sub, '*');
bool is_both = n >= 3;
bool is_italic = n == 1 || is_both;
bool is_bold = n == 2 || is_both;
if (is_italic) {
ostrPrintf(out, "<%s>", md->is_italic ? "/i" : "i");
md->is_italic = !md->is_italic;
}
if (is_bold) {
ostrPrintf(out, "<%s>", md->is_bold ? "/b" : "b");
md->is_bold = !md->is_bold;
}
if (is_both) {
for (int k = 3; k < n; ++k) {
ostrPutc(out, '*');
}
}
i += n - 1;
break;
}
// url
case '[':
{
instream_t in = istrInitLen(line.buf + i, line.len - i);
strview_t url = STRV_EMPTY;
strview_t text = STRV_EMPTY;
if (markdown__try_parse_url(&in, &url, &text)) {
ostrPrintf(out, "<a href=\"%v\">%v</a>", url, strvIsEmpty(text) ? url : text);
i += istrTell(in) - 1;
}
else{
ostrPutc(out, line.buf[i]);
}
break;
}
// image
case '!':
{
instream_t in = istrInitLen(line.buf + i, line.len - i);
strview_t url = STRV_EMPTY;
strview_t text = STRV_EMPTY;
istrSkip(&in, 1); // skip !
if (markdown__try_parse_url(&in, &url, &text)) {
ostrPrintf(out, "<img src=\"%v\"", url);
if (!strvIsEmpty(text)) {
ostrPrintf(out, " alt=\"%v\"", text);
}
ostrPutc(out, '>');
i += istrTell(in) - 1;
}
else{
ostrPutc(out, line.buf[i]);
}
break;
}
// code block
case '`':
{
bool is_escaped = false;
if ((i + 1) < line.len) {
is_escaped = line.buf[i + 1] == '`';
}
instream_t in = istrInitLen(line.buf + i, line.len - i);
istrSkip(&in, is_escaped ? 2 : 1); // skip `
ostrPuts(out, strv("<code>"));
while (!istrIsFinished(in)) {
strview_t code = istrGetView(&in, '`');
markdown__escape(code, out);
if (!is_escaped || istrPeek(&in) == '`') {
break;
}
ostrPutc(out, '`');
}
ostrPuts(out, strv("</code>"));
i += istrTell(in);
break;
}
default:
ostrPutc(out, line.buf[i]);
break;
}
}
if (add_newline && !md->code.is_in_block) {
ostrPutc(out, '\n');
}
}
static void markdown__empty_line(markdown_ctx_t *md, outstream_t *out) {
// close lists
while (md->list.count > 0) {
if (md->list.list_is_ordered[--md->list.count]) {
ostrPuts(out, strv("</ol>\n"));
}
else {
ostrPuts(out, strv("</ul>\n"));
}
}
md->list.indent = -1;
// close paragraph
if (md->is_in_paragraph) {
ostrPuts(out, strv("</p>\n"));
}
md->is_in_paragraph = false;
}
static void markdown__close_list(markdown_ctx_t *md, outstream_t *out) {
if (md->list.count > 0) {
if (md->list.list_is_ordered[--md->list.count]) {
ostrPuts(out, strv("</ol>\n"));
}
else {
ostrPuts(out, strv("</ul>\n"));
}
}
}
static void markdown__escape(strview_t view, outstream_t *out) {
for (usize i = 0; i < view.len; ++i) {
switch (view.buf[i]){
case '&':
ostrPuts(out, strv("&amp"));
break;
case '<':
ostrPuts(out, strv("&lt"));
break;
case '>':
ostrPuts(out, strv("&gt"));
break;
default:
ostrPutc(out, view.buf[i]);
break;
}
}
}

View file

@ -1,59 +0,0 @@
#pragma once
#include "str.h"
typedef struct outstream_t outstream_t;
typedef struct {
strview_t lang;
void *userdata;
void (*init)(void *userdata);
void (*finish)(void *userdata);
void (*callback)(strview_t line, outstream_t *out, void *userdata);
} md_parser_t;
typedef struct {
md_parser_t *parsers;
int parsers_count;
ini_t *out_config;
} md_options_t;
typedef struct ini_t ini_t;
str_t markdown(arena_t *arena, arena_t scratch, strview_t filename, md_options_t *options);
str_t markdownStr(arena_t *arena, strview_t markdown_str, md_options_t *options);
/*
md-lite
a subset of markdown that can be parsed line by line
rules:
begin of file:
[ ] if there are three dashes (---), everythin until the next three dashes will be read as an ini config
begin of line:
[x] n # -> <h1..n>
[x] *** or --- or ___ on their own line -> <hr>
[x] - or * -> unordered list
[x] n. -> ordered list
[x] ```xyz and newline -> code block of language <xyz> (xyz is optional)
mid of line:
[x] * -> italic
[x] ** -> bold
[x] *** -> bold and italic
[x] [text](link) -> link
[x] ![text](link) -> image
[x] ` -> code block until next backtick
other:
[x] empty line -> </p>
[x] \ -> escape character
todo?:
[ ] two space at end of line or \ -> <br>
[ ] indent inside list -> continue in point
[ ] 4 spaces -> line code block (does NOT work with multiline, use ``` instead)
[ ] <url> -> link
[ ] [text](link "title") -> link
[ ] fix ***both***
*/

632
net.c Normal file
View file

@ -0,0 +1,632 @@
#include "net.h"
#include "arena.h"
#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);
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, 5);
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;
}
}
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 sha1Str(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 outlen = arena_tell(arena) - start;
return (buffer_t){
.data = out,
.len = outlen,
};
}

190
net.h Normal file
View file

@ -0,0 +1,190 @@
#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_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;
strview_t body;
} http_request_desc_t;
// 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);
// 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 Normal file
View file

@ -0,0 +1,235 @@
#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", path);
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;
}

216
os.h Normal file
View file

@ -0,0 +1,216 @@
#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_RESET,
LOG_COL_BLACK,
LOG_COL_BLUE,
LOG_COL_GREEN,
LOG_COL_CYAN,
LOG_COL_RED,
LOG_COL_MAGENTA,
LOG_COL_YELLOW,
LOG_COL_WHITE,
} 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);
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 path);
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);
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);
// == VMEM ======================================
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

844
parsers.c Normal file
View file

@ -0,0 +1,844 @@
#include "parsers.h"
#include "os.h"
// == INI ============================================
void ini__parse(arena_t *arena, ini_t *ini, const iniopt_t *options);
ini_t ini_parse(arena_t *arena, strview_t filename, iniopt_t *opt) {
oshandle_t fp = os_file_open(filename, FILEMODE_READ);
ini_t out = ini_parse_fp(arena, fp, opt);
os_file_close(fp);
return out;
}
ini_t ini_parse_fp(arena_t *arena, oshandle_t file, iniopt_t *opt) {
str_t data = os_file_read_all_str_fp(arena, file);
return ini_parse_str(arena, strv(data), opt);
}
ini_t ini_parse_str(arena_t *arena, strview_t str, iniopt_t *opt) {
ini_t out = {
.text = str,
.tables = NULL,
};
ini__parse(arena, &out, opt);
return out;
}
bool ini_is_valid(ini_t *ini) {
return ini && !strv_is_empty(ini->text);
}
initable_t *ini_get_table(ini_t *ini, strview_t name) {
initable_t *t = ini ? ini->tables : NULL;
while (t) {
if (strv_equals(t->name, name)) {
return t;
}
t = t->next;
}
return NULL;
}
inivalue_t *ini_get(initable_t *table, strview_t key) {
inivalue_t *v = table ? table->values : NULL;
while (v) {
if (strv_equals(v->key, key)) {
return v;
}
v = v->next;
}
return NULL;
}
iniarray_t ini_as_arr(arena_t *arena, inivalue_t *value, char delim) {
strview_t v = value ? value->value : STRV_EMPTY;
if (!delim) delim = ' ';
strview_t *beg = (strview_t *)arena->cur;
usize count = 0;
usize start = 0;
for (usize i = 0; i < v.len; ++i) {
if (v.buf[i] == delim) {
strview_t arrval = strv_trim(strv_sub(v, start, i));
if (!strv_is_empty(arrval)) {
strview_t *newval = alloc(arena, strview_t);
*newval = arrval;
++count;
}
start = i + 1;
}
}
strview_t last = strv_trim(strv_sub(v, start, SIZE_MAX));
if (!strv_is_empty(last)) {
strview_t *newval = alloc(arena, strview_t);
*newval = last;
++count;
}
return (iniarray_t){
.values = beg,
.count = count,
};
}
u64 ini_as_uint(inivalue_t *value) {
strview_t v = value ? value->value : STRV_EMPTY;
instream_t in = istr_init(v);
u64 out = 0;
if (!istr_get_u64(&in, &out)) {
out = 0;
}
return out;
}
i64 ini_as_int(inivalue_t *value) {
strview_t v = value ? value->value : STRV_EMPTY;
instream_t in = istr_init(v);
i64 out = 0;
if (!istr_get_i64(&in, &out)) {
out = 0;
}
return out;
}
double ini_as_num(inivalue_t *value) {
strview_t v = value ? value->value : STRV_EMPTY;
instream_t in = istr_init(v);
double out = 0;
if (!istr_get_num(&in, &out)) {
out = 0;
}
return out;
}
bool ini_as_bool(inivalue_t *value) {
strview_t v = value ? value->value : STRV_EMPTY;
instream_t in = istr_init(v);
bool out = 0;
if (!istr_get_bool(&in, &out)) {
out = 0;
}
return out;
}
///// ini-private ////////////////////////////////////
iniopt_t ini__get_options(const iniopt_t *options) {
iniopt_t out = {
.key_value_divider = '=',
.comment_vals = strv(";#"),
};
#define SETOPT(v) out.v = options->v ? options->v : out.v
if (options) {
SETOPT(key_value_divider);
SETOPT(merge_duplicate_keys);
SETOPT(merge_duplicate_tables);
out.comment_vals = strv_is_empty(options->comment_vals) ? out.comment_vals : options->comment_vals;
}
#undef SETOPT
return out;
}
void ini__add_value(arena_t *arena, initable_t *table, instream_t *in, iniopt_t *opts) {
assert(table);
strview_t key = strv_trim(istr_get_view(in, opts->key_value_divider));
istr_skip(in, 1);
strview_t value = strv_trim(istr_get_view(in, '\n'));
usize comment_pos = strv_find_either(value, opts->comment_vals, 0);
if (comment_pos != STR_NONE) {
value = strv_sub(value, 0, comment_pos);
}
istr_skip(in, 1);
inivalue_t *newval = NULL;
if (opts->merge_duplicate_keys) {
newval = table->values;
while (newval) {
if (strv_equals(newval->key, key)) {
break;
}
newval = newval->next;
}
}
if (newval) {
newval->value = value;
}
else {
newval = alloc(arena, inivalue_t);
newval->key = key;
newval->value = value;
if (!table->values) {
table->values = newval;
}
else {
table->tail->next = newval;
}
table->tail = newval;
}
}
void ini__add_table(arena_t *arena, ini_t *ctx, instream_t *in, iniopt_t *options) {
istr_skip(in, 1); // skip [
strview_t name = istr_get_view(in, ']');
istr_skip(in, 1); // skip ]
initable_t *table = NULL;
if (options->merge_duplicate_tables) {
table = ctx->tables;
while (table) {
if (strv_equals(table->name, name)) {
break;
}
table = table->next;
}
}
if (!table) {
table = alloc(arena, initable_t);
table->name = name;
if (!ctx->tables) {
ctx->tables = table;
}
else {
ctx->tail->next = table;
}
ctx->tail = table;
}
istr_ignore_and_skip(in, '\n');
while (!istr_is_finished(in)) {
switch (istr_peek(in)) {
case '\n': // fallthrough
case '\r':
return;
case '#': // fallthrough
case ';':
istr_ignore_and_skip(in, '\n');
break;
default:
ini__add_value(arena, table, in, options);
break;
}
}
}
void ini__parse(arena_t *arena, ini_t *ini, const iniopt_t *options) {
iniopt_t opts = ini__get_options(options);
initable_t *root = alloc(arena, initable_t);
root->name = INI_ROOT;
ini->tables = root;
ini->tail = root;
instream_t in = istr_init(ini->text);
while (!istr_is_finished(&in)) {
istr_skip_whitespace(&in);
switch (istr_peek(&in)) {
case '[':
ini__add_table(arena, ini, &in, &opts);
break;
case '#': // fallthrough
case ';':
istr_ignore_and_skip(&in, '\n');
break;
default:
ini__add_value(arena, ini->tables, &in, &opts);
break;
}
}
}
// == JSON ===========================================
bool json__parse_obj(arena_t *arena, instream_t *in, jsonflags_e flags, json_t **out);
json_t *json_parse(arena_t *arena, strview_t filename, jsonflags_e flags) {
str_t data = os_file_read_all_str(arena, filename);
return json_parse_str(arena, strv(data), flags);
}
json_t *json_parse_str(arena_t *arena, strview_t str, jsonflags_e flags) {
arena_t before = *arena;
json_t *root = alloc(arena, json_t);
root->type = JSON_OBJECT;
instream_t in = istr_init(str);
if (!json__parse_obj(arena, &in, flags, &root->object)) {
// reset arena
*arena = before;
return NULL;
}
return root;
}
json_t *json_get(json_t *node, strview_t key) {
if (!node) return NULL;
if (node->type != JSON_OBJECT) {
err("passed type is not an object");
return NULL;
}
node = node->object;
while (node) {
if (strv_equals(node->key, key)) {
return node;
}
node = node->next;
}
return NULL;
}
void json__pretty_print_value(json_t *value, int indent, const json_pretty_opts_t *options);
void json_pretty_print(json_t *root, const json_pretty_opts_t *options) {
json_pretty_opts_t default_options = { 0 };
if (options) {
memmove(&default_options, options, sizeof(json_pretty_opts_t));
}
if (!os_handle_valid(default_options.custom_target)) {
default_options.custom_target = os_stdout();
}
if (!default_options.use_custom_colours) {
os_log_colour_e default_col[JSON_PRETTY_COLOUR__COUNT] = {
LOG_COL_YELLOW, // JSON_PRETTY_COLOUR_KEY,
LOG_COL_CYAN, // JSON_PRETTY_COLOUR_STRING,
LOG_COL_BLUE, // JSON_PRETTY_COLOUR_NUM,
LOG_COL_BLACK, // JSON_PRETTY_COLOUR_NULL,
LOG_COL_GREEN, // JSON_PRETTY_COLOUR_TRUE,
LOG_COL_RED, // JSON_PRETTY_COLOUR_FALSE,
};
memmove(default_options.colours, default_col, sizeof(default_col));
}
json__pretty_print_value(root, 0, &default_options);
os_file_putc(default_options.custom_target, '\n');
}
///// json-private ///////////////////////////////////
#define json__ensure(c) json__check_char(in, c)
bool json__parse_value(arena_t *arena, instream_t *in, jsonflags_e flags, json_t **out);
bool json__check_char(instream_t *in, char c) {
if (istr_get(in) == c) {
return true;
}
istr_rewind_n(in, 1);
err("wrong character at %zu, should be '%c' but is 0x%02x '%c'", istr_tell(in), c, istr_peek(in), istr_peek(in));
return false;
}
bool json__is_value_finished(instream_t *in) {
usize old_pos = istr_tell(in);
istr_skip_whitespace(in);
switch(istr_peek(in)) {
case '}': // fallthrough
case ']': // fallthrough
case ',':
return true;
}
in->cur = in->beg + old_pos;
return false;
}
bool json__parse_null(instream_t *in) {
strview_t null_view = istr_get_view_len(in, 4);
bool is_valid = true;
if (!strv_equals(null_view, strv("null"))) {
err("should be null but is: (%.*s) at %zu", null_view.len, null_view.buf, istr_tell(in));
is_valid = false;
}
if (!json__is_value_finished(in)) {
err("null, should be finished, but isn't at %zu", istr_tell(in));
is_valid = false;
}
return is_valid;
}
bool json__parse_array(arena_t *arena, instream_t *in, jsonflags_e flags, json_t **out) {
json_t *head = NULL;
if (!json__ensure('[')) {
goto fail;
}
istr_skip_whitespace(in);
// if it is an empty array
if (istr_peek(in) == ']') {
istr_skip(in, 1);
goto success;
}
if (!json__parse_value(arena, in, flags, &head)) {
goto fail;
}
json_t *cur = head;
while (true) {
istr_skip_whitespace(in);
switch (istr_get(in)) {
case ']':
goto success;
case ',':
{
istr_skip_whitespace(in);
// trailing comma
if (istr_peek(in) == ']') {
if (flags & JSON_NO_TRAILING_COMMAS) {
err("trailing comma in array at at %zu: (%c)(%d)", istr_tell(in), *in->cur, *in->cur);
goto fail;
}
else {
continue;
}
}
json_t *next = NULL;
if (!json__parse_value(arena, in, flags, &next)) {
goto fail;
}
cur->next = next;
next->prev = cur;
cur = next;
break;
}
default:
istr_rewind_n(in, 1);
err("unknown char after array at %zu: (%c)(%d)", istr_tell(in), *in->cur, *in->cur);
goto fail;
}
}
success:
*out = head;
return true;
fail:
*out = NULL;
return false;
}
bool json__parse_string(arena_t *arena, instream_t *in, strview_t *out) {
COLLA_UNUSED(arena);
*out = STRV_EMPTY;
istr_skip_whitespace(in);
if (!json__ensure('"')) {
goto fail;
}
const char *from = in->cur;
for (; !istr_is_finished(in) && *in->cur != '"'; ++in->cur) {
if (istr_peek(in) == '\\') {
++in->cur;
}
}
usize len = in->cur - from;
*out = strv(from, len);
if (!json__ensure('"')) {
goto fail;
}
return true;
fail:
return false;
}
bool json__parse_pair(arena_t *arena, instream_t *in, jsonflags_e flags, json_t **out) {
strview_t key = {0};
if (!json__parse_string(arena, in, &key)) {
goto fail;
}
// skip preamble
istr_skip_whitespace(in);
if (!json__ensure(':')) {
goto fail;
}
if (!json__parse_value(arena, in, flags, out)) {
goto fail;
}
(*out)->key = key;
return true;
fail:
*out = NULL;
return false;
}
bool json__parse_obj(arena_t *arena, instream_t *in, jsonflags_e flags, json_t **out) {
if (!json__ensure('{')) {
goto fail;
}
istr_skip_whitespace(in);
// if it is an empty object
if (istr_peek(in) == '}') {
istr_skip(in, 1);
*out = NULL;
return true;
}
json_t *head = NULL;
if (!json__parse_pair(arena, in, flags, &head)) {
goto fail;
}
json_t *cur = head;
while (true) {
istr_skip_whitespace(in);
switch (istr_get(in)) {
case '}':
goto success;
case ',':
{
istr_skip_whitespace(in);
// trailing commas
if (!(flags & JSON_NO_TRAILING_COMMAS) && istr_peek(in) == '}') {
goto success;
}
json_t *next = NULL;
if (!json__parse_pair(arena, in, flags, &next)) {
goto fail;
}
cur->next = next;
next->prev = cur;
cur = next;
break;
}
default:
istr_rewind_n(in, 1);
err("unknown char after object at %zu: (%c)(%d)", istr_tell(in), *in->cur, *in->cur);
goto fail;
}
}
success:
*out = head;
return true;
fail:
*out = NULL;
return false;
}
bool json__parse_value(arena_t *arena, instream_t *in, jsonflags_e flags, json_t **out) {
json_t *val = alloc(arena, json_t);
istr_skip_whitespace(in);
switch (istr_peek(in)) {
// object
case '{':
if (!json__parse_obj(arena, in, flags, &val->object)) {
goto fail;
}
val->type = JSON_OBJECT;
break;
// array
case '[':
if (!json__parse_array(arena, in, flags, &val->array)) {
goto fail;
}
val->type = JSON_ARRAY;
break;
// string
case '"':
if (!json__parse_string(arena, in, &val->string)) {
goto fail;
}
val->type = JSON_STRING;
break;
// boolean
case 't': // fallthrough
case 'f':
if (!istr_get_bool(in, &val->boolean)) {
goto fail;
}
val->type = JSON_BOOL;
break;
// null
case 'n':
if (!json__parse_null(in)) {
goto fail;
}
val->type = JSON_NULL;
break;
// comment
case '/':
err("TODO comments");
break;
// number
default:
if (!istr_get_num(in, &val->number)) {
goto fail;
}
val->type = JSON_NUMBER;
break;
}
*out = val;
return true;
fail:
*out = NULL;
return false;
}
#undef json__ensure
#define JSON_PRETTY_INDENT(ind) for (int i = 0; i < ind; ++i) os_file_puts(options->custom_target, strv(" "))
void json__pretty_print_value(json_t *value, int indent, const json_pretty_opts_t *options) {
switch (value->type) {
case JSON_NULL:
os_log_set_colour(options->colours[JSON_PRETTY_COLOUR_NULL]);
os_file_puts(options->custom_target, strv("null"));
os_log_set_colour(LOG_COL_RESET);
break;
case JSON_ARRAY:
os_file_puts(options->custom_target, strv("[\n"));
for_each (node, value->array) {
JSON_PRETTY_INDENT(indent + 1);
json__pretty_print_value(node, indent + 1, options);
if (node->next) {
os_file_putc(options->custom_target, ',');
}
os_file_putc(options->custom_target, '\n');
}
JSON_PRETTY_INDENT(indent);
os_file_putc(options->custom_target, ']');
break;
case JSON_STRING:
os_log_set_colour(options->colours[JSON_PRETTY_COLOUR_STRING]);
os_file_putc(options->custom_target, '\"');
os_file_puts(options->custom_target, value->string);
os_file_putc(options->custom_target, '\"');
os_log_set_colour(LOG_COL_RESET);
break;
case JSON_NUMBER:
{
os_log_set_colour(options->colours[JSON_PRETTY_COLOUR_NUM]);
u8 scratchbuf[256];
arena_t scratch = arena_make(ARENA_STATIC, sizeof(scratchbuf), scratchbuf);
os_file_print(
scratch,
options->custom_target,
"%g",
value->number
);
os_log_set_colour(LOG_COL_RESET);
break;
}
case JSON_BOOL:
os_log_set_colour(options->colours[value->boolean ? JSON_PRETTY_COLOUR_TRUE : JSON_PRETTY_COLOUR_FALSE]);
os_file_puts(options->custom_target, value->boolean ? strv("true") : strv("false"));
os_log_set_colour(LOG_COL_RESET);
break;
case JSON_OBJECT:
os_file_puts(options->custom_target, strv("{\n"));
for_each(node, value->object) {
JSON_PRETTY_INDENT(indent + 1);
os_log_set_colour(options->colours[JSON_PRETTY_COLOUR_KEY]);
os_file_putc(options->custom_target, '\"');
os_file_puts(options->custom_target, node->key);
os_file_putc(options->custom_target, '\"');
os_log_set_colour(LOG_COL_RESET);
os_file_puts(options->custom_target, strv(": "));
json__pretty_print_value(node, indent + 1, options);
if (node->next) {
os_file_putc(options->custom_target, ',');
}
os_file_putc(options->custom_target, '\n');
}
JSON_PRETTY_INDENT(indent);
os_file_putc(options->custom_target, '}');
break;
}
}
#undef JSON_PRETTY_INDENT
// == XML ============================================
xmltag_t *xml__parse_tag(arena_t *arena, instream_t *in);
xml_t xml_parse(arena_t *arena, strview_t filename) {
str_t str = os_file_read_all_str(arena, filename);
return xml_parse_str(arena, strv(str));
}
xml_t xml_parse_str(arena_t *arena, strview_t xmlstr) {
xml_t out = {
.text = xmlstr,
.root = alloc(arena, xmltag_t),
};
instream_t in = istr_init(xmlstr);
while (!istr_is_finished(&in)) {
xmltag_t *tag = xml__parse_tag(arena, &in);
if (out.tail) out.tail->next = tag;
else out.root->child = tag;
out.tail = tag;
}
return out;
}
xmltag_t *xml_get_tag(xmltag_t *parent, strview_t key, bool recursive) {
xmltag_t *t = parent ? parent->child : NULL;
while (t) {
if (strv_equals(key, t->key)) {
return t;
}
if (recursive && t->child) {
xmltag_t *out = xml_get_tag(t, key, recursive);
if (out) {
return out;
}
}
t = t->next;
}
return NULL;
}
strview_t xml_get_attribute(xmltag_t *tag, strview_t key) {
xmlattr_t *a = tag ? tag->attributes : NULL;
while (a) {
if (strv_equals(key, a->key)) {
return a->value;
}
a = a->next;
}
return STRV_EMPTY;
}
///// xml-private ////////////////////////////////////
xmlattr_t *xml__parse_attr(arena_t *arena, instream_t *in) {
if (istr_peek(in) != ' ') {
return NULL;
}
strview_t key = strv_trim(istr_get_view(in, '='));
istr_skip(in, 2); // skip = and "
strview_t val = strv_trim(istr_get_view(in, '"'));
istr_skip(in, 1); // skip "
if (strv_is_empty(key) || strv_is_empty(val)) {
warn("key or value empty");
return NULL;
}
xmlattr_t *attr = alloc(arena, xmlattr_t);
attr->key = key;
attr->value = val;
return attr;
}
xmltag_t *xml__parse_tag(arena_t *arena, instream_t *in) {
istr_skip_whitespace(in);
// we're either parsing the body, or we have finished the object
if (istr_peek(in) != '<' || istr_peek_next(in) == '/') {
return NULL;
}
istr_skip(in, 1); // skip <
// meta tag, we don't care about these
if (istr_peek(in) == '?') {
istr_ignore_and_skip(in, '\n');
return NULL;
}
xmltag_t *tag = alloc(arena, xmltag_t);
tag->key = strv_trim(istr_get_view_either(in, strv(" >")));
xmlattr_t *attr = xml__parse_attr(arena, in);
while (attr) {
attr->next = tag->attributes;
tag->attributes = attr;
attr = xml__parse_attr(arena, in);
}
// this tag does not have children, return
if (istr_peek(in) == '/') {
istr_skip(in, 2); // skip / and >
return tag;
}
istr_skip(in, 1); // skip >
xmltag_t *child = xml__parse_tag(arena, in);
while (child) {
if (tag->tail) {
tag->tail->next = child;
tag->tail = child;
}
else {
tag->child = tag->tail = child;
}
child = xml__parse_tag(arena, in);
}
// parse content
istr_skip_whitespace(in);
tag->content = istr_get_view(in, '<');
// closing tag
istr_skip(in, 2); // skip < and /
strview_t closing = strv_trim(istr_get_view(in, '>'));
if (!strv_equals(tag->key, closing)) {
warn("opening and closing tags are different!: (%v) != (%v)", tag->key, closing);
}
istr_skip(in, 1); // skip >
return tag;
}

156
parsers.h Normal file
View file

@ -0,0 +1,156 @@
#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);
// == 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);
#endif

454
server.c
View file

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

View file

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

120
sha1.c
View file

@ -1,120 +0,0 @@
#include "sha1.h"
sha1_t sha1_init(void) {
return (sha1_t) {
.digest = {
0x67452301,
0xEFCDAB89,
0x98BADCFE,
0x10325476,
0xC3D2E1F0,
},
};
}
uint32 sha1_left_rotate(uint32 value, uint32 count) {
return (value << count) ^ (value >> (32 - count));
}
void sha1_process_block(sha1_t *ctx) {
uint32 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);
}
uint32 a = ctx->digest[0];
uint32 b = ctx->digest[1];
uint32 c = ctx->digest[2];
uint32 d = ctx->digest[3];
uint32 e = ctx->digest[4];
for (usize i = 0; i < 80; ++i) {
uint32 f = 0;
uint32 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;
}
uint32 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, uint8 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 uint8 *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));
// memcpy(digest, m_digest, 5 * sizeof(uint32_t));#
sha1_result_t result = {0};
memcpy(result.digest, ctx->digest, sizeof(result.digest));
return result;
}
str_t sha1Str(arena_t *arena, sha1_t *ctx, const void *buf, usize len) {
sha1_result_t result = sha1(ctx, buf, len);
return strFmt(arena, "%08x%08x%08x%08x%08x", result.digest[0], result.digest[1], result.digest[2], result.digest[3], result.digest[4]);
}

18
sha1.h
View file

@ -1,18 +0,0 @@
#pragma once
#include "str.h"
typedef struct {
uint32 digest[5];
uint8 block[64];
usize block_index;
usize byte_count;
} sha1_t;
typedef struct {
uint32 digest[5];
} sha1_result_t;
sha1_t sha1_init(void);
sha1_result_t sha1(sha1_t *ctx, const void *buf, usize len);
str_t sha1Str(arena_t *arena, sha1_t *ctx, const void *buf, usize len);

286
socket.c
View file

@ -1,286 +0,0 @@
#include "socket.h"
#if COLLA_WIN && COLLA_CMT_LIB
#pragma comment(lib, "Ws2_32")
#endif
#if COLLA_WIN
typedef int socklen_t;
bool skInit(void) {
WSADATA w;
int error = WSAStartup(0x0202, &w);
return error == 0;
}
bool skCleanup(void) {
return WSACleanup() == 0;
}
bool skClose(socket_t sock) {
return closesocket(sock) != SOCKET_ERROR;
}
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) {
return WSAPoll(to_poll, num_to_poll, timeout);
}
int skGetError(void) {
return WSAGetLastError();
}
#else
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>
#include <string.h> // strerror
#include <poll.h>
bool skInit(void) {
return true;
}
bool skCleanup(void) {
return true;
}
bool skClose(socket_t sock) {
return close(sock) != SOCKET_ERROR;
}
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) {
return poll(to_poll, num_to_poll, timeout);
}
int skGetError(void) {
return errno;
}
const char *skGetErrorString(void) {
return strerror(errno);
}
#endif
socket_t skOpen(sktype_e type) {
int sock_type = 0;
switch(type) {
case SOCK_TCP: sock_type = SOCK_STREAM; break;
case SOCK_UDP: sock_type = SOCK_DGRAM; break;
default: fatal("skType not recognized: %d", type); break;
}
return skOpenPro(AF_INET, sock_type, 0);
}
socket_t skOpenEx(const char *protocol) {
struct protoent *proto = getprotobyname(protocol);
if(!proto) {
return (socket_t){0};
}
return skOpenPro(AF_INET, SOCK_STREAM, proto->p_proto);
}
socket_t skOpenPro(int af, int type, int protocol) {
return socket(af, type, protocol);
}
bool skIsValid(socket_t sock) {
return sock != SOCKET_ERROR;
}
skaddrin_t skAddrinInit(const char *ip, uint16_t port) {
return (skaddrin_t){
.sin_family = AF_INET,
.sin_port = htons(port),
// TODO use inet_pton instead
.sin_addr.s_addr = inet_addr(ip),
};
}
bool skBind(socket_t sock, const char *ip, uint16_t port) {
skaddrin_t addr = skAddrinInit(ip, port);
return skBindPro(sock, (skaddr_t *)&addr, sizeof(addr));
}
bool skBindPro(socket_t sock, const skaddr_t *name, int namelen) {
return bind(sock, name, namelen) != SOCKET_ERROR;
}
bool skListen(socket_t sock) {
return skListenPro(sock, 1);
}
bool skListenPro(socket_t sock, int backlog) {
return listen(sock, backlog) != SOCKET_ERROR;
}
socket_t skAccept(socket_t sock) {
skaddrin_t addr = {0};
int addr_size = sizeof(addr);
return skAcceptPro(sock, (skaddr_t *)&addr, &addr_size);
}
socket_t skAcceptPro(socket_t sock, skaddr_t *addr, int *addrlen) {
return accept(sock, addr, (socklen_t *)addrlen);
}
bool skConnect(socket_t sock, const char *server, unsigned short server_port) {
// TODO use getaddrinfo insetad
struct hostent *host = gethostbyname(server);
// if gethostbyname fails, inet_addr will also fail and return an easier to debug error
const char *address = server;
if(host) {
address = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]);
}
skaddrin_t addr = skAddrinInit(address, server_port);
return skConnectPro(sock, (skaddr_t *)&addr, sizeof(addr));
}
bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen) {
return connect(sock, name, namelen) != SOCKET_ERROR;
}
int skSend(socket_t sock, const void *buf, int len) {
return skSendPro(sock, buf, len, 0);
}
int skSendPro(socket_t sock, const void *buf, int len, int flags) {
return send(sock, buf, len, flags);
}
int skSendTo(socket_t sock, const void *buf, int len, const skaddrin_t *to) {
return skSendToPro(sock, buf, len, 0, (skaddr_t*)to, sizeof(skaddrin_t));
}
int skSendToPro(socket_t sock, const void *buf, int len, int flags, const skaddr_t *to, int tolen) {
return sendto(sock, buf, len, flags, to, tolen);
}
int skReceive(socket_t sock, void *buf, int len) {
return skReceivePro(sock, buf, len, 0);
}
int skReceivePro(socket_t sock, void *buf, int len, int flags) {
return recv(sock, buf, len, flags);
}
int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from) {
int fromlen = sizeof(skaddr_t);
return skReceiveFromPro(sock, buf, len, 0, (skaddr_t*)from, &fromlen);
}
int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, skaddr_t *from, int *fromlen) {
return recvfrom(sock, buf, len, flags, from, (socklen_t *)fromlen);
}
// put this at the end of file to not make everything else unreadable
#if COLLA_WIN
const char *skGetErrorString(void) {
switch(skGetError()) {
case WSA_INVALID_HANDLE: return "Specified event object handle is invalid.";
case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available.";
case WSA_INVALID_PARAMETER: return "One or more parameters are invalid.";
case WSA_OPERATION_ABORTED: return "Overlapped operation aborted.";
case WSA_IO_INCOMPLETE: return "Overlapped I/O event object not in signaled state.";
case WSA_IO_PENDING: return "Overlapped operations will complete later.";
case WSAEINTR: return "Interrupted function call.";
case WSAEBADF: return "File handle is not valid.";
case WSAEACCES: return "Permission denied.";
case WSAEFAULT: return "Bad address.";
case WSAEINVAL: return "Invalid argument.";
case WSAEMFILE: return "Too many open files.";
case WSAEWOULDBLOCK: return "Resource temporarily unavailable.";
case WSAEINPROGRESS: return "Operation now in progress.";
case WSAEALREADY: return "Operation already in progress.";
case WSAENOTSOCK: return "Socket operation on nonsocket.";
case WSAEDESTADDRREQ: return "Destination address required.";
case WSAEMSGSIZE: return "Message too long.";
case WSAEPROTOTYPE: return "Protocol wrong type for socket.";
case WSAENOPROTOOPT: return "Bad protocol option.";
case WSAEPROTONOSUPPORT: return "Protocol not supported.";
case WSAESOCKTNOSUPPORT: return "Socket type not supported.";
case WSAEOPNOTSUPP: return "Operation not supported.";
case WSAEPFNOSUPPORT: return "Protocol family not supported.";
case WSAEAFNOSUPPORT: return "Address family not supported by protocol family.";
case WSAEADDRINUSE: return "Address already in use.";
case WSAEADDRNOTAVAIL: return "Cannot assign requested address.";
case WSAENETDOWN: return "Network is down.";
case WSAENETUNREACH: return "Network is unreachable.";
case WSAENETRESET: return "Network dropped connection on reset.";
case WSAECONNABORTED: return "Software caused connection abort.";
case WSAECONNRESET: return "Connection reset by peer.";
case WSAENOBUFS: return "No buffer space available.";
case WSAEISCONN: return "Socket is already connected.";
case WSAENOTCONN: return "Socket is not connected.";
case WSAESHUTDOWN: return "Cannot send after socket shutdown.";
case WSAETOOMANYREFS: return "Too many references.";
case WSAETIMEDOUT: return "Connection timed out.";
case WSAECONNREFUSED: return "Connection refused.";
case WSAELOOP: return "Cannot translate name.";
case WSAENAMETOOLONG: return "Name too long.";
case WSAEHOSTDOWN: return "Host is down.";
case WSAEHOSTUNREACH: return "No route to host.";
case WSAENOTEMPTY: return "Directory not empty.";
case WSAEPROCLIM: return "Too many processes.";
case WSAEUSERS: return "User quota exceeded.";
case WSAEDQUOT: return "Disk quota exceeded.";
case WSAESTALE: return "Stale file handle reference.";
case WSAEREMOTE: return "Item is remote.";
case WSASYSNOTREADY: return "Network subsystem is unavailable.";
case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range.";
case WSANOTINITIALISED: return "Successful WSAStartup not yet performed.";
case WSAEDISCON: return "Graceful shutdown in progress.";
case WSAENOMORE: return "No more results.";
case WSAECANCELLED: return "Call has been canceled.";
case WSAEINVALIDPROCTABLE: return "Procedure call table is invalid.";
case WSAEINVALIDPROVIDER: return "Service provider is invalid.";
case WSAEPROVIDERFAILEDINIT: return "Service provider failed to initialize.";
case WSASYSCALLFAILURE: return "System call failure.";
case WSASERVICE_NOT_FOUND: return "Service not found.";
case WSATYPE_NOT_FOUND: return "Class type not found.";
case WSA_E_NO_MORE: return "No more results.";
case WSA_E_CANCELLED: return "Call was canceled.";
case WSAEREFUSED: return "Database query was refused.";
case WSAHOST_NOT_FOUND: return "Host not found.";
case WSATRY_AGAIN: return "Nonauthoritative host not found.";
case WSANO_RECOVERY: return "This is a nonrecoverable error.";
case WSANO_DATA: return "Valid name, no data record of requested type.";
case WSA_QOS_RECEIVERS: return "QoS receivers.";
case WSA_QOS_SENDERS: return "QoS senders.";
case WSA_QOS_NO_SENDERS: return "No QoS senders.";
case WSA_QOS_NO_RECEIVERS: return "QoS no receivers.";
case WSA_QOS_REQUEST_CONFIRMED: return "QoS request confirmed.";
case WSA_QOS_ADMISSION_FAILURE: return "QoS admission error.";
case WSA_QOS_POLICY_FAILURE: return "QoS policy failure.";
case WSA_QOS_BAD_STYLE: return "QoS bad style.";
case WSA_QOS_BAD_OBJECT: return "QoS bad object.";
case WSA_QOS_TRAFFIC_CTRL_ERROR: return "QoS traffic control error.";
case WSA_QOS_GENERIC_ERROR: return "QoS generic error.";
case WSA_QOS_ESERVICETYPE: return "QoS service type error.";
case WSA_QOS_EFLOWSPEC: return "QoS flowspec error.";
case WSA_QOS_EPROVSPECBUF: return "Invalid QoS provider buffer.";
case WSA_QOS_EFILTERSTYLE: return "Invalid QoS filter style.";
case WSA_QOS_EFILTERTYPE: return "Invalid QoS filter type.";
case WSA_QOS_EFILTERCOUNT: return "Incorrect QoS filter count.";
case WSA_QOS_EOBJLENGTH: return "Invalid QoS object length.";
case WSA_QOS_EFLOWCOUNT: return "Incorrect QoS flow count.";
case WSA_QOS_EUNKOWNPSOBJ: return "Unrecognized QoS object.";
case WSA_QOS_EPOLICYOBJ: return "Invalid QoS policy object.";
case WSA_QOS_EFLOWDESC: return "Invalid QoS flow descriptor.";
case WSA_QOS_EPSFLOWSPEC: return "Invalid QoS provider-specific flowspec.";
case WSA_QOS_EPSFILTERSPEC: return "Invalid QoS provider-specific filterspec.";
case WSA_QOS_ESDMODEOBJ: return "Invalid QoS shape discard mode object.";
case WSA_QOS_ESHAPERATEOBJ: return "Invalid QoS shaping rate object.";
case WSA_QOS_RESERVED_PETYPE: return "Reserved policy QoS element type.";
}
return "(nothing)";
}
#endif

View file

@ -1,93 +0,0 @@
#pragma once
#include "collatypes.h"
#if COLLA_TCC
#include "tcc/colla_tcc.h"
#elif COLLA_WIN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#elif COLLA_LIN || COLLA_OSX
#include <sys/socket.h>
#include <netinet/in.h>
#endif
typedef uintptr_t socket_t;
typedef struct sockaddr skaddr_t;
typedef struct sockaddr_in skaddrin_t;
typedef struct pollfd skpoll_t;
#define SOCKET_ERROR (-1)
typedef enum {
SOCK_TCP,
SOCK_UDP,
} sktype_e;
// Initialize sockets, returns true on success
bool skInit(void);
// Terminates sockets, returns true on success
bool skCleanup(void);
// Opens a socket, check socket_t with skValid
socket_t skOpen(sktype_e type);
// Opens a socket using 'protocol', options are
// ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp
// check socket_t with skValid
socket_t skOpenEx(const char *protocol);
// Opens a socket, check socket_t with skValid
socket_t skOpenPro(int af, int type, int protocol);
// Checks that a opened socket is valid, returns true on success
bool skIsValid(socket_t sock);
// Closes a socket, returns true on success
bool skClose(socket_t sock);
// Fill out a sk_addrin_t structure with "ip" and "port"
skaddrin_t skAddrinInit(const char *ip, uint16_t port);
// Associate a local address with a socket
bool skBind(socket_t sock, const char *ip, uint16_t port);
// Associate a local address with a socket
bool skBindPro(socket_t sock, const skaddr_t *name, int namelen);
// Place a socket in a state in which it is listening for an incoming connection
bool skListen(socket_t sock);
// Place a socket in a state in which it is listening for an incoming connection
bool skListenPro(socket_t sock, int backlog);
// Permits an incoming connection attempt on a socket
socket_t skAccept(socket_t sock);
// Permits an incoming connection attempt on a socket
socket_t skAcceptPro(socket_t sock, skaddr_t *addr, int *addrlen);
// Connects to a server (e.g. "127.0.0.1" or "google.com") with a port(e.g. 1234), returns true on success
bool skConnect(socket_t sock, const char *server, unsigned short server_port);
// Connects to a server, returns true on success
bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen);
// Sends data on a socket, returns true on success
int skSend(socket_t sock, const void *buf, int len);
// Sends data on a socket, returns true on success
int skSendPro(socket_t sock, const void *buf, int len, int flags);
// Sends data to a specific destination
int skSendTo(socket_t sock, const void *buf, int len, const skaddrin_t *to);
// Sends data to a specific destination
int skSendToPro(socket_t sock, const void *buf, int len, int flags, const skaddr_t *to, int tolen);
// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error
int skReceive(socket_t sock, void *buf, int len);
// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error
int skReceivePro(socket_t sock, void *buf, int len, int flags);
// Receives a datagram and stores the source address.
int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from);
// Receives a datagram and stores the source address.
int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, skaddr_t *from, int *fromlen);
// Wait for an event on some sockets
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout);
// Returns latest socket error, returns 0 if there is no error
int skGetError(void);
// Returns a human-readable string from a skGetError
const char *skGetErrorString(void);

754
str.c
View file

@ -1,135 +1,143 @@
#include "str.h"
#include "warnings/colla_warn_beg.h"
#include "arena.h"
#include "format.h"
#include "tracelog.h"
#include <limits.h>
#include <math.h>
#include <stdlib.h>
#if COLLA_WIN
#include <windows.h>
#include "win/str_win32.c"
#else
#include <wchar.h>
#endif
#if COLLA_TCC
#include "tcc/colla_tcc.h"
#error "platform not supported"
#endif
// == STR_T ========================================================
str_t strInit(arena_t *arena, const char *buf) {
return buf ? strInitLen(arena, buf, strlen(buf)) : STR_EMPTY;
str_t str_init(arena_t *arena, const char *buf) {
return str_init_len(arena, buf, buf ? strlen(buf) : 0);
}
str_t strInitLen(arena_t *arena, const char *buf, usize len) {
str_t str_init_len(arena_t *arena, const char *buf, usize len) {
if (!buf || !len) return STR_EMPTY;
str_t out = {
.buf = alloc(arena, char, len + 1),
.len = len
};
memcpy(out.buf, buf, len);
return out;
char *str = alloc(arena, char, len + 1);
memmove(str, buf, len);
return (str_t){ str, len };
}
str_t strInitView(arena_t *arena, strview_t view) {
return strInitLen(arena, view.buf, view.len);
str_t str_init_view(arena_t *arena, strview_t view) {
return str_init_len(arena, view.buf, view.len);
}
str_t strFmt(arena_t *arena, const char *fmt, ...) {
str_t str_fmt(arena_t *arena, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
str_t out = strFmtv(arena, fmt, args);
str_t out = str_fmtv(arena, fmt, args);
va_end(args);
return out;
}
str_t strFmtv(arena_t *arena, const char *fmt, va_list args) {
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 = fmtBufferv(NULL, 0, fmt, vcopy);
int len = fmt_bufferv(NULL, 0, fmt, vcopy);
va_end(vcopy);
char *buffer = alloc(arena, char, len + 1);
fmtBufferv(buffer, len + 1, fmt, args);
fmt_bufferv(buffer, len + 1, fmt, args);
return (str_t) { .buf = buffer, .len = (usize)len };
}
str_t strFromWChar(arena_t *arena, const wchar_t *src) {
return strFromWCharLen(arena, src, 0);
}
str_t strFromWCharLen(arena_t *arena, const wchar_t *src, usize srclen) {
if (!src) return STR_EMPTY;
if (!srclen) srclen = wcslen(src);
str_t out = {0};
#if COLLA_WIN
int outlen = WideCharToMultiByte(
CP_UTF8, 0,
src, (int)srclen,
NULL, 0,
NULL, NULL
);
if (outlen == 0) {
unsigned long error = GetLastError();
if (error == ERROR_NO_UNICODE_TRANSLATION) {
err("couldn't translate wide string (%S) to utf8, no unicode translation", src);
}
else {
err("couldn't translate wide string (%S) to utf8, %u", error);
}
return STR_EMPTY;
}
out.buf = alloc(arena, char, outlen + 1);
WideCharToMultiByte(
CP_UTF8, 0,
src, (int)srclen,
out.buf, outlen,
NULL, NULL
);
out.len = outlen;
#elif COLLA_LIN
fatal("strFromWChar not implemented yet!");
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;
}
bool strEquals(str_t a, str_t b) {
return strCompare(a, b) == 0;
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
}
int strCompare(str_t a, str_t b) {
return a.len == b.len ?
memcmp(a.buf, b.buf, a.len) :
(int)(a.len - b.len);
str16_t str16_from_str(arena_t *arena, str_t src) {
return strv_to_str16(arena, strv(src));
}
str_t strDup(arena_t *arena, str_t src) {
return strInitLen(arena, src.buf, src.len);
bool str_equals(str_t a, str_t b) {
return str_compare(a, b) == 0;
}
bool strIsEmpty(str_t ctx) {
return ctx.len == 0 || ctx.buf == NULL;
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);
}
void strReplace(str_t *ctx, char from, char to) {
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) {
@ -137,145 +145,68 @@ void strReplace(str_t *ctx, char from, char to) {
}
}
strview_t strSub(str_t ctx, usize from, usize to) {
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 };
}
void strLower(str_t *ctx) {
char *buf = ctx->buf;
for (usize i = 0; i < ctx->len; ++i) {
buf[i] = (buf[i] >= 'A' && buf[i] <= 'Z') ?
buf[i] += 'a' - 'A' :
buf[i];
}
}
void strUpper(str_t *ctx) {
char *buf = ctx->buf;
for (usize i = 0; i < ctx->len; ++i) {
buf[i] = (buf[i] >= 'a' && buf[i] <= 'z') ?
buf[i] -= 'a' - 'A' :
buf[i];
}
}
str_t strToLower(arena_t *arena, str_t ctx) {
strLower(&ctx);
return strDup(arena, ctx);
}
str_t strToUpper(arena_t *arena, str_t ctx) {
strUpper(&ctx);
return strDup(arena, ctx);
}
// == STRVIEW_T ====================================================
strview_t strvInit(const char *cstr) {
return (strview_t){
.buf = cstr,
.len = cstr ? strlen(cstr) : 0,
};
strview_t strv_init(const char *cstr) {
return strv_init_len(cstr, cstr ? strlen(cstr) : 0);
}
strview_t strvInitLen(const char *buf, usize size) {
strview_t strv_init_len(const char *buf, usize size) {
return (strview_t){
.buf = buf,
.len = size,
};
}
strview_t strvInitStr(str_t str) {
strview_t strv_init_str(str_t str) {
return (strview_t){
.buf = str.buf,
.len = str.len
};
}
bool strvIsEmpty(strview_t ctx) {
bool strv_is_empty(strview_t ctx) {
return ctx.len == 0 || !ctx.buf;
}
bool strvEquals(strview_t a, strview_t b) {
return strvCompare(a, b) == 0;
bool strv_equals(strview_t a, strview_t b) {
return strv_compare(a, b) == 0;
}
int strvCompare(strview_t a, strview_t b) {
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 strvFront(strview_t ctx) {
char strv_front(strview_t ctx) {
return ctx.len > 0 ? ctx.buf[0] : '\0';
}
char strvBack(strview_t ctx) {
char strv_back(strview_t ctx) {
return ctx.len > 0 ? ctx.buf[ctx.len - 1] : '\0';
}
wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen) {
wchar_t *out = NULL;
int len = 0;
if (strvIsEmpty(ctx)) {
goto error;
str16_t strv_to_str16(arena_t *arena, strview_t src) {
return strv_os_to_str16(arena, src);
}
#if COLLA_WIN
len = MultiByteToWideChar(
CP_UTF8, 0,
ctx.buf, (int)ctx.len,
NULL, 0
);
if (len == 0) {
unsigned long error = GetLastError();
if (error == ERROR_NO_UNICODE_TRANSLATION) {
err("couldn't translate string (%v) to a wide string, no unicode translation", ctx);
}
else {
err("couldn't translate string (%v) to a wide string, %u", ctx, error);
}
goto error;
}
out = alloc(arena, wchar_t, len + 1);
MultiByteToWideChar(
CP_UTF8, 0,
ctx.buf, (int)ctx.len,
out, len
);
#elif COLLA_LIN
fatal("strFromWChar not implemented yet!");
#endif
error:
if (outlen) {
*outlen = (usize)len;
}
return out;
}
TCHAR *strvToTChar(arena_t *arena, strview_t str) {
tstr_t strv_to_tstr(arena_t *arena, strview_t src) {
#if UNICODE
return strvToWChar(arena, str, NULL);
return strv_to_str16(arena, src);
#else
char *cstr = alloc(arena, char, str.len + 1);
memcpy(cstr, str.buf, str.len);
return cstr;
return str(arena, src);
#endif
}
strview_t strvRemovePrefix(strview_t ctx, usize n) {
strview_t strv_remove_prefix(strview_t ctx, usize n) {
if (n > ctx.len) n = ctx.len;
return (strview_t){
.buf = ctx.buf + n,
@ -283,7 +214,7 @@ strview_t strvRemovePrefix(strview_t ctx, usize n) {
};
}
strview_t strvRemoveSuffix(strview_t ctx, usize n) {
strview_t strv_remove_suffix(strview_t ctx, usize n) {
if (n > ctx.len) n = ctx.len;
return (strview_t){
.buf = ctx.buf,
@ -291,11 +222,11 @@ strview_t strvRemoveSuffix(strview_t ctx, usize n) {
};
}
strview_t strvTrim(strview_t ctx) {
return strvTrimLeft(strvTrimRight(ctx));
strview_t strv_trim(strview_t ctx) {
return strv_trim_left(strv_trim_right(ctx));
}
strview_t strvTrimLeft(strview_t 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];
@ -308,7 +239,7 @@ strview_t strvTrimLeft(strview_t ctx) {
return out;
}
strview_t strvTrimRight(strview_t ctx) {
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];
@ -320,29 +251,30 @@ strview_t strvTrimRight(strview_t ctx) {
return out;
}
strview_t strvSub(strview_t ctx, usize from, usize to) {
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 strvStartsWith(strview_t ctx, char c) {
bool strv_starts_with(strview_t ctx, char c) {
return ctx.len > 0 && ctx.buf[0] == c;
}
bool strvStartsWithView(strview_t ctx, strview_t view) {
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 strvEndsWith(strview_t ctx, char c) {
bool strv_ends_with(strview_t ctx, char c) {
return ctx.len > 0 && ctx.buf[ctx.len - 1] == c;
}
bool strvEndsWithView(strview_t ctx, strview_t view) {
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 strvContains(strview_t ctx, char c) {
bool strv_contains(strview_t ctx, char c) {
for(usize i = 0; i < ctx.len; ++i) {
if(ctx.buf[i] == c) {
return true;
@ -351,7 +283,7 @@ bool strvContains(strview_t ctx, char c) {
return false;
}
bool strvContainsView(strview_t ctx, strview_t view) {
bool strv_contains_view(strview_t ctx, strview_t view) {
if (ctx.len < view.len) return false;
usize end = ctx.len - view.len;
for (usize i = 0; i < end; ++i) {
@ -362,7 +294,17 @@ bool strvContainsView(strview_t ctx, strview_t view) {
return false;
}
usize strvFind(strview_t ctx, char c, usize from) {
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;
@ -371,8 +313,7 @@ usize strvFind(strview_t ctx, char c, usize from) {
return STR_NONE;
}
usize strvFindView(strview_t ctx, strview_t view, usize from) {
if (ctx.len < view.len) return STR_NONE;
usize strv_find_view(strview_t ctx, strview_t view, usize from) {
usize end = ctx.len - view.len;
for (usize i = from; i < end; ++i) {
if (memcmp(ctx.buf + i, view.buf, view.len) == 0) {
@ -382,7 +323,20 @@ usize strvFindView(strview_t ctx, strview_t view, usize from) {
return STR_NONE;
}
usize strvRFind(strview_t ctx, char c, usize from_right) {
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) {
@ -393,7 +347,8 @@ usize strvRFind(strview_t ctx, char c, usize from_right) {
return STR_NONE;
}
usize strvRFindView(strview_t ctx, strview_t view, usize from_right) {
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;
@ -405,4 +360,403 @@ usize strvRFindView(strview_t ctx, strview_t view, usize from_right) {
return STR_NONE;
}
#include "warnings/colla_warn_beg.h"
// == 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';
}
// == 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');
str_t out = {
.buf = ctx->beg,
.len = ostr_tell(ctx) - 1,
};
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);
}

299
str.h
View file

@ -1,36 +1,50 @@
#pragma once
#ifndef COLLA_STR_H
#define COLLA_STR_H
#include <stdarg.h> // va_list
#include <string.h> // strlen
#include "collatypes.h"
typedef struct arena_t arena_t;
#include "core.h"
#include "darr.h"
#define STR_NONE SIZE_MAX
typedef struct {
typedef struct str_t str_t;
struct str_t {
char *buf;
usize len;
} str_t;
};
typedef struct {
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;
} strview_t;
};
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 *: strInit, \
char *: strInit, \
const wchar_t *: strFromWChar, \
wchar_t *: strFromWChar, \
strview_t: strInitView \
const char *: str_init, \
char *: str_init, \
strview_t: str_init_view \
)(arena, x)
#define str__2(arena, cstr, clen) strInitLen(arena, cstr, clen)
#define str__2(arena, cstr, clen) str_init_len(arena, cstr, clen)
#define str__impl(_1, _2, n, ...) str__##n
// either:
@ -40,80 +54,223 @@ typedef struct {
#define STR_EMPTY (str_t){0}
str_t strInit(arena_t *arena, const char *buf);
str_t strInitLen(arena_t *arena, const char *buf, usize len);
str_t strInitView(arena_t *arena, strview_t view);
str_t strFmt(arena_t *arena, const char *fmt, ...);
str_t strFmtv(arena_t *arena, const char *fmt, va_list args);
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);
str_t strFromWChar(arena_t *arena, const wchar_t *src);
str_t strFromWCharLen(arena_t *arena, const wchar_t *src, usize srclen);
tstr_t tstr_init(TCHAR *str, usize optional_len);
str16_t str16_init(u16 *str, usize optional_len);
bool strEquals(str_t a, str_t b);
int strCompare(str_t a, str_t b);
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);
str_t strDup(arena_t *arena, str_t src);
bool strIsEmpty(str_t ctx);
bool str_equals(str_t a, str_t b);
int str_compare(str_t a, str_t b);
void strReplace(str_t *ctx, char from, char to);
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 strSub(str_t ctx, usize from, usize to);
void strLower(str_t *ctx);
void strUpper(str_t *ctx);
str_t strToLower(arena_t *arena, str_t ctx);
str_t strToUpper(arena_t *arena, str_t ctx);
strview_t str_sub(str_t ctx, usize from, usize to);
// == STRVIEW_T ====================================================
#define strv__1(x) \
_Generic((x), \
char *: strvInit, \
const char *: strvInit, \
str_t: strvInitStr \
)(x)
#define strv__2(cstr, clen) strvInitLen(cstr, clen)
#define strv__impl(_1, _2, n, ...) strv__##n
#define strv(...) strv__impl(__VA_ARGS__, 2, 1, 0)(__VA_ARGS__)
// 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}
strview_t strvInit(const char *cstr);
strview_t strvInitLen(const char *buf, usize size);
strview_t strvInitStr(str_t str);
// needed for strv__init_literal _Generic implementation, it's never actually called
inline strview_t strv__ignore(str_t s, size_t l) {
COLLA_UNUSED(s); COLLA_UNUSED(l);
return STRV_EMPTY;
}
bool strvIsEmpty(strview_t ctx);
bool strvEquals(strview_t a, strview_t b);
int strvCompare(strview_t a, strview_t b);
#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)
char strvFront(strview_t ctx);
char strvBack(strview_t ctx);
#define strv__1(x) \
_Generic((x), \
char *: strv_init, \
const char *: strv_init, \
str_t: strv_init_str \
)(x)
wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen);
TCHAR *strvToTChar(arena_t *arena, strview_t str);
#define strv__2(cstr, clen) strv_init_len(cstr, clen)
strview_t strvRemovePrefix(strview_t ctx, usize n);
strview_t strvRemoveSuffix(strview_t ctx, usize n);
strview_t strvTrim(strview_t ctx);
strview_t strvTrimLeft(strview_t ctx);
strview_t strvTrimRight(strview_t ctx);
#define strv__impl(_1, _2, n, ...) strv__##n
strview_t strvSub(strview_t ctx, usize from, usize to);
#define strv(...) strv__check(__VA_ARGS__) ? strv__init_literal(__VA_ARGS__) : strv__impl(__VA_ARGS__, 2, 1, 0)(__VA_ARGS__)
bool strvStartsWith(strview_t ctx, char c);
bool strvStartsWithView(strview_t ctx, strview_t view);
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 strvEndsWith(strview_t ctx, char c);
bool strvEndsWithView(strview_t ctx, strview_t view);
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);
bool strvContains(strview_t ctx, char c);
bool strvContainsView(strview_t ctx, strview_t view);
char strv_front(strview_t ctx);
char strv_back(strview_t ctx);
usize strvFind(strview_t ctx, char c, usize from);
usize strvFindView(strview_t ctx, strview_t view, usize from);
str16_t strv_to_str16(arena_t *arena, strview_t src);
tstr_t strv_to_tstr(arena_t *arena, strview_t src);
usize strvRFind(strview_t ctx, char c, usize from_right);
usize strvRFindView(strview_t ctx, strview_t view, usize from_right);
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);
// == 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

View file

@ -1,655 +0,0 @@
#include "strstream.h"
#include "warnings/colla_warn_beg.h"
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <ctype.h>
#include <math.h> // HUGE_VALF
#include "tracelog.h"
#include "arena.h"
#if COLLA_WIN && COLLA_TCC
#define strtoull _strtoui64
#define strtoll _strtoi64
#define strtof strtod
#endif
/* == INPUT STREAM ============================================ */
instream_t istrInit(const char *str) {
return istrInitLen(str, strlen(str));
}
instream_t istrInitLen(const char *str, usize len) {
instream_t res;
res.start = res.cur = str;
res.size = len;
return res;
}
char istrGet(instream_t *ctx) {
return ctx && ctx->cur ? *ctx->cur++ : '\0';
}
void istrIgnore(instream_t *ctx, char delim) {
for (; !istrIsFinished(*ctx) && *ctx->cur != delim; ++ctx->cur) {
}
}
void istrIgnoreAndSkip(instream_t *ctx, char delim) {
istrIgnore(ctx, delim);
istrSkip(ctx, 1);
}
char istrPeek(instream_t *ctx) {
return ctx && ctx->cur ? *ctx->cur : '\0';
}
char istrPeekNext(instream_t *ctx) {
if (!ctx || !ctx->cur) return '\0';
usize offset = (ctx->cur - ctx->start) + 1;
return offset > ctx->size ? '\0' : *(ctx->cur + 1);
}
char istrPrev(instream_t *ctx) {
if (!ctx || ctx->cur == ctx->start) return '\0';
return *(ctx->cur - 1);
}
char istrPrevPrev(instream_t *ctx) {
if (!ctx || (ctx->cur - 1) == ctx->start) return '\0';
return *(ctx->cur - 2);
}
void istrSkip(instream_t *ctx, usize n) {
if (!ctx || !ctx->cur) return;
usize remaining = ctx->size - (ctx->cur - ctx->start);
if(n > remaining) {
return;
}
ctx->cur += n;
}
void istrSkipWhitespace(instream_t *ctx) {
if (!ctx || !ctx->cur) return;
while (*ctx->cur && isspace(*ctx->cur)) {
++ctx->cur;
}
}
void istrRead(instream_t *ctx, char *buf, usize len) {
if (!ctx || !ctx->cur) return;
usize remaining = ctx->size - (ctx->cur - ctx->start);
if(len > remaining) {
warn("istrRead: trying to read len %zu from remaining %zu", len, remaining);
return;
}
memcpy(buf, ctx->cur, len);
ctx->cur += len;
}
usize istrReadMax(instream_t *ctx, char *buf, usize len) {
if (!ctx || !ctx->cur) return 0;
usize remaining = ctx->size - (ctx->cur - ctx->start);
len = remaining < len ? remaining : len;
memcpy(buf, ctx->cur, len);
ctx->cur += len;
return len;
}
void istrRewind(instream_t *ctx) {
ctx->cur = ctx->start;
}
void istrRewindN(instream_t *ctx, usize amount) {
if (!ctx || !ctx->cur) return;
usize remaining = ctx->size - (ctx->cur - ctx->start);
if (amount > remaining) amount = remaining;
ctx->cur -= amount;
}
usize istrTell(instream_t ctx) {
return ctx.cur ? ctx.cur - ctx.start : 0;
}
usize istrRemaining(instream_t ctx) {
return ctx.cur ? ctx.size - (ctx.cur - ctx.start) : 0;
}
bool istrIsFinished(instream_t ctx) {
return ctx.cur ? (usize)(ctx.cur - ctx.start) >= ctx.size : true;
}
bool istrGetBool(instream_t *ctx, bool *val) {
if (!ctx || !ctx->cur) return false;
usize remaining = ctx->size - (ctx->cur - ctx->start);
if(strncmp(ctx->cur, "true", remaining) == 0) {
*val = true;
return true;
}
if(strncmp(ctx->cur, "false", remaining) == 0) {
*val = false;
return true;
}
return false;
}
bool istrGetU8(instream_t *ctx, uint8 *val) {
if (!ctx || !ctx->cur) return false;
char *end = NULL;
*val = (uint8) strtoul(ctx->cur, &end, 0);
if(ctx->cur == end) {
warn("istrGetU8: no valid conversion could be performed");
return false;
}
else if(*val == UINT8_MAX) {
warn("istrGetU8: value read is out of the range of representable values");
return false;
}
ctx->cur = end;
return true;
}
bool istrGetU16(instream_t *ctx, uint16 *val) {
if (!ctx || !ctx->cur) return false;
char *end = NULL;
*val = (uint16) strtoul(ctx->cur, &end, 0);
if(ctx->cur == end) {
warn("istrGetU16: no valid conversion could be performed");
return false;
}
else if(*val == UINT16_MAX) {
warn("istrGetU16: value read is out of the range of representable values");
return false;
}
ctx->cur = end;
return true;
}
bool istrGetU32(instream_t *ctx, uint32 *val) {
if (!ctx || !ctx->cur) return false;
char *end = NULL;
*val = (uint32) strtoul(ctx->cur, &end, 0);
if(ctx->cur == end) {
warn("istrGetU32: no valid conversion could be performed");
return false;
}
else if(*val == UINT32_MAX) {
warn("istrGetU32: value read is out of the range of representable values");
return false;
}
ctx->cur = end;
return true;
}
bool istrGetU64(instream_t *ctx, uint64 *val) {
if (!ctx || !ctx->cur) return false;
char *end = NULL;
*val = strtoull(ctx->cur, &end, 0);
if(ctx->cur == end) {
warn("istrGetU64: no valid conversion could be performed");
return false;
}
else if(*val == ULLONG_MAX) {
warn("istrGetU64: value read is out of the range of representable values");
return false;
}
ctx->cur = end;
return true;
}
bool istrGetI8(instream_t *ctx, int8 *val) {
if (!ctx || !ctx->cur) return false;
char *end = NULL;
*val = (int8) strtol(ctx->cur, &end, 0);
if(ctx->cur == end) {
warn("istrGetI8: no valid conversion could be performed");
return false;
}
else if(*val == INT8_MAX || *val == INT8_MIN) {
warn("istrGetI8: value read is out of the range of representable values");
return false;
}
ctx->cur = end;
return true;
}
bool istrGetI16(instream_t *ctx, int16 *val) {
if (!ctx || !ctx->cur) return false;
char *end = NULL;
*val = (int16) strtol(ctx->cur, &end, 0);
if(ctx->cur == end) {
warn("istrGetI16: no valid conversion could be performed");
return false;
}
else if(*val == INT16_MAX || *val == INT16_MIN) {
warn("istrGetI16: value read is out of the range of representable values");
return false;
}
ctx->cur = end;
return true;
}
bool istrGetI32(instream_t *ctx, int32 *val) {
if (!ctx || !ctx->cur) return false;
char *end = NULL;
*val = (int32) strtol(ctx->cur, &end, 0);
if(ctx->cur == end) {
warn("istrGetI32: no valid conversion could be performed");
return false;
}
else if(*val == INT32_MAX || *val == INT32_MIN) {
warn("istrGetI32: value read is out of the range of representable values");
return false;
}
ctx->cur = end;
return true;
}
bool istrGetI64(instream_t *ctx, int64 *val) {
if (!ctx || !ctx->cur) return false;
char *end = NULL;
*val = strtoll(ctx->cur, &end, 0);
if(ctx->cur == end) {
warn("istrGetI64: no valid conversion could be performed");
return false;
}
else if(*val == INT64_MAX || *val == INT64_MIN) {
warn("istrGetI64: value read is out of the range of representable values");
return false;
}
ctx->cur = end;
return true;
}
bool istrGetFloat(instream_t *ctx, float *val) {
if (!ctx || !ctx->cur) return false;
char *end = NULL;
*val = strtof(ctx->cur, &end);
if(ctx->cur == end) {
warn("istrGetFloat: no valid conversion could be performed");
return false;
}
else if(*val == HUGE_VALF || *val == -HUGE_VALF) {
warn("istrGetFloat: value read is out of the range of representable values");
return false;
}
ctx->cur = end;
return true;
}
bool istrGetDouble(instream_t *ctx, double *val) {
if (!ctx || !ctx->cur) 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;
}
str_t istrGetStr(arena_t *arena, instream_t *ctx, char delim) {
if (!ctx || !ctx->cur) return STR_EMPTY;
const char *from = ctx->cur;
istrIgnore(ctx, delim);
// if it didn't actually find it, it just reached the end of the string
if(*ctx->cur != delim) {
return STR_EMPTY;
}
usize len = ctx->cur - from;
str_t out = {
.buf = alloc(arena, char, len + 1),
.len = len
};
memcpy(out.buf, from, len);
return out;
}
usize istrGetBuf(instream_t *ctx, char *buf, usize buflen) {
if (!ctx || !ctx->cur) return 0;
usize remaining = ctx->size - (ctx->cur - ctx->start);
buflen -= 1;
buflen = remaining < buflen ? remaining : buflen;
memcpy(buf, ctx->cur, buflen);
buf[buflen] = '\0';
ctx->cur += buflen;
return buflen;
}
strview_t istrGetView(instream_t *ctx, char delim) {
if (!ctx || !ctx->cur) return STRV_EMPTY;
const char *from = ctx->cur;
istrIgnore(ctx, delim);
usize len = ctx->cur - from;
return strvInitLen(from, len);
}
strview_t istrGetViewEither(instream_t *ctx, strview_t chars) {
if (!ctx || !ctx->cur) return STRV_EMPTY;
const char *from = ctx->cur;
for (; !istrIsFinished(*ctx) && !strvContains(chars, *ctx->cur); ++ctx->cur) {
}
usize len = ctx->cur - from;
return strvInitLen(from, len);
}
strview_t istrGetViewLen(instream_t *ctx, usize len) {
if (!ctx || !ctx->cur) return STRV_EMPTY;
const char *from = ctx->cur;
istrSkip(ctx, len);
usize buflen = ctx->cur - from;
return (strview_t){ from, buflen };
}
strview_t istrGetLine(instream_t *ctx) {
strview_t line = istrGetView(ctx, '\n');
istrSkip(ctx, 1);
if (strvEndsWith(line, '\r')) {
line = strvRemoveSuffix(line, 1);
}
return line;
}
/* == OUTPUT STREAM =========================================== */
static void ostr__remove_null(outstream_t *o) {
usize len = ostrTell(o);
if (len && o->beg[len - 1] == '\0') {
arenaPop(o->arena, 1);
}
}
outstream_t ostrInit(arena_t *arena) {
return (outstream_t){
.beg = (char *)(arena ? arena->current : NULL),
.arena = arena,
};
}
void ostrClear(outstream_t *ctx) {
arenaPop(ctx->arena, ostrTell(ctx));
}
usize ostrTell(outstream_t *ctx) {
return ctx->arena ? (char *)ctx->arena->current - ctx->beg : 0;
}
char ostrBack(outstream_t *ctx) {
usize len = ostrTell(ctx);
return len ? ctx->beg[len - 1] : '\0';
}
str_t ostrAsStr(outstream_t *ctx) {
if (ostrTell(ctx) == 0 || ostrBack(ctx) != '\0') {
ostrPutc(ctx, '\0');
}
str_t out = {
.buf = ctx->beg,
.len = ostrTell(ctx) - 1
};
ctx->beg = NULL;
ctx->arena = NULL;
return out;
}
strview_t ostrAsView(outstream_t *ctx) {
bool is_null_terminated = ostrBack(ctx) == '\0';
return (strview_t){
.buf = ctx->beg,
.len = ostrTell(ctx) - is_null_terminated
};
}
void ostrPrintf(outstream_t *ctx, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
ostrPrintfV(ctx, fmt, args);
va_end(args);
}
void ostrPrintfV(outstream_t *ctx, const char *fmt, va_list args) {
if (!ctx->arena) return;
ostr__remove_null(ctx);
strFmtv(ctx->arena, fmt, args);
// remove null termination
arenaPop(ctx->arena, 1);
}
void ostrPutc(outstream_t *ctx, char c) {
if (!ctx->arena) return;
ostr__remove_null(ctx);
char *newc = alloc(ctx->arena, char);
*newc = c;
}
void ostrPuts(outstream_t *ctx, strview_t v) {
if (strvIsEmpty(v)) return;
ostr__remove_null(ctx);
str(ctx->arena, v);
}
void ostrAppendBool(outstream_t *ctx, bool val) {
ostrPuts(ctx, val ? strv("true") : strv("false"));
}
void ostrAppendUInt(outstream_t *ctx, uint64 val) {
ostrPrintf(ctx, "%I64u", val);
}
void ostrAppendInt(outstream_t *ctx, int64 val) {
ostrPrintf(ctx, "%I64d", val);
}
void ostrAppendNum(outstream_t *ctx, double val) {
ostrPrintf(ctx, "%g", val);
}
/* == OUT BYTE STREAM ========================================= */
obytestream_t obstrInit(arena_t *exclusive_arena) {
return (obytestream_t){
.beg = exclusive_arena ? exclusive_arena->current : NULL,
.arena = exclusive_arena,
};
}
void obstrClear(obytestream_t *ctx) {
if (ctx->arena) {
ctx->arena->current = ctx->beg;
}
}
usize obstrTell(obytestream_t *ctx) {
return ctx->arena ? ctx->arena->current - ctx->beg : 0;
}
buffer_t obstrAsBuf(obytestream_t *ctx) {
return (buffer_t){ .data = ctx->beg, .len = obstrTell(ctx) };
}
void obstrWrite(obytestream_t *ctx, const void *buf, usize buflen) {
uint8 *dst = alloc(ctx->arena, uint8, buflen, ALLOC_NOZERO);
memcpy(dst, buf, buflen);
}
void obstrPuts(obytestream_t *ctx, strview_t str) {
obstrWrite(ctx, str.buf, str.len);
}
void obstrAppendU8(obytestream_t *ctx, uint8 value) {
obstrWrite(ctx, &value, sizeof(value));
}
void obstrAppendU16(obytestream_t *ctx, uint16 value) {
obstrWrite(ctx, &value, sizeof(value));
}
void obstrAppendU32(obytestream_t *ctx, uint32 value) {
obstrWrite(ctx, &value, sizeof(value));
}
void obstrAppendU64(obytestream_t *ctx, uint64 value) {
obstrWrite(ctx, &value, sizeof(value));
}
void obstrAppendI8(obytestream_t *ctx, int8 value) {
obstrWrite(ctx, &value, sizeof(value));
}
void obstrAppendI16(obytestream_t *ctx, int16 value) {
obstrWrite(ctx, &value, sizeof(value));
}
void obstrAppendI32(obytestream_t *ctx, int32 value) {
obstrWrite(ctx, &value, sizeof(value));
}
void obstrAppendI64(obytestream_t *ctx, int64 value) {
obstrWrite(ctx, &value, sizeof(value));
}
/* == IN BYTE STREAM ========================================== */
ibytestream_t ibstrInit(const void *buf, usize len) {
return (ibytestream_t) {
.cur = buf,
.start = buf,
.size = len,
};
}
usize ibstrRemaining(ibytestream_t *ctx) {
return ctx->size - (ctx->cur - ctx->start);
}
usize ibstrTell(ibytestream_t *ctx) {
return ctx->cur ? ctx->cur - ctx->start : 0;
}
usize ibstrRead(ibytestream_t *ctx, void *buf, usize buflen) {
if (!ctx->cur) return 0;
usize remaining = ibstrRemaining(ctx);
if (buflen > remaining) buflen = remaining;
memcpy(buf, ctx->cur, buflen);
ctx->cur += buflen;
return buflen;
}
uint8 ibstrGetU8(ibytestream_t *ctx) {
uint8 value = 0;
usize read = ibstrRead(ctx, &value, sizeof(value));
return read == sizeof(value) ? value : 0;
}
uint16 ibstrGetU16(ibytestream_t *ctx) {
uint16 value = 0;
usize read = ibstrRead(ctx, &value, sizeof(value));
return read == sizeof(value) ? value : 0;
}
uint32 ibstrGetU32(ibytestream_t *ctx) {
uint32 value = 0;
usize read = ibstrRead(ctx, &value, sizeof(value));
return read == sizeof(value) ? value : 0;
}
uint64 ibstrGetU64(ibytestream_t *ctx) {
uint64 value = 0;
usize read = ibstrRead(ctx, &value, sizeof(value));
return read == sizeof(value) ? value : 0;
}
int8 ibstrGetI8(ibytestream_t *ctx) {
int8 value = 0;
usize read = ibstrRead(ctx, &value, sizeof(value));
return read == sizeof(value) ? value : 0;
}
int16 ibstrGetI16(ibytestream_t *ctx) {
int16 value = 0;
usize read = ibstrRead(ctx, &value, sizeof(value));
return read == sizeof(value) ? value : 0;
}
int32 ibstrGetI32(ibytestream_t *ctx) {
int32 value = 0;
usize read = ibstrRead(ctx, &value, sizeof(value));
return read == sizeof(value) ? value : 0;
}
int64 ibstrGetI64(ibytestream_t *ctx) {
int64 value = 0;
usize read = ibstrRead(ctx, &value, sizeof(value));
return read == sizeof(value) ? value : 0;
}
float ibstrGetFloat(ibytestream_t *ctx) {
float value = 0;
usize read = ibstrRead(ctx, &value, sizeof(value));
return read == sizeof(value) ? value : 0;
}
double ibstrGetDouble(ibytestream_t *ctx) {
double value = 0;
usize read = ibstrRead(ctx, &value, sizeof(value));
return read == sizeof(value) ? value : 0;
}
strview_t ibstrGetView(ibytestream_t *ctx, usize lensize) {
uint64 len = 0;
usize read = ibstrRead(ctx, &len, lensize);
if (read != lensize) {
warn("couldn't read %zu bytes, instead read %zu for string", lensize, read);
return STRV_EMPTY;
}
usize remaining = ibstrRemaining(ctx);
if (len > remaining) {
warn("trying to read a string of length %zu, but only %zu bytes remaining", len, remaining);
len = remaining;
}
const char *str = (const char *)ctx->cur;
ctx->cur += len;
return (strview_t){
.buf = str,
.len = len,
};
}
#include "warnings/colla_warn_end.h"

View file

@ -1,160 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdarg.h>
#include "collatypes.h"
#include "str.h"
typedef struct arena_t arena_t;
/* == INPUT STREAM ============================================ */
typedef struct instream_t {
const char *start;
const char *cur;
usize size;
} instream_t;
// initialize with null-terminated string
instream_t istrInit(const char *str);
instream_t istrInitLen(const char *str, usize len);
// get the current character and advance
char istrGet(instream_t *ctx);
// get the current character but don't advance
char istrPeek(instream_t *ctx);
// get the next character but don't advance
char istrPeekNext(instream_t *ctx);
// returns the previous character
char istrPrev(instream_t *ctx);
// returns the character before the previous
char istrPrevPrev(instream_t *ctx);
// ignore characters until the delimiter
void istrIgnore(instream_t *ctx, char delim);
// ignore characters until the delimiter and skip it
void istrIgnoreAndSkip(instream_t *ctx, char delim);
// skip n characters
void istrSkip(instream_t *ctx, usize n);
// skips whitespace (' ', '\\n', '\\t', '\\r')
void istrSkipWhitespace(instream_t *ctx);
// read len bytes into buffer, the buffer will not be null terminated
void istrRead(instream_t *ctx, char *buf, usize len);
// read a maximum of len bytes into buffer, the buffer will not be null terminated
// returns the number of bytes read
usize istrReadMax(instream_t *ctx, char *buf, usize len);
// returns to the beginning of the stream
void istrRewind(instream_t *ctx);
// returns back <amount> characters
void istrRewindN(instream_t *ctx, usize amount);
// returns the number of bytes read from beginning of stream
usize istrTell(instream_t ctx);
// returns the number of bytes left to read in the stream
usize istrRemaining(instream_t ctx);
// return true if the stream doesn't have any new bytes to read
bool istrIsFinished(instream_t ctx);
bool istrGetBool(instream_t *ctx, bool *val);
bool istrGetU8(instream_t *ctx, uint8 *val);
bool istrGetU16(instream_t *ctx, uint16 *val);
bool istrGetU32(instream_t *ctx, uint32 *val);
bool istrGetU64(instream_t *ctx, uint64 *val);
bool istrGetI8(instream_t *ctx, int8 *val);
bool istrGetI16(instream_t *ctx, int16 *val);
bool istrGetI32(instream_t *ctx, int32 *val);
bool istrGetI64(instream_t *ctx, int64 *val);
bool istrGetFloat(instream_t *ctx, float *val);
bool istrGetDouble(instream_t *ctx, double *val);
str_t istrGetStr(arena_t *arena, instream_t *ctx, char delim);
// get a string of maximum size len, the string is not allocated by the function and will be null terminated
usize istrGetBuf(instream_t *ctx, char *buf, usize buflen);
strview_t istrGetView(instream_t *ctx, char delim);
strview_t istrGetViewEither(instream_t *ctx, strview_t chars);
strview_t istrGetViewLen(instream_t *ctx, usize len);
strview_t istrGetLine(instream_t *ctx);
/* == OUTPUT STREAM =========================================== */
typedef struct outstream_t {
char *beg;
arena_t *arena;
} outstream_t;
outstream_t ostrInit(arena_t *exclusive_arena);
void ostrClear(outstream_t *ctx);
usize ostrTell(outstream_t *ctx);
char ostrBack(outstream_t *ctx);
str_t ostrAsStr(outstream_t *ctx);
strview_t ostrAsView(outstream_t *ctx);
void ostrPrintf(outstream_t *ctx, const char *fmt, ...);
void ostrPrintfV(outstream_t *ctx, const char *fmt, va_list args);
void ostrPutc(outstream_t *ctx, char c);
void ostrPuts(outstream_t *ctx, strview_t v);
void ostrAppendBool(outstream_t *ctx, bool val);
void ostrAppendUInt(outstream_t *ctx, uint64 val);
void ostrAppendInt(outstream_t *ctx, int64 val);
void ostrAppendNum(outstream_t *ctx, double val);
/* == OUT BYTE STREAM ========================================= */
typedef struct {
uint8 *beg;
arena_t *arena;
} obytestream_t;
obytestream_t obstrInit(arena_t *exclusive_arena);
void obstrClear(obytestream_t *ctx);
usize obstrTell(obytestream_t *ctx);
buffer_t obstrAsBuf(obytestream_t *ctx);
void obstrWrite(obytestream_t *ctx, const void *buf, usize buflen);
void obstrPuts(obytestream_t *ctx, strview_t str);
void obstrAppendU8(obytestream_t *ctx, uint8 value);
void obstrAppendU16(obytestream_t *ctx, uint16 value);
void obstrAppendU32(obytestream_t *ctx, uint32 value);
void obstrAppendU64(obytestream_t *ctx, uint64 value);
void obstrAppendI8(obytestream_t *ctx, int8 value);
void obstrAppendI16(obytestream_t *ctx, int16 value);
void obstrAppendI32(obytestream_t *ctx, int32 value);
void obstrAppendI64(obytestream_t *ctx, int64 value);
/* == IN BYTE STREAM ========================================== */
typedef struct {
const uint8 *start;
const uint8 *cur;
usize size;
} ibytestream_t;
ibytestream_t ibstrInit(const void *buf, usize len);
usize ibstrRemaining(ibytestream_t *ctx);
usize ibstrTell(ibytestream_t *ctx);
usize ibstrRead(ibytestream_t *ctx, void *buf, usize buflen);
uint8 ibstrGetU8(ibytestream_t *ctx);
uint16 ibstrGetU16(ibytestream_t *ctx);
uint32 ibstrGetU32(ibytestream_t *ctx);
uint64 ibstrGetU64(ibytestream_t *ctx);
int8 ibstrGetI8(ibytestream_t *ctx);
int16 ibstrGetI16(ibytestream_t *ctx);
int32 ibstrGetI32(ibytestream_t *ctx);
int64 ibstrGetI64(ibytestream_t *ctx);
float ibstrGetFloat(ibytestream_t *ctx);
double ibstrGetDouble(ibytestream_t *ctx);
// reads a string, before reads <lensize> bytes for the length (e.g. sizeof(uint32))
// then reads sizeof(char) * strlen
strview_t ibstrGetView(ibytestream_t *ctx, usize lensize);
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -3,6 +3,7 @@
#if COLLA_TCC && COLLA_WIN
#include <Windows.h>
#include <stdlib.h>
//// FILE.H ////////////////////////////////////////////////////////////////////////////////////
@ -20,6 +21,15 @@ static BOOL tcc_GetFileSizeEx(HANDLE hFile, PLARGE_INTEGER lpFileSize) {
//// STR.H /////////////////////////////////////////////////////////////////////////////////////
#define CP_UTF8 65001
#define strtoull _strtoui64
#define strtoll _strtoi64
#define strtof strtod
extern unsigned __int64 __stdcall _strtoui64(const char *strSource, char **endptr, int base);
extern __int64 __stdcall _strtoi64(const char *strSource, char **endptr, int base);
// extern double __cdecl strtod(const char *strSource, char **endptr);
extern int __stdcall WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWCH lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCCH lpDefaultChar, LPBOOL lpUsedDefaultChar);
extern int __stdcall MultiByteToWideChar(UINT CodePage, DWORD dwFlags, LPCCH lpMultiByteStr, int cbMultiByte, LPWSTR lpWideCharStr, int cchWideChar);
@ -264,6 +274,9 @@ extern BOOL __stdcall SleepConditionVariableCS(PCONDITION_VARIABLE ConditionVari
#define INTERNET_DEFAULT_HTTPS_PORT 443
#define INTERNET_SERVICE_HTTP 3
#define INTERNET_FLAG_SECURE 0x00800000
#define HTTP_QUERY_STATUS_CODE 19
#define HTTP_QUERY_RAW_HEADERS_CRLF 22
#define HTTP_QUERY_FLAG_NUMBER 0x20000000
typedef LPVOID HINTERNET;
@ -274,27 +287,35 @@ typedef WORD INTERNET_PORT;
#define InternetConnect InternetConnectW
#define HttpOpenRequest HttpOpenRequestW
#define HttpSendRequest HttpSendRequestW
#define HttpAddRequestHeaders HttpAddRequestHeadersW
#define HttpQueryInfo HttpQueryInfoW
#else
#define InternetOpen InternetOpenA
#define InternetConnect InternetConnectA
#define HttpOpenRequest HttpOpenRequestA
#define HttpSendRequest HttpSendRequestA
#define HttpAddRequestHeaders HttpAddRequestHeadersA
#define HttpQueryInfo HttpQueryInfoA
#endif
extern HINTERNET __stdcall InternetOpenW(LPCWSTR lpszAgent, DWORD dwAccessType, LPCWSTR lpszProxy, LPCWSTR lpszProxyBypass, DWORD dwFlags);
extern HINTERNET __stdcall InternetConnectW(HINTERNET hInternet, LPCWSTR lpszServerName, INTERNET_PORT nServerPort, LPCWSTR lpszUserName, LPCWSTR lpszPassword, DWORD dwService, DWORD dwFlags, DWORD_PTR dwContext);
extern HINTERNET __stdcall HttpOpenRequestW(HINTERNET hConnect, LPCWSTR lpszVerb, LPCWSTR lpszObjectName, LPCWSTR lpszVersion, LPCWSTR lpszReferrer, LPCWSTR *lplpszAcceptTypes, DWORD dwFlags, DWORD_PTR dwContext);
extern BOOL __stdcall HttpSendRequestW(HINTERNET hRequest, LPCWSTR lpszHeaders, DWORD dwHeadersLength, LPVOID lpOptional, DWORD dwOptionalLength);
extern BOOL __stdcall HttpAddRequestHeadersW(HINTERNET hRequest, LPCWSTR lpszHeaders, DWORD dwHeadersLength, DWORD dwModifiers);
extern BOOL __stdcall HttpQueryInfoW(HINTERNET hRequest, DWORD dwInfoLevel, LPVOID lpBuffer, LPDWORD lpdwBufferLength, LPDWORD lpdwIndex);
extern HINTERNET __stdcall InternetOpenA(LPCSTR lpszAgent, DWORD dwAccessType, LPCSTR lpszProxy, LPCSTR lpszProxyBypass, DWORD dwFlags);
extern HINTERNET __stdcall InternetConnectA(HINTERNET hInternet, LPCSTR lpszServerName, INTERNET_PORT nServerPort, LPCSTR lpszUserName, LPCSTR lpszPassword, DWORD dwService, DWORD dwFlags, DWORD_PTR dwContext);
extern HINTERNET __stdcall HttpOpenRequestA(HINTERNET hConnect, LPCSTR lpszVerb, LPCSTR lpszObjectName, LPCSTR lpszVersion, LPCSTR lpszReferrer, LPCSTR *lplpszAcceptTypes, DWORD dwFlags, DWORD_PTR dwContext);
extern BOOL __stdcall HttpSendRequestA(HINTERNET hRequest, LPCSTR lpszHeaders, DWORD dwHeadersLength, LPVOID lpOptional, DWORD dwOptionalLength);
extern BOOL __stdcall HttpAddRequestHeadersA(HINTERNET hRequest, LPCSTR lpszHeaders, DWORD dwHeadersLength, DWORD dwModifiers);
extern BOOL __stdcall HttpQueryInfoA(HINTERNET hRequest, DWORD dwInfoLevel, LPVOID lpBuffer, LPDWORD lpdwBufferLength, LPDWORD lpdwIndex);
extern BOOL __stdcall InternetReadFile(HINTERNET hFile, LPVOID lpBuffer, DWORD dwNumberOfBytesToRead, LPDWORD lpdwNumberOfBytesRead);
extern BOOL __stdcall InternetCloseHandle(HINTERNET hInternet);
#endif
#endif

View file

@ -1,8 +0,0 @@
#!/bin/bash
# cosmocc -Os -mtiny -o docs.com docs.c
# optimised version for x86/64 only, shaves off ~300KB
x86_64-unknown-cosmo-cc -Os -mtiny -o ../docs/docs.com docs.c
rm ../docs/docs.com.dbg

View file

@ -1,465 +0,0 @@
#include "../arena.c"
#include "../file.c"
#include "../format.c"
#include "../ini.c"
#include "../str.c"
#include "../strstream.c"
#include "../tracelog.c"
#include "../vmem.c"
#include "../markdown.c"
#include "../highlight.c"
#include "../dir.c"
#include "../socket.c"
#include "../http.c"
#include "../server.c"
#include "../html.h"
const char *raw_css;
struct {
bool gen;
strview_t gendir;
} options = {0};
typedef struct page_t {
str_t title;
str_t url;
str_t data;
struct page_t *next;
} page_t;
typedef struct {
uint8 arenabuf[KB(5)];
arena_t arena;
hl_ctx_t *hl;
int line;
} cparser_ctx_t;
void md_cparser_init(void *udata) {
cparser_ctx_t *cparser = udata;
cparser->line = 1;
if (cparser->hl) {
return;
}
cparser->arena = arenaMake(ARENA_STATIC, sizeof(cparser->arenabuf), cparser->arenabuf);
cparser->hl = hlInit(&cparser->arena, &(hl_config_t){
.colors = {
[HL_COLOR_NORMAL] = strv("</span>"),
[HL_COLOR_PREPROC] = strv("<span class=\"c-preproc\">"),
[HL_COLOR_TYPES] = strv("<span class=\"c-types\">"),
[HL_COLOR_CUSTOM_TYPES] = strv("<span class=\"c-custom-types\">"),
[HL_COLOR_KEYWORDS] = strv("<span class=\"c-kwrds\">"),
[HL_COLOR_NUMBER] = strv("<span class=\"c-num\">"),
[HL_COLOR_STRING] = strv("<span class=\"c-str\">"),
[HL_COLOR_COMMENT] = strv("<span class=\"c-cmnt\">"),
[HL_COLOR_FUNC] = strv("<span class=\"c-func\">"),
[HL_COLOR_SYMBOL] = strv("<span class=\"c-sym\">"),
[HL_COLOR_MACRO] = strv("<span class=\"c-macro\">"),
},
.flags = HL_FLAG_HTML,
});
}
void md_cparser_callback(strview_t line, outstream_t *out, void *udata) {
cparser_ctx_t *cparser = udata;
arena_t scratch = cparser->arena;
str_t highlighted = hlHighlight(&scratch, cparser->hl, line);
ostrPrintf(out, "<li>%v</li>", highlighted);
}
page_t *get_pages(arena_t *arena, strview_t path, strview_t default_page) {
arena_t scratch = arenaMake(ARENA_VIRTUAL, MB(1));
dir_t *dir = dirOpen(&scratch, path);
dir_entry_t *entry = NULL;
page_t *first = NULL;
page_t *head = NULL;
page_t *tail = NULL;
cparser_ctx_t cparser = {0};
while ((entry = dirNext(&scratch, dir))) {
if (entry->type != DIRTYPE_FILE) {
continue;
}
strview_t name, ext;
fileSplitPath(strv(entry->name), NULL, &name, &ext);
if (!strvEquals(ext, strv(".md"))) {
continue;
}
str_t fullname = strFmt(&scratch, "%v/%v", path, entry->name);
str_t markdown_str = fileReadWholeStr(&scratch, strv(fullname));
str_t md = markdownStr(&scratch, strv(markdown_str), &(md_options_t){
.parsers = (md_parser_t[]){
{
.init = md_cparser_init,
.callback = md_cparser_callback,
.userdata = &cparser,
.lang = strv("c"),
},
},
.parsers_count = 1,
});
page_t *page = alloc(arena, page_t);
page->data = md;
page->url = str(arena, name);
usize line_end = strvFind(strv(markdown_str), '\n', 0);
strview_t line = strvSub(strv(markdown_str), 0, line_end);
strview_t page_title = strvTrim(strvRemovePrefix(line, 1));
page->title = strFmt(arena, "%v", page_title);
if (!first && strvEquals(name, default_page)) {
first = page;
}
else {
if (!head) head = page;
if (tail) tail->next = page;
tail = page;
}
}
if (first) {
first->next = head;
head = first;
}
strview_t css = strv(raw_css);
for_each(page, head) {
str_t html = STR_EMPTY;
htmlBeg(arena, &html);
headBeg();
title(page->title);
style(css);
headEnd();
bodyBeg();
divBeg(.id="main");
divBeg(.class="content");
divBeg(.class="pages-container");
divBeg(.class="pages");
for_each(item, head) {
str_t class = strFmt(&scratch, "page-item%s", item == page ? " page-current" : "");
str_t href = STR_EMPTY;
if (options.gen) {
href = strFmt(&scratch, "%v.html", item->url);
}
else {
href = strFmt(&scratch, "%v", item->url);
}
a(
item->title,
.href = href.buf,
.class = class.buf,
);
}
divEnd();
divEnd();
divBeg(.class="document-container");
divBeg(.class="document");
htmlPuts(page->data);
htmlRaw(<div class="document-padding"></div>);
divEnd();
divEnd();
divEnd();
divEnd();
bodyEnd();
htmlEnd();
page->data = html;
}
arenaCleanup(&scratch);
return head;
}
str_t server_default(arena_t scratch, server_t *server, server_req_t *req, void *userdata) {
strview_t needle = strv(req->page);
if (strvFront(needle) == '/') {
needle = strvRemovePrefix(strv(req->page), 1);
}
page_t *page = userdata;
while (page) {
if (strvEquals(strv(page->url), needle)) {
break;
}
page = page->next;
}
// if the url is invalid, return the default page
if (!page) {
page = userdata;
}
return serverMakeResponse(&scratch, 200, strv("text/html"), strv(page->data));
}
str_t server_quit(arena_t scratch, server_t *server, server_req_t *req, void *userdata) {
serverStop(server);
return STR_EMPTY;
}
int main(int argc, char **argv) {
arena_t arena = arenaMake(ARENA_VIRTUAL, MB(1));
for (int i = 1; i < argc; ++i) {
strview_t arg = strv(argv[i]);
if (strvEquals(arg, strv("-h"))) {
info("usage: %s [-h, -gen <outdir>]", argv[0]);
return 0;
}
else if (strvEquals(arg, strv("-gen"))) {
options.gen = true;
if ((i + 1) < argc) {
options.gendir = strv(argv[++i]);
}
}
}
page_t *pages = get_pages(&arena, strv("."), strv("readme"));
if (!pages) {
err("could not get pages");
return 1;
}
if (options.gen) {
if (strvBack(options.gendir) == '/' || strvBack(options.gendir) == '\\') {
options.gendir.len -= 1;
}
for_each(page, pages) {
arena_t scratch = arena;
str_t fname = strFmt(&scratch, "%v/%v.html", options.gendir, page->url);
if (!fileWriteWhole(strv(fname), page->data.buf, page->data.len)) {
err("couldn't save page %v", fname);
}
else {
info("saved %v", fname);
}
}
return 0;
}
server_t *s = serverSetup(&arena, 8080, true);
serverRouteDefault(&arena, s, server_default, pages);
serverRoute(&arena, s, strv("/quit"), server_quit, NULL);
serverStart(arena, s);
arenaCleanup(&arena);
}
//// HTML GENERATION STUFF ///////////////////////////////
const char *raw_css = ""
"html, body, #main {\n"
" margin: 0;\n"
" width: 100%;\n"
" height: 100%;\n"
" font-family: -apple-system,BlinkMacSystemFont,'Segoe UI','Noto Sans',Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji';\n"
" \n"
" --black: #121212;\n"
" --dark-gray: #212121;\n"
" --gray: #303030;\n"
" --light-gray: #424242;\n"
" --accent: #FFA500;\n"
" /* --accent: #F98334; */\n"
"\n"
" --blue: #45559E;\n"
" --orange: #F98334;\n"
" --yellow: #FABD2F;\n"
" --red: #FB4934;\n"
" --pink: #D3869B;\n"
" --green: #B8BB26;\n"
" --azure: #7FA375;\n"
" --white: #FBF1C7;\n"
" --light-blue: #83A598;\n"
"}\n"
"\n"
"hr {\n"
" color: white;\n"
" opacity: 0.5;\n"
"}\n"
"\n"
"a {\n"
" text-decoration: none;\n"
" font-weight: bold;\n"
" color: var(--red);\n"
"}\n"
"\n"
"a:hover {\n"
" text-decoration: underline;\n"
"}\n"
"\n"
".content {\n"
" width: 100%;\n"
" height: 100%;\n"
" background-color: var(--black);\n"
" display: flex;\n"
" gap: 20px;\n"
" align-items: stretch;\n"
" color: white;\n"
" overflow-x: scroll;\n"
"}\n"
"\n"
".pages-container {\n"
" position: sticky;\n"
" top: 0;\n"
" min-width: 200px;\n"
" background-color: var(--dark-gray);\n"
" border-right: 1px solid var(--light-gray);\n"
" box-shadow: 0 0 20px rgba(0, 0, 0, 1.0);\n"
" overflow-y: scroll;\n"
"}\n"
"\n"
".document-container {\n"
" display: flex;\n"
" justify-content: center;\n"
" width: 100%;\n"
"}\n"
"\n"
".document {\n"
" width: 100%;\n"
" max-width: 700px;\n"
" line-height: 1.5;\n"
"}\n"
"\n"
".document-padding {\n"
" height: 50px;\n"
"}\n"
"\n"
".pages {\n"
" position: relative;\n"
" background-color: var(--dark-gray);\n"
"}\n"
"\n"
".page-item:last-of-type {\n"
" border-bottom: 0;\n"
"}\n"
"\n"
".page-item {\n"
" text-decoration: none;\n"
" display: block;\n"
" color: rgba(255, 255, 255, 0.5);\n"
" padding: 0.2em 0 0.2em 1.5em;\n"
" border-bottom: 1px solid var(--light-gray);\n"
"}\n"
"\n"
".page-item:hover {\n"
" background-color: var(--light-gray);\n"
" cursor: pointer;\n"
" color: white;\n"
" opacity: 1;\n"
"}\n"
"\n"
".page-current {\n"
" color: var(--accent);\n"
" opacity: 1;\n"
"}\n"
"\n"
".page-current:hover {\n"
" color: var(--accent);\n"
"}\n"
"\n"
".page-spacing {\n"
" height: 25px; \n"
"}\n"
"\n"
"code {\n"
" color: var(--accent);\n"
" background-color: var(--dark-gray);\n"
" padding: 0.2em 0.5em 0.2em 0.5em;\n"
" border-radius: 0.5em;\n"
"}\n"
"\n"
"pre {\n"
" margin: 0;\n"
" margin-top: 2em;\n"
" background-color: var(--dark-gray);\n"
" padding: 16px;\n"
" border-radius: 0.3em;\n"
" overflow-x: scroll;\n"
"\n"
" border: 1px solid var(--light-gray);\n"
" box-shadow: 0 0 20px rgba(0, 0, 0, 1.0);\n"
"}\n"
"\n"
"pre > code {\n"
" color: #FBF1C7;\n"
" padding: 0;\n"
" background-color: transparent;\n"
"}\n"
"\n"
"pre ol {\n"
" counter-reset: item;\n"
" padding-left: 0;\n"
"}\n"
"\n"
"pre li {\n"
" display: block;\n"
" margin-left: 0em;\n"
"}\n"
"\n"
"pre li::before {\n"
" display: inline-block;\n"
" content: counter(item);\n"
" counter-increment: item;\n"
" width: 2em;\n"
" padding-right: 1.5em;\n"
" text-align: right;\n"
"}\n"
"\n"
"/* code block colors */\n"
".c-preproc {\n"
" color: #45559E;\n"
"}\n"
"\n"
".c-types {\n"
" color: #F98334;\n"
"}\n"
"\n"
".c-custom-types {\n"
" color: #FABD2F;\n"
"}\n"
"\n"
".c-kwrds {\n"
" color: #FB4934;\n"
"}\n"
"\n"
".c-num {\n"
" color: #D3869B;\n"
"}\n"
"\n"
".c-str {\n"
" color: #B8BB26;\n"
"}\n"
"\n"
".c-cmnt {\n"
" color: #928374;\n"
"}\n"
"\n"
".c-func {\n"
" color: #7FA375;\n"
"}\n"
"\n"
".c-sym {\n"
" color: #FBF1C7;\n"
"}\n"
"\n"
".c-macro {\n"
" color: #83A598;\n"
"}\n"
"";

418
tools/nob.c Normal file
View file

@ -0,0 +1,418 @@
#define COLLA_NO_CONDITION_VARIABLE 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_file_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);
if (!os_run_cmd(scratch, os_make_cmd(strv(vcvars_path), strv("&&"), strv("set"), strv(">"), strv("build\\cache.ini")), 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"));
}
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();
}

View file

@ -1,212 +0,0 @@
#include "tracelog.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "format.h"
#if COLLA_WIN
#if COLLA_MSVC
#pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS.
#endif
#include <windows.h>
#if COLLA_CMT_LIB
#pragma comment(lib, "User32")
#endif
#if COLLA_TCC
#include "tcc/colla_tcc.h"
#endif
//#ifndef TLOG_NO_COLOURS
// #define TLOG_NO_COLOURS
//#endif
#endif
#if COLLA_EMC
#define TLOG_NO_COLOURS
#endif
#if COLLA_EMC
#define COLOUR_BLACK "<span class=\"black\">"
#define COLOUR_RED "<span class=\"red\">"
#define COLOUR_GREEN "<span class=\"green\">"
#define COLOUR_YELLOW "<span class=\"yellow\">"
#define COLOUR_BLUE "<span class=\"blue\">"
#define COLOUR_MAGENTA "<span class=\"magenta\">"
#define COLOUR_CYAN "<span class=\"cyan\">"
#define COLOUR_WHITE "<span class=\"white\">"
#define COLOUR_RESET "</span></b>"
#define COLOUR_BOLD "<b>"
#elif defined(TLOG_NO_COLOURS)
#define COLOUR_BLACK ""
#define COLOUR_RED ""
#define COLOUR_GREEN ""
#define COLOUR_YELLOW ""
#define COLOUR_BLUE ""
#define COLOUR_MAGENTA ""
#define COLOUR_CYAN ""
#define COLOUR_WHITE ""
#define COLOUR_RESET ""
#define COLOUR_BOLD ""
#else
#define COLOUR_BLACK "\033[30m"
#define COLOUR_RED "\033[31m"
#define COLOUR_GREEN "\033[32m"
#define COLOUR_YELLOW "\033[33m"
#define COLOUR_BLUE "\033[22;34m"
#define COLOUR_MAGENTA "\033[35m"
#define COLOUR_CYAN "\033[36m"
#define COLOUR_WHITE "\033[37m"
#define COLOUR_RESET "\033[0m"
#define COLOUR_BOLD "\033[1m"
#endif
static bool tl_use_newline = true;
#if COLLA_WIN
static void setLevelColour(int level) {
WORD attribute = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
switch (level) {
case LogDebug: attribute = FOREGROUND_BLUE; break;
case LogInfo: attribute = FOREGROUND_GREEN; break;
case LogWarning: attribute = FOREGROUND_RED | FOREGROUND_GREEN; break;
case LogError: attribute = FOREGROUND_RED; break;
case LogFatal: attribute = FOREGROUND_RED; break;
}
HANDLE hc = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(hc, attribute);
}
#else
static void setLevelColour(int level) {
switch (level) {
case LogDebug: printf(COLOUR_BLUE); break;
case LogInfo: printf(COLOUR_GREEN); break;
case LogWarning: printf(COLOUR_YELLOW); break;
case LogError: printf(COLOUR_RED); break;
case LogFatal: printf(COLOUR_MAGENTA); break;
default: printf(COLOUR_RESET); break;
}
}
#endif
void traceLog(int level, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
traceLogVaList(level, fmt, args);
va_end(args);
}
#ifdef TLOG_MUTEX
#include "cthreads.h"
static cmutex_t g_mtx = 0;
#endif
void traceLogVaList(int level, const char *fmt, va_list args) {
#ifdef TLOG_MUTEX
if (!g_mtx) g_mtx = mtxInit();
mtxLock(g_mtx);
#endif
const char *beg = "";
switch (level) {
case LogAll: beg = "[ALL" ; break;
case LogTrace: beg = "[TRACE" ; break;
case LogDebug: beg = "[DEBUG" ; break;
case LogInfo: beg = "[INFO" ; break;
case LogWarning: beg = "[WARNING" ; break;
case LogError: beg = "[ERROR" ; break;
case LogFatal: beg = "[FATAL" ; break;
default: break;
}
#if COLLA_WIN
SetConsoleOutputCP(CP_UTF8);
#endif
setLevelColour(level);
printf("%s", beg);
#if GAME_CLIENT
putchar(':');
traceSetColour(COL_CYAN);
printf("CLIENT");
#elif GAME_HOST
putchar(':');
traceSetColour(COL_MAGENTA);
printf("HOST");
#endif
setLevelColour(level);
printf("]: ");
// set back to white
setLevelColour(LogTrace);
fmtPrintv(fmt, args);
if(tl_use_newline) {
#if COLLA_EMC
puts("<br>");
#else
puts("");
#endif
}
#ifndef TLOG_DONT_EXIT_ON_FATAL
if (level == LogFatal) {
#ifndef TLOG_NO_MSGBOX
#if COLLA_WIN
char errbuff[1024];
fmtBufferv(errbuff, sizeof(errbuff), fmt, args);
char captionbuf[] =
#if GAME_HOST
"Fatal Host Error";
#elif GAME_CLIENT
"Fatal Client Error";
#else
"Fatal Error";
#endif
MessageBoxA(
NULL,
errbuff, captionbuf,
MB_ICONERROR | MB_OK
);
#endif
#endif
fflush(stdout);
abort();
}
#endif
#ifdef TLOG_MUTEX
mtxUnlock(g_mtx);
#endif
}
void traceUseNewline(bool newline) {
tl_use_newline = newline;
}
void traceSetColour(colour_e colour) {
#if COLLA_WIN
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), (WORD)colour);
#else
switch (colour) {
case COL_RESET: printf(COLOUR_RESET); break;
case COL_BLACK: printf(COLOUR_BLACK); break;
case COL_BLUE: printf(COLOUR_BLUE); break;
case COL_GREEN: printf(COLOUR_GREEN); break;
case COL_CYAN: printf(COLOUR_CYAN); break;
case COL_RED: printf(COLOUR_RED); break;
case COL_MAGENTA: printf(COLOUR_MAGENTA); break;
case COL_YELLOW: printf(COLOUR_YELLOW); break;
}
#endif
}

View file

@ -1,59 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/* Define any of this to turn on the option
* -> TLOG_NO_COLOURS: print without using colours
* -> TLOG_VS: print to visual studio console, also turns on TLOG_NO_COLOURS
* -> TLOG_DONT_EXIT_ON_FATAL: don't call 'exit(1)' when using LogFatal
* -> TLOG_DISABLE: turn off all logging, useful for release builds
* -> TLOG_MUTEX: use a mutex on every traceLog call
*/
#include "collatypes.h"
#include <stdarg.h>
enum {
LogAll, LogTrace, LogDebug, LogInfo, LogWarning, LogError, LogFatal
};
typedef enum {
COL_RESET = 15,
COL_BLACK = 8,
COL_BLUE = 9,
COL_GREEN = 10,
COL_CYAN = 11,
COL_RED = 12,
COL_MAGENTA = 13,
COL_YELLOW = 14,
COL_WHITE = 15
} colour_e;
void traceLog(int level, const char *fmt, ...);
void traceLogVaList(int level, const char *fmt, va_list args);
void traceUseNewline(bool use_newline);
void traceSetColour(colour_e colour);
#ifdef TLOG_DISABLE
#define tall(...)
#define trace(...)
#define debug(...)
#define info(...)
#define warn(...)
#define err(...)
#define fatal(...)
#else
#define tall(...) traceLog(LogAll, __VA_ARGS__)
#define trace(...) traceLog(LogTrace, __VA_ARGS__)
#define debug(...) traceLog(LogDebug, __VA_ARGS__)
#define info(...) traceLog(LogInfo, __VA_ARGS__)
#define warn(...) traceLog(LogWarning, __VA_ARGS__)
#define err(...) traceLog(LogError, __VA_ARGS__)
#define fatal(...) traceLog(LogFatal, __VA_ARGS__)
#endif
#ifdef __cplusplus
} // extern "C"
#endif

172
utf8.c
View file

@ -1,172 +0,0 @@
#include "utf8.h"
static const uint8 masks[] = {
0x7f, // 0111-1111
0x1f, // 0001-1111
0x0f, // 0000-1111
0x07, // 0000-0111
0x03, // 0000-0011
0x01 // 0000-0001
};
static struct {
uint8 mask;
uint8 result;
int octets;
} sizes[] = {
{ 0x80, 0x00, 1 }, // 1000-0000, 0000-0000
{ 0xE0, 0xC0, 2 }, // 1110-0000, 1100-0000
{ 0xF0, 0xE0, 3 }, // 1111-0000, 1110-0000
{ 0xF8, 0xF0, 4 }, // 1111-1000, 1111-0000
{ 0xFC, 0xF8, 5 }, // 1111-1100, 1111-1000
{ 0xFE, 0xF8, 6 }, // 1111-1110, 1111-1000
{ 0x80, 0x80, -1 }, // 1000-0000, 1000-0000
};
/*
UTF-8 codepoints are encoded using the first bits of the first character
byte 1 | byte 2 | byte 3 | byte 4
0xxx xxxx | | |
110x xxxx | 10xx xxxx | |
1110 xxxx | 10xx xxxx | 10xx xxxx |
1111 0xxx | 10xx xxxx | 10xx xxxx | 10xx xxxx
so when we decode it we first find the size of the codepoint (from 1 to 4)
then we apply the mask to the first byte to get the first character
then we keep shifting the rune left 6 and applying the next byte to the mask
until the codepoint is finished (size is 0)
## EXAMPLE
utf8 string () = 1110-0010 1000-0010 1010-1100
cp = 0000-0000 0000-0000 0000-0000 0000-0000
size = 3
mask = 0x0f -> 0000-1111
cp = *s & mask = 1110-0010 & 0000-1111 = 0000-0000 0000-0000 0000-0000 0000-0010
++s = 1000-0010
--size = 2
cp <<= 6 = 0000-0000 0000-0000 0000-0000 1000-0000
cp |= *s & 0x3f = 1000-0010 & 0011-1111 = 0000-0000 0000-0000 0000-0000 1000-0010
++s = 1010-1100
--size = 1
cp <<= 6 = 0000-0000 0000-0000 0010-0000 1000-0000
cp |= *s & 0x3f = 1010-1100 & 0011-1111 = 0000-0000 0000-0000 0010-0000 1010-1100
++s = ----------
final codepoint = 0010-0000 1010-1100
codepoint = 0010-0000 1010-1100
*/
rune utf8Decode(const char **char_str) {
const uint8 **s = (const uint8 **)char_str;
rune ch = 0;
// if is ascii
if (**s < 128) {
ch = **s;
++*s;
return ch;
}
int size = utf8Size((const char *)*s);
if (size == -1) {
++*s;
return UTF8_INVALID;
}
uint8 mask = masks[size - 1];
ch = **s & mask;
++*s;
while(--size) {
ch <<= 6;
ch |= **s & 0x3f; // 0011-1111
++*s;
}
return ch;
}
/*
to encode a codepoint in a utf8 string we first need to find
the length of the codepoint
then we start from the rightmost byte and loop for each byte of the codepoint
using the length we got before until the first byte (which we skip)
> and (&) with 0x3f so we ignore the first to bits of the codepoint
> or (|) with 0x80 so we make sure that the first two bits are 10
> bitshift the codepoint right 6
finally, we apply the correct length-mask to the first byte
## EXAMPLE
ch = 0010-0000 1010-1100
ch < 0x10000
first = 0xe0 = 1110-0000
len = 3
str[2] = (ch & 0x3f) | 0x80 = 1010-1100 & 0011-1111 | 1000-0000
= 1010-1100
ch >>= 6 = 0010-0000 1010-1100 >> 6 = 1000-0010
str[1] = (ch & 0x3f) | 0x80 = 1000-0010 & 0011-1111 | 1000-000
= 1000-0010
ch >>= 6 = 1000-0010 >> 6 = 0000-0010
str[0] = ch | first_mask = 0000-0010 | 1111-0000
= 1111-0010
str = 1111-0010 1000-0010 1010-1100
utf8 = 1110-0010 1000-0010 1010-1100
*/
usize utf8Encode(char *str, rune codepoint) {
usize len = 0;
uint8 first;
if (codepoint < 0x80) { // 0000-0000 0000-0000 0000-0000 1000-0000
first = 0;
len = 1;
}
else if (codepoint < 0x800) { // 0000-0000 0000-0000 0000-1000 0000-0000
first = 0xc0; // 1100-0000
len = 2;
}
else if (codepoint < 0x10000) { // 0000-0000 0000-0001 0000-0000 0000-0000
first = 0xe0; // 1110-0000
len = 3;
}
else {
first = 0xf0; // 1111-0000
len = 4;
}
for (usize i = len - 1; i > 0; --i) {
// 0x3f -> 0011-1111
// 0x80 -> 1000-0000
str[i] = (codepoint & 0x3f) | 0x80;
codepoint >>= 6;
}
str[0] = (char)(codepoint | first);
return len;
}
int utf8Size(const char *str) {
uint8 c = (uint8)*str;
for(usize i = 0; i < (sizeof(sizes) / sizeof(*sizes)); ++i) {
if ((c & sizes[i].mask) == sizes[i].result) {
return sizes[i].octets;
}
}
return -1;
}
usize utf8CpSize(rune ch) {
if (ch < 0x80) return 1;
else if (ch < 0x800) return 2;
else if (ch < 0x10000) return 3;
return 4;
}

19
utf8.h
View file

@ -1,19 +0,0 @@
#pragma once
#include "collatypes.h"
typedef uint32 rune;
enum {
UTF8_MAX_SIZE = 4,
UTF8_INVALID = 0x80
};
// grabs the next UTF-8 codepoint and advances string ptr
rune utf8Decode(const char **str);
// encodes a codepoint as UTF-8 and returns the length
usize utf8Encode(char *str, rune ch);
// returns the size of the next UTF-8 codepoint
int utf8Size(const char *str);
// returns the size of a UTF-8 codepoint
usize utf8CpSize(rune ch);

71
vec.h
View file

@ -1,71 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#define vec(T) T *
#define vecFree(vec) ((vec) ? free(_vecheader(vec)), NULL : NULL)
#define vecCopy(src, dest) (vecFree(dest), vecAdd(dest, vecCount(src)), memcpy(dest, src, vecCount(src)))
#define vecAppend(vec, ...) (_vecmaygrow(vec, 1), (vec)[_veclen(vec)] = (__VA_ARGS__), _veclen(vec)++)
#define vecRemove(vec, ind) ((vec) ? (vec)[(ind)] = (vec)[--_veclen(vec)], NULL : 0)
#define vecRemoveIt(vec, it) (vecRemove((vec), (it) - (vec)))
#define vecLen(vec) ((vec) ? _veclen(vec) : 0)
#define vecCap(vec) ((vec) ? _veccap(vec) : 0)
#define vecBeg(vec) (vec)
#define vecEnd(vec) ((vec) ? (vec) + _veclen(vec) : NULL)
#define vecBack(vec) ((vec)[_veclen(vec) - 1])
#define vecAdd(vec, n) (_vecmaygrow(vec, (n)), _veclen(vec) += (size_type)(n), &(vec)[_veclen(vec)-(n)])
#define vecReserve(vec, n) (_vecmaygrow(vec, (n)))
#define vecShrink(vec) (_vecshrink((void **)&(vec), _veclen(vec), sizeof(*(vec))))
#define vecClear(vec) ((vec) ? _veclen(vec) = 0 : 0)
#define vecPop(vec) ((vec)[--_veclen(vec)])
// == IMPLEMENTATION ==========================================================================================
#include <stdlib.h>
#include <assert.h>
#include <stdint.h>
#ifndef size_type
#define size_type uint32_t
#endif
#define _vecheader(vec) ((size_type *)(vec) - 2)
#define _veccap(vec) _vecheader(vec)[0]
#define _veclen(vec) _vecheader(vec)[1]
#define _vecneedgrow(vec, n) ((vec) == NULL || _veclen(vec) + n >= _veccap(vec))
#define _vecmaygrow(vec, n) (_vecneedgrow(vec, (n)) ? _vecgrow(vec, (size_type)(n)) : (void)0)
#define _vecgrow(vec, n) _vecgrowimpl((void **)&(vec), (n), sizeof(*(vec)))
inline static void _vecgrowimpl(void **arr, size_type increment, size_type itemsize) {
int newcap = *arr ? 2 * _veccap(*arr) + increment : increment + 1;
void *ptr = realloc(*arr ? _vecheader(*arr) : 0, itemsize * newcap + sizeof(size_type) * 2);
assert(ptr);
if (ptr) {
if (!*arr) ((size_type *)ptr)[1] = 0;
*arr = (void *) ((size_type *)ptr + 2);
_veccap(*arr) = newcap;
}
}
inline static void _vecshrink(void **arr, size_type newcap, size_t itemsize) {
if (newcap == _veccap(*arr) || !*arr) return;
void *ptr = realloc(_vecheader(*arr), itemsize * newcap + sizeof(size_type) * 2);
assert(ptr);
if (ptr) {
*arr = (void *) ((size_type *)ptr + 2);
if (_veclen(*arr) < newcap) _veclen(*arr) = newcap;
_veccap(*arr) = newcap;
}
}
#ifdef __cplusplus
} // extern "C"
#endif

635
vfs.c
View file

@ -1,635 +0,0 @@
#include "vfs.h"
#include "lz4/lz4.c"
#include "file.h"
#include "strstream.h"
#include "arena.h"
#include "dir.h"
#include "bits.h"
/*
vfs format:
=====================
header:
-----------
magic: VFS
u32 header_size
u32 file_count
u8 flags
for each file:
u64 hash // todo remove
u8 namelen
char *name
u64 offset
u64 size
u64 compressed_size
-----------
* binary data *
*/
typedef struct vfsfile_t vfsfile_t;
struct vfsfile_t {
vfsfile_t *next;
str_t path;
uint64 size;
uint64 comp_size;
};
typedef struct vfshmap_t vfshmap_t;
typedef struct vfshnode_t vfshnode_t;
typedef struct {
uint64 offset;
uint64 size;
uint64 compsize;
} vfsdata_t;
struct vfshnode_t {
vfshnode_t *next;
uint64 hash;
str_t key;
uint32 index;
};
struct vfshmap_t {
vfshnode_t **buckets;
vfsdata_t *values;
uint32 size;
uint32 count;
uint32 collisions;
uint32 max_values;
};
struct virtualfs_t {
file_t fp;
buffer_t buffer;
vfshmap_t hmap;
uint64 base_offset;
vfsflags_e flags;
};
vfsflags_e vfs_validate_flags(vfsflags_e flags);
bool vfs_read(arena_t *arena, virtualfs_t *vfs, vfsdata_t *data, buffer_t *out, bool null_terminate);
vfsfile_t *vfs_add_dir(arena_t *arena, strview_t path, vfsfile_t *tail, uint32 *count, uint64 *bytesize);
vfshmap_t vfs_hmap_init(arena_t *arena, int pow2, uint32 max_values);
void vfs_hmap_add(arena_t *arena, vfshmap_t *hmap, strview_t key, vfsdata_t value);
vfsdata_t *vfs_hmap_get(vfshmap_t *hmap, strview_t key);
uint64 sdbm_hash(const void *bytes, usize count);
uint64 djb2_hash(const void *bytes, usize count);
bool vfsVirtualiseDir(arena_t scratch, strview_t dirpath, strview_t outfile, vfsflags_e flags) {
bool success = false;
flags = vfs_validate_flags(flags);
if (strvBack(dirpath) != '/') {
str_t newpath = strFmt(&scratch, "%v/", dirpath);
dirpath = strv(newpath);
}
uint32 count = 0;
uint64 bytesize = 0;
vfsfile_t file_head = {0};
vfs_add_dir(&scratch, dirpath, &file_head, &count, &bytesize);
vfsfile_t *files = file_head.next;
arena_t comp_arena = {0};
if (flags & VFS_FLAGS_COMPRESSED) {
arena_t comp_arena = arenaMake(ARENA_VIRTUAL, GB(1));
for_each (file, files) {
arena_t tmp = scratch;
buffer_t buf = fileReadWhole(&tmp, strv(file->path));
usize maxlen = LZ4_compressBound(buf.len);
uint8 *compressed = alloc(&comp_arena, uint8, maxlen);
int actual_len = LZ4_compress_default(buf.data, compressed, buf.len, maxlen);
assert(actual_len > 0 && actual_len <= maxlen);
usize pop = maxlen - (usize)actual_len;
// pop extra bytes that were allocated but not used
arenaPop(&comp_arena, pop);
file->comp_size = actual_len;
}
}
obytestream_t header = obstrInit(&scratch);
obstrAppendU32(&header, count);
obstrAppendU8(&header, flags);
uint64 offset = 0;
for_each (file, files) {
assert(file->path.len < 256);
uint64 hash = djb2_hash(file->path.buf, file->path.len);
obstrAppendU64(&header, hash);
obstrAppendU8(&header, file->path.len);
obstrPuts(&header, strv(file->path));
obstrAppendU64(&header, offset);
obstrAppendU64(&header, file->size);
obstrAppendU64(&header, file->comp_size);
offset += file->comp_size;
}
buffer_t headerbuf = obstrAsBuf(&header);
buffer_t binbuf = {0};
file_t fp = fileOpen(outfile, FILE_WRITE);
if (!fileIsValid(fp)) {
err("could not open file %v", outfile);
goto failed;
}
uint32 header_size = headerbuf.len + 3 + sizeof(uint32); // + strlen("VFS") + sizeof(header_size)
filePuts(fp, strv("VFS"));
fileWrite(fp, &header_size, sizeof(header_size));
fileWrite(fp, headerbuf.data, headerbuf.len);
if (flags & VFS_FLAGS_COMPRESSED) {
buffer_t compressed = {
.data = comp_arena.start,
.len = arenaTell(&comp_arena)
};
fileWrite(fp, compressed.data, compressed.len);
}
else {
for_each (file, files) {
arena_t tmp = scratch;
buffer_t bin = fileReadWhole(&tmp, strv(file->path));
if (flags & VFS_FLAGS_NULL_TERMINATE_FILES) {
alloc(&tmp, uint8);
bin.len += 1;
}
fileWrite(fp, bin.data, bin.len);
}
}
fileClose(fp);
success = true;
failed:
arenaCleanup(&comp_arena);
arenaCleanup(&scratch);
return success;
}
virtualfs_t *vfsReadFromFile(arena_t *arena, strview_t vfs_file, vfsflags_e flags) {
usize pos_before = arenaTell(arena);
virtualfs_t *vfs = alloc(arena, virtualfs_t);
file_t fp = fileOpen(vfs_file, FILE_READ);
if (!fileIsValid(fp)) {
goto failed;
}
// read header
struct {
char magic[3];
uint32 size;
uint32 file_count;
uint8 flags;
} header = {0};
fileRead(fp, &header.magic, sizeof(header.magic));
fileRead(fp, &header.size, sizeof(header.size));
fileRead(fp, &header.file_count, sizeof(header.file_count));
fileRead(fp, &header.flags, sizeof(header.flags));
if (memcmp(header.magic, "VFS", 3) != 0) {
err("VirtualFS: magic characters are wrong: %.3s (0x%x%x%x)", header.magic, header.magic[0], header.magic[1], header.magic[2]);
goto failed;
}
uint32 default_pow2 = 1 << 10;
uint32 pow2 = bitsNextPow2(header.file_count);
pow2 = bitsCtz(max(pow2, default_pow2));
vfs->hmap = vfs_hmap_init(arena, pow2, header.file_count);
for (uint32 i = 0; i < header.file_count; ++i) {
struct {
uint64 hash;
char name[256];
uint64 offset;
uint64 size;
uint64 comp;
} file = {0};
uint8 namelen = 0;
fileRead(fp, &file.hash, sizeof(file.hash));
fileRead(fp, &namelen, sizeof(namelen));
fileRead(fp, &file.name, namelen);
fileRead(fp, &file.offset, sizeof(file.offset));
fileRead(fp, &file.size, sizeof(file.size));
fileRead(fp, &file.comp, sizeof(file.comp));
vfsdata_t data = {
.offset = file.offset,
.size = file.size,
.compsize = file.comp,
};
strview_t path = strvInitLen(file.name, namelen);
vfs_hmap_add(arena, &vfs->hmap, path, data);
}
vfs->flags = vfs_validate_flags(header.flags | flags);
vfs->base_offset = header.size;
if (vfs->flags & VFS_FLAGS_DONT_STREAM) {
// get remaining size of the file
usize pos = fileTell(fp);
fileSeekEnd(fp);
usize endpos = fileTell(fp);
fileSeek(fp, pos);
usize binsize = endpos - pos;
// read binary data and save it to buffer for later
buffer_t buf = {
.data = alloc(arena, uint8, binsize),
.len = binsize,
};
usize read = fileRead(fp, buf.data, buf.len);
if (read != buf.len) {
err("couldn't read all of the binary data, expected %zu bytes but got %zu", buf.len, read);
goto failed;
}
fileClose(fp);
vfs->buffer = buf;
}
else {
vfs->fp = fp;
}
return vfs;
failed:
fileClose(fp);
arenaRewind(arena, pos_before);
return NULL;
}
buffer_t vfsRead(arena_t *arena, virtualfs_t *vfs, strview_t path) {
buffer_t out = {0};
usize pos_before = arenaTell(arena);
if (!vfs) {
goto failed;
}
vfsdata_t *data = vfs_hmap_get(&vfs->hmap, path);
if (!data) {
goto failed;
}
if (!vfs_read(arena, vfs, data, &out, false)) {
goto failed;
}
return out;
failed:
arenaRewind(arena, pos_before);
return (buffer_t){0};
}
str_t vfsReadStr(arena_t *arena, virtualfs_t *vfs, strview_t path) {
buffer_t buf = {0};
usize pos_before = arenaTell(arena);
if (!vfs) {
goto failed;
}
vfsdata_t *data = vfs_hmap_get(&vfs->hmap, path);
if (!data) {
goto failed;
}
if (!vfs_read(arena, vfs, data, &buf, true)) {
goto failed;
}
return (str_t){
.buf = buf.data,
.len = buf.len,
};
failed:
arenaRewind(arena, pos_before);
return STR_EMPTY;
}
// == VFS FILE API ===================================
virtualfs_t *g_vfs = NULL;
void vfsSetGlobalVirtualFS(virtualfs_t *vfs) {
g_vfs = vfs;
}
bool vfsFileExists(strview_t path) {
if (!g_vfs) return false;
return vfs_hmap_get(&g_vfs->hmap, path) != NULL;
}
vfs_file_t vfsFileOpen(strview_t name, int mode) {
if (!g_vfs) goto failed;
if (mode != FILE_READ) {
err("VirtualFS: trying to open file (%v) for write, VirtualFS is read only!", name);
goto failed;
}
vfsdata_t *data = vfs_hmap_get(&g_vfs->hmap, name);
return (vfs_file_t){
.handle = (uintptr_t)data,
};
failed:
return (vfs_file_t){0};
}
void vfsFileClose(vfs_file_t ctx) {
(void)ctx;
}
bool vfsFileIsValid(vfs_file_t ctx) {
return g_vfs && ctx.handle != 0;
}
usize vfsFileSize(vfs_file_t ctx) {
if (!vfsFileIsValid(ctx)) return 0;
vfsdata_t *data = (vfsdata_t *)ctx.handle;
return data->size;
}
buffer_t vfsFileReadWhole(arena_t *arena, strview_t name) {
return vfsRead(arena, g_vfs, name);
}
buffer_t vfsFileReadWholeFP(arena_t *arena, vfs_file_t ctx) {
if (!vfsFileIsValid(ctx)) return (buffer_t){0};
vfsdata_t *data = (vfsdata_t *)ctx.handle;
buffer_t out = {0};
usize pos_before = arenaTell(arena);
if (!vfs_read(arena, g_vfs, data, &out, false)) {
arenaRewind(arena, pos_before);
return (buffer_t){0};
}
return out;
}
str_t vfsFileReadWholeStr(arena_t *arena, strview_t name) {
return vfsReadStr(arena, g_vfs, name);
}
str_t vfsFileReadWholeStrFP(arena_t *arena, vfs_file_t ctx) {
if (!vfsFileIsValid(ctx)) return STR_EMPTY;
vfsdata_t *data = (vfsdata_t *)ctx.handle;
buffer_t buf = {0};
usize pos_before = arenaTell(arena);
if (!vfs_read(arena, g_vfs, data, &buf, true)) {
arenaRewind(arena, pos_before);
return STR_EMPTY;
}
return (str_t){
.buf = buf.data,
.len = buf.len,
};
}
// == PRIVATE FUNCTIONS ==============================
vfsflags_e vfs_validate_flags(vfsflags_e flags) {
if (flags & VFS_FLAGS_COMPRESSED && flags & VFS_FLAGS_NULL_TERMINATE_FILES) {
warn("VirtualFS: both COMPRESSEd and NULL_TERMINATE_FILES flags are set to ON, but they are mutually exclusive. turning NULL_TERMINATE_FILES off");
flags &= ~VFS_FLAGS_NULL_TERMINATE_FILES;
}
return flags;
}
bool vfs_read(arena_t *arena, virtualfs_t *vfs, vfsdata_t *data, buffer_t *out, bool null_terminate) {
if (!vfs || !data || !out) {
return false;
}
bool is_allocated = true;
out->len = data->size;
if (vfs->flags & VFS_FLAGS_COMPRESSED) {
out->data = alloc(arena, uint8, out->len);
uint8 *compressed = NULL;
if (vfs->flags & VFS_FLAGS_DONT_STREAM) {
assert((data->offset + data->compsize) < vfs->buffer.len);
compressed = vfs->buffer.data + data->offset;
}
else {
uint64 offset = vfs->base_offset + data->offset;
fileSeek(vfs->fp, offset);
arena_t scratch = *arena;
uint8 *compressed = alloc(&scratch, uint8, data->compsize);
usize read = fileRead(vfs->fp, compressed, data->compsize);
if (read != data->compsize) {
err("VirtualFS: read %zu bytes, but should have read %zu", read, data->compsize);
return false;
}
}
int decompsize = LZ4_decompress_safe(compressed, out->data, data->compsize, out->len);
if (decompsize < 0) {
err("VirtualFS: couldn't decompress buffer: %d", decompsize);
return false;
}
}
else {
if (vfs->flags & VFS_FLAGS_DONT_STREAM) {
assert((data->offset + data->size) < vfs->buffer.len);
out->data = vfs->buffer.data + data->offset;
is_allocated = false;
}
else {
out->data = alloc(arena, uint8, data->size);
uint64 offset = vfs->base_offset + data->offset;
fileSeek(vfs->fp, offset);
usize read = fileRead(vfs->fp, out->data, out->len);
if (read != out->len) {
err("VirtualFS: read %zu bytes, but should have read %zu", read, out->len);
return false;
}
}
}
if (null_terminate && !(vfs->flags & VFS_FLAGS_NULL_TERMINATE_FILES)) {
if (is_allocated) {
alloc(arena, char);
}
else {
uint8 *buf = alloc(arena, uint8, out->len + 1);
memcpy(buf, out->data, out->len);
out->data = buf;
}
out->len += 1;
}
return true;
}
vfsfile_t *vfs_add_dir(arena_t *arena, strview_t path, vfsfile_t *tail, uint32 *count, uint64 *bytesize) {
uint8 tmpbuf[KB(1)];
dir_t *dir = dirOpen(arena, path);
dir_entry_t *entry = NULL;
if (strvEquals(path, strv("./"))) {
path = STRV_EMPTY;
}
vfsfile_t *head = tail;
vfsfile_t *cur = tail;
while ((entry = dirNext(arena, dir))) {
arena_t scratch = arenaMake(ARENA_STATIC, sizeof(tmpbuf), tmpbuf);
vfsfile_t *newfile = NULL;
if (entry->type == DIRTYPE_DIR) {
if (strvEquals(strv(entry->name), strv(".")) ||
strvEquals(strv(entry->name), strv(".."))
) {
continue;
}
str_t fullpath = strFmt(&scratch, "%v%v/", path, entry->name);
newfile = vfs_add_dir(arena, strv(fullpath), cur, count, bytesize);
}
else {
newfile = alloc(arena, vfsfile_t);
newfile->path = strFmt(arena, "%v%v", path, entry->name);
newfile->size = entry->filesize;
newfile->comp_size = newfile->size;
if (cur) cur->next = newfile;
(*count)++;
(*bytesize) += newfile->size;
}
if (!head) head = newfile;
cur = newfile;
}
return cur;
}
// == HASH MAP =======================================
vfshmap_t vfs_hmap_init(arena_t *arena, int pow2, uint32 max_values) {
uint size = 1 << pow2;
return (vfshmap_t) {
.size = size,
.max_values = max_values,
.buckets = alloc(arena, vfshnode_t*, size),
.values = alloc(arena, vfsdata_t, max_values),
};
}
void vfs_hmap_add(arena_t *arena, vfshmap_t *hmap, strview_t key, vfsdata_t value) {
if (!hmap) return;
if ((float)hmap->count >= (float)hmap->size * 0.6f) {
warn("more than 60%% of the hashmap is being used: %d/%d", hmap->count, hmap->size);
}
uint64 hash = djb2_hash(key.buf, key.len);
usize index = hash & (hmap->size - 1);
vfshnode_t *bucket = hmap->buckets[index];
if (bucket) hmap->collisions++;
while (bucket) {
// already exists
if (bucket->hash == hash && strvEquals(strv(bucket->key), key)) {
hmap->values[bucket->index] = value;
return;
}
bucket = bucket->next;
}
assert(hmap->count < hmap->max_values);
bucket = alloc(arena, vfshnode_t);
bucket->hash = hash;
bucket->key = str(arena, key);
bucket->index = hmap->count;
bucket->next = hmap->buckets[index];
hmap->values[hmap->count++] = value;
hmap->buckets[index] = bucket;
}
vfsdata_t *vfs_hmap_get(vfshmap_t *hmap, strview_t key) {
if (!hmap || hmap->count == 0) {
return NULL;
}
uint64 hash = djb2_hash(key.buf, key.len);
usize index = hash & (hmap->size - 1);
vfshnode_t *bucket = hmap->buckets[index];
while (bucket) {
if (bucket->hash == hash && strvEquals(strv(bucket->key), key)) {
return &hmap->values[bucket->index];
}
bucket = bucket->next;
}
return NULL;
}
uint64 sdbm_hash(const void *bytes, usize count) {
const uint8 *data = bytes;
uint64 hash = 0;
for (usize i = 0; i < count; ++i) {
hash = data[i] + (hash << 6) + (hash << 16) - hash;
}
return hash;
}
uint64 djb2_hash(const void *bytes, usize count) {
const uint8 *data = bytes;
uint64 hash = 5381;
int c;
for (usize i = 0; i < count; ++i) {
hash = ((hash << 5) + hash) + data[i];
}
return hash;
}
uint64 java_hash(const void *bytes, usize count) {
const uint8 *data = bytes;
uint64 hash = 1125899906842597L;
for (usize i = 0; i < count; ++i) {
hash = ((hash << 5) - hash) + data[i];
}
return hash;
}

36
vfs.h
View file

@ -1,36 +0,0 @@
#pragma once
#include "str.h"
#include "arena.h"
typedef enum {
VFS_FLAGS_NONE = 0,
VFS_FLAGS_COMPRESSED = 1 << 0,
VFS_FLAGS_NULL_TERMINATE_FILES = 1 << 1, // only valid when compress is false
VFS_FLAGS_DONT_STREAM = 1 << 2,
} vfsflags_e;
typedef struct virtualfs_t virtualfs_t;
bool vfsVirtualiseDir(arena_t scratch, strview_t dirpath, strview_t outfile, vfsflags_e flags);
virtualfs_t *vfsReadFromFile(arena_t *arena, strview_t vfs_file, vfsflags_e flags);
buffer_t vfsRead(arena_t *arena, virtualfs_t *vfs, strview_t path);
str_t vfsReadStr(arena_t *arena, virtualfs_t *vfs, strview_t path);
// vfs replacement for the file api
typedef struct {
uintptr_t handle;
} vfs_file_t;
void vfsSetGlobalVirtualFS(virtualfs_t *vfs);
bool vfsFileExists(strview_t path);
vfs_file_t vfsFileOpen(strview_t name, int mode);
void vfsFileClose(vfs_file_t ctx);
bool vfsFileIsValid(vfs_file_t ctx);
usize vfsFileSize(vfs_file_t ctx);
buffer_t vfsFileReadWhole(arena_t *arena, strview_t name);
buffer_t vfsFileReadWholeFP(arena_t *arena, vfs_file_t ctx);
str_t vfsFileReadWholeStr(arena_t *arena, strview_t name);
str_t vfsFileReadWholeStrFP(arena_t *arena, vfs_file_t ctx);

View file

@ -1,34 +0,0 @@
#pragma once
#include "vfs.h"
#include "tracelog.h"
#define fileExists vfsFileExists
#define fileOpen vfsFileOpen
#define fileClose vfsFileClose
#define fileIsValid vfsFileIsValid
#define fileSize vfsFileSize
#define fileReadWhole vfsFileReadWhole
#define fileReadWholeFP vfsFileReadWholeFP
#define fileReadWholeStr vfsFileReadWholeStr
#define fileReadWholeStrFP vfsFileReadWholeStrFP
#define VFS_FAIL_READONLY(fn) err("VirtualFS: trying to call function " #fn "which requires writing to files. VirtualFS is read only!")
#define VFS_FAIL_STATELESS(fn) err("VirtualFS: trying to call function " #fn "which requires state. VirtualFS only works on stateless file's commands!")
#define fileDelete(...) VFS_FAIL_READONLY(fileDelete)
#define filePutc(...) VFS_FAIL_READONLY(filePutc)
#define filePuts(...) VFS_FAIL_READONLY(filePuts)
#define filePrintf(...) VFS_FAIL_READONLY(filePrintf)
#define filePrintfv(...) VFS_FAIL_READONLY(filePrintfv)
#define fileWrite(...) VFS_FAIL_READONLY(fileWrite)
#define fileWriteWhole(...) VFS_FAIL_READONLY(fileWriteWhole)
#define fileGetTime(...) VFS_FAIL_READONLY(fileGetTime)
#define fileGetTimeFP(...) VFS_FAIL_READONLY(fileGetTimeFP)
#define fileHasChanged(...) VFS_FAIL_READONLY(fileHasChanged)
#define fileTell(...) VFS_FAIL_STATELESS(fileTell)
#define fileRead(...) VFS_FAIL_STATELESS(fileRead)
#define fileSeek(...) VFS_FAIL_STATELESS(fileSeek)
#define fileSeekEnd(...) VFS_FAIL_STATELESS(fileSeekEnd)
#define fileRewind(...) VFS_FAIL_STATELESS(fileRewind)

133
vmem.c
View file

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

12
vmem.h
View file

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

View file

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

View file

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

View file

@ -1,142 +0,0 @@
#include "websocket.h"
#include "arena.h"
#include "str.h"
#include "server.h"
#include "socket.h"
#include "base64.h"
#include "strstream.h"
#include "sha1.h"
#if !COLLA_MSVC
extern uint16_t ntohs(uint16_t hostshort);
extern uint16_t htons(uint16_t hostshort);
extern uint32_t htonl(uint32_t hostlong);
uint64_t ntohll(uint64_t input) {
uint64_t rval = 0;
uint8_t *data = (uint8_t *)&rval;
data[0] = input >> 56;
data[1] = input >> 48;
data[2] = input >> 40;
data[3] = input >> 32;
data[4] = input >> 24;
data[5] = input >> 16;
data[6] = input >> 8;
data[7] = input >> 0;
return rval;
}
uint64_t htonll(uint64_t input) {
return ntohll(input);
}
#endif
bool wsInitialiseSocket(arena_t scratch, socket_t websocket, strview_t key) {
str_t full_key = strFmt(&scratch, "%v" WEBSOCKET_MAGIC, key, WEBSOCKET_MAGIC);
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 = base64Encode(&scratch, (buffer_t){ (uint8 *)sha1_data.digest, sizeof(sha1_data.digest) });
str_t response = strFmt(
&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
);
bool success = skSend(websocket, response.buf, response.len);
return success;
}
buffer_t wsEncodeMessage(arena_t *arena, strview_t message) {
int extra = 6;
if (message.len > UINT16_MAX) extra += sizeof(uint64);
else if (message.len > UINT8_MAX) extra += sizeof(uint16);
uint8 *bytes = alloc(arena, uint8, message.len + extra);
bytes[0] = 0b10000001;
bytes[1] = 0b10000000;
int offset = 2;
if (message.len > UINT16_MAX) {
bytes[1] |= 0b01111111;
uint64 len = htonll(message.len);
memcpy(bytes + 2, &len, sizeof(len));
offset += sizeof(uint64);
}
else if (message.len > UINT8_MAX) {
bytes[1] |= 0b01111110;
uint16 len = htons((uint16)message.len);
memcpy(bytes + 2, &len, sizeof(len));
offset += sizeof(uint16);
}
else {
bytes[1] |= (uint8)message.len;
}
uint32 mask = 0;
memcpy(bytes + offset, &mask, sizeof(mask));
offset += sizeof(mask);
memcpy(bytes + offset, message.buf, message.len);
return (buffer_t){ bytes, message.len + extra };
}
str_t wsDecodeMessage(arena_t *arena, buffer_t message) {
str_t out = STR_EMPTY;
uint8 *bytes = message.data;
bool mask = bytes[1] & 0b10000000;
int offset = 2;
uint64 msglen = bytes[1] & 0b01111111;
// 16bit msg len
if (msglen == 126) {
uint64 be_len = 0;
memcpy(&be_len, bytes + 2, sizeof(be_len));
msglen = ntohs(be_len);
offset += sizeof(uint16);
}
// 64bit msg len
else if (msglen == 127) {
uint64 be_len = 0;
memcpy(&be_len, bytes + 2, sizeof(be_len));
msglen = ntohll(be_len);
offset += sizeof(uint64);
}
if (msglen == 0) {
warn("message length = 0");
}
else if (mask) {
uint8 *decoded = alloc(arena, uint8, msglen + 1);
uint8 masks[4] = {0};
memcpy(masks, bytes + offset, sizeof(masks));
offset += 4;
for (uint64 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;
}

View file

@ -1,13 +0,0 @@
#pragma once
#include "arena.h"
#include "str.h"
#define WEBSOCKET_MAGIC "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
#define WEBSOCKET_HTTP_KEY "Sec-WebSocket-Key"
typedef uintptr_t socket_t;
bool wsInitialiseSocket(arena_t scratch, socket_t websocket, strview_t key);
buffer_t wsEncodeMessage(arena_t *arena, strview_t message);
str_t wsDecodeMessage(arena_t *arena, buffer_t message);

337
win/net_win32.c Normal file
View file

@ -0,0 +1,337 @@
#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) {
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) {
err("call to HttpSendRequest failed: %v", os_get_error_string(os_get_last_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;
}
ostr_puts(&body, strv(read_buffer, read));
}
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);
}

693
win/os_win32.c Normal file
View file

@ -0,0 +1,693 @@
#include <windows.h>
#include <assert.h>
#include "../os.h"
#include "../net.h"
#if COLLA_TCC
#include "../tcc/colla_tcc.h"
#endif
typedef enum os_entity_kind_e {
OS_KIND_NULL,
OS_KIND_THREAD,
OS_KIND_MUTEX,
OS_KIND_CONDITION_VARIABLE,
} os_entity_kind_e;
typedef struct os_entity_t os_entity_t;
struct os_entity_t {
os_entity_t *next;
os_entity_kind_e kind;
union {
struct {
HANDLE handle;
thread_func_t *func;
void *userdata;
DWORD id;
} thread;
CRITICAL_SECTION mutex;
CONDITION_VARIABLE cv;
};
};
struct {
arena_t arena;
os_system_info_t info;
os_entity_t *entity_free;
oshandle_t hstdout;
oshandle_t hstdin;
} w32_data = {0};
os_entity_t *os__win_alloc_entity(os_entity_kind_e kind) {
os_entity_t *entity = w32_data.entity_free;
if (entity) {
list_pop(w32_data.entity_free);
}
else {
entity = alloc(&w32_data.arena, os_entity_t);
}
entity->kind = kind;
return entity;
}
void os__win_free_entity(os_entity_t *entity) {
entity->kind = OS_KIND_NULL;
list_push(w32_data.entity_free, entity);
}
void os_init(void) {
SetConsoleOutputCP(CP_UTF8);
SYSTEM_INFO sysinfo = {0};
GetSystemInfo(&sysinfo);
os_system_info_t *info = &w32_data.info;
info->processor_count = (u64)sysinfo.dwNumberOfProcessors;
info->page_size = sysinfo.dwPageSize;
w32_data.arena = arena_make(ARENA_VIRTUAL, OS_ARENA_SIZE);
TCHAR namebuf[MAX_COMPUTERNAME_LENGTH + 1];
DWORD namebuflen = sizeof(namebuf);
BOOL result = GetComputerName(namebuf, &namebuflen);
if (!result) {
err("failed to get computer name: %v", os_get_error_string(os_get_last_error()));
}
info->machine_name = str_from_tstr(&w32_data.arena, (tstr_t){ namebuf, namebuflen});
HANDLE hstdout = CreateFile(TEXT("CONOUT$"), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
HANDLE hstdin = CreateFile(TEXT("CONIN$"), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (hstdout == INVALID_HANDLE_VALUE) err("couldn't open CONOUT$");
else w32_data.hstdout.data = (uptr)hstdout;
if (hstdin == INVALID_HANDLE_VALUE) err("couldn't open CONIN$");
else w32_data.hstdin.data = (uptr)hstdin;
}
void os_cleanup(void) {
os_file_close(w32_data.hstdout);
os_file_close(w32_data.hstdin);
arena_cleanup(&w32_data.arena);
}
void os_abort(int code) {
ExitProcess(code);
}
iptr os_get_last_error(void) {
return (iptr)GetLastError();
}
str_t os_get_error_string(iptr error) {
static u8 tmpbuf[1024] = {0};
arena_t arena = arena_make(ARENA_STATIC, sizeof(tmpbuf), tmpbuf);
DWORD code = LOWORD(error);
WCHAR msgbuf[512];
DWORD chars;
chars = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, code, 0, msgbuf, arrlen(msgbuf), NULL);
if (chars == 0) {
err("FormatMessageW: %ld", os_get_last_error());
return STR_EMPTY;
}
debug("error: %zi 0x%zX", error, error);
// remove \r\n at the end
return str_from_str16(&arena, str16_init(msgbuf, chars - 2));
}
os_wait_t os_wait_on_handles(oshandle_t *handles, int count, bool wait_all, u32 milliseconds) {
HANDLE win_handles[OS_MAX_WAITABLE_HANDLES] = {0};
assert(count < MAXIMUM_WAIT_OBJECTS);
for (int i = 0; i < count; ++i) {
win_handles[i] = (HANDLE)(handles[i].data);
}
DWORD result = WaitForMultipleObjects(count, win_handles, wait_all, milliseconds);
os_wait_t out = {0};
if (result == WAIT_FAILED) {
out.result = OS_WAIT_FAILED;
}
else if (result == WAIT_TIMEOUT) {
out.result = OS_WAIT_TIMEOUT;
}
else if (result >= WAIT_ABANDONED_0) {
out.result = OS_WAIT_ABANDONED;
out.index = result - WAIT_ABANDONED_0;
}
else {
out.result = OS_WAIT_FINISHED;
out.index = result - WAIT_OBJECT_0;
}
return out;
}
os_system_info_t os_get_system_info(void) {
return w32_data.info;
}
void os_log_set_colour(os_log_colour_e colour) {
WORD attribute = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
switch (colour) {
case LOG_COL_BLACK: attribute = 0; break;
case LOG_COL_RED: attribute = FOREGROUND_RED; break;
case LOG_COL_GREEN: attribute = FOREGROUND_GREEN; break;
case LOG_COL_BLUE: attribute = FOREGROUND_BLUE; break;
case LOG_COL_MAGENTA: attribute = FOREGROUND_RED | FOREGROUND_BLUE; break;
case LOG_COL_YELLOW: attribute = FOREGROUND_RED | FOREGROUND_GREEN; break;
case LOG_COL_CYAN: attribute = FOREGROUND_GREEN | FOREGROUND_BLUE; break;
default: break;
}
HANDLE hc = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(hc, attribute | FOREGROUND_INTENSITY);
}
oshandle_t os_stdout(void) {
return w32_data.hstdout;
}
oshandle_t os_stdin(void) {
return w32_data.hstdin;
}
// == FILE ======================================
#define OS_SMALL_SCRATCH() \
u8 tmpbuf[KB(1)]; \
arena_t scratch = arena_make(ARENA_STATIC, sizeof(tmpbuf), tmpbuf)
DWORD os__win_mode_to_access(filemode_e mode) {
DWORD out = 0;
if (mode & FILEMODE_READ) out |= GENERIC_READ;
if (mode & FILEMODE_WRITE) out |= GENERIC_WRITE;
return out;
}
DWORD os__win_mode_to_creation(filemode_e mode) {
if (mode == FILEMODE_READ) return OPEN_EXISTING;
if (mode == FILEMODE_WRITE) return CREATE_ALWAYS;
return OPEN_ALWAYS;
}
bool os_file_exists(strview_t path) {
OS_SMALL_SCRATCH();
tstr_t name = strv_to_tstr(&scratch, path);
DWORD attributes = GetFileAttributes(name.buf);
return attributes != INVALID_FILE_ATTRIBUTES && !(attributes & FILE_ATTRIBUTE_DIRECTORY);
}
tstr_t os_file_fullpath(arena_t *arena, strview_t filename) {
OS_SMALL_SCRATCH();
TCHAR long_path_prefix[] = TEXT("\\\\?\\");
const usize prefix_len = arrlen(long_path_prefix) - 1;
tstr_t rel_path = strv_to_tstr(&scratch, filename);
DWORD pathlen = GetFullPathName(rel_path.buf, 0, NULL, NULL);
tstr_t full_path = {
.buf = alloc(arena, TCHAR, pathlen + prefix_len + 1),
.len = pathlen + prefix_len,
};
memcpy(full_path.buf, long_path_prefix, prefix_len * sizeof(TCHAR));
GetFullPathName(rel_path.buf, pathlen + 1, full_path.buf + prefix_len, NULL);
return full_path;
}
bool os_file_delete(strview_t path) {
OS_SMALL_SCRATCH();
tstr_t fname = strv_to_tstr(&scratch, path);
return DeleteFile(fname.buf);
}
oshandle_t os_file_open(strview_t path, filemode_e mode) {
OS_SMALL_SCRATCH();
tstr_t full_path = os_file_fullpath(&scratch, path);
HANDLE handle = CreateFile(
full_path.buf,
os__win_mode_to_access(mode),
FILE_SHARE_READ,
NULL,
os__win_mode_to_creation(mode),
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (handle == INVALID_HANDLE_VALUE) {
handle = NULL;
}
return (oshandle_t){
.data = (uptr)handle
};
}
void os_file_close(oshandle_t handle) {
if (!os_handle_valid(handle)) return;
CloseHandle((HANDLE)handle.data);
}
usize os_file_read(oshandle_t handle, void *buf, usize len) {
if (!os_handle_valid(handle)) return 0;
DWORD read = 0;
ReadFile((HANDLE)handle.data, buf, (DWORD)len, &read, NULL);
return (usize)read;
}
usize os_file_write(oshandle_t handle, const void *buf, usize len) {
if (!os_handle_valid(handle)) return 0;
DWORD written = 0;
WriteFile((HANDLE)handle.data, buf, (DWORD)len, &written, NULL);
return (usize)written;
}
bool os_file_seek(oshandle_t handle, usize offset) {
if (!os_handle_valid(handle)) return false;
LARGE_INTEGER offset_large = {
.QuadPart = offset,
};
DWORD result = SetFilePointer((HANDLE)handle.data, offset_large.LowPart, &offset_large.HighPart, FILE_BEGIN);
return result != INVALID_SET_FILE_POINTER;
}
bool os_file_seek_end(oshandle_t handle) {
if (!os_handle_valid(handle)) return false;
DWORD result = SetFilePointer((HANDLE)handle.data, 0, NULL, FILE_END);
return result != INVALID_SET_FILE_POINTER;
}
void os_file_rewind(oshandle_t handle) {
if (!os_handle_valid(handle)) return;
SetFilePointer((HANDLE)handle.data, 0, NULL, FILE_BEGIN);
}
usize os_file_tell(oshandle_t handle) {
if (!os_handle_valid(handle)) return 0;
LARGE_INTEGER tell = {0};
BOOL result = SetFilePointerEx((HANDLE)handle.data, (LARGE_INTEGER){0}, &tell, FILE_CURRENT);
return result == TRUE ? (usize)tell.QuadPart : 0;
}
usize os_file_size(oshandle_t handle) {
if (!os_handle_valid(handle)) return 0;
LARGE_INTEGER size = {0};
BOOL result = GetFileSizeEx((HANDLE)handle.data, &size);
return result == TRUE ? (usize)size.QuadPart : 0;
}
bool os_file_is_finished(oshandle_t handle) {
if (!os_handle_valid(handle)) return 0;
char tmp = 0;
DWORD read = 0;
BOOL success = ReadFile((HANDLE)handle.data, &tmp, sizeof(tmp), &read, NULL);
bool is_finished = success && read == 0;
if (!is_finished) {
SetFilePointer((HANDLE)handle.data, -1, NULL, FILE_CURRENT);
}
return is_finished;
}
u64 os_file_time_fp(oshandle_t handle) {
if (!os_handle_valid(handle)) return 0;
FILETIME time = {0};
GetFileTime((HANDLE)handle.data, NULL, NULL, &time);
ULARGE_INTEGER utime = {
.HighPart = time.dwHighDateTime,
.LowPart = time.dwLowDateTime,
};
return (u64)utime.QuadPart;
}
// == DIR WALKER ================================
typedef struct dir_t {
WIN32_FIND_DATA find_data;
HANDLE handle;
dir_entry_t cur_entry;
dir_entry_t next_entry;
} dir_t;
dir_entry_t os__dir_entry_from_find_data(arena_t *arena, WIN32_FIND_DATA *fd) {
dir_entry_t out = {0};
out.name = str_from_tstr(arena, tstr_init(fd->cFileName, 0));
if (fd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
out.type = DIRTYPE_DIR;
}
else {
LARGE_INTEGER filesize = {
.LowPart = fd->nFileSizeLow,
.HighPart = fd->nFileSizeHigh,
};
out.file_size = filesize.QuadPart;
}
return out;
}
dir_t *os_dir_open(arena_t *arena, strview_t path) {
u8 tmpbuf[KB(1)] = {0};
arena_t scratch = arena_make(ARENA_STATIC, sizeof(tmpbuf), tmpbuf);
tstr_t winpath = strv_to_tstr(&scratch, path);
// get a little extra leeway
TCHAR fullpath[MAX_PATH + 16] = {0};
DWORD pathlen = GetFullPathName(winpath.buf, MAX_PATH, fullpath, NULL);
// add asterisk at the end of the path
if (fullpath[pathlen] != '\\' && fullpath[pathlen] != '/') {
fullpath[pathlen++] = '\\';
}
fullpath[pathlen++] = '*';
fullpath[pathlen++] = '\0';
dir_t *ctx = alloc(arena, dir_t);
ctx->handle = FindFirstFile(fullpath, &ctx->find_data);
if (ctx->handle == INVALID_HANDLE_VALUE) {
arena_pop(arena, sizeof(dir_t));
return NULL;
}
ctx->next_entry = os__dir_entry_from_find_data(arena, &ctx->find_data);
return ctx;
}
void os_dir_close(dir_t *dir) {
FindClose(dir->handle);
dir->handle = INVALID_HANDLE_VALUE;
}
bool os_dir_is_valid(dir_t *dir) {
return dir && dir->handle != INVALID_HANDLE_VALUE;
}
dir_entry_t *os_dir_next(arena_t *arena, dir_t *dir) {
if (!os_dir_is_valid(dir)) {
return NULL;
}
dir->cur_entry = dir->next_entry;
dir->next_entry = (dir_entry_t){0};
if (FindNextFile(dir->handle, &dir->find_data)) {
dir->next_entry = os__dir_entry_from_find_data(arena, &dir->find_data);
}
else {
os_dir_close(dir);
}
return &dir->cur_entry;
}
// == PROCESS ===================================
struct os_env_t {
void *data;
};
void os_set_env_var(arena_t scratch, strview_t key, strview_t value) {
str16_t k = strv_to_str16(&scratch, key);
str16_t v = strv_to_str16(&scratch, value);
SetEnvironmentVariableW(k.buf, v.buf);
}
str_t os_get_env_var(arena_t *arena, strview_t key) {
u8 tmpbuf[KB(1)];
arena_t scratch = arena_make(ARENA_STATIC, sizeof(tmpbuf), tmpbuf);
wchar_t static_buf[1024] = {0};
wchar_t *buf = static_buf;
str16_t k = strv_to_str16(&scratch, key);
DWORD len = GetEnvironmentVariableW(k.buf, static_buf, arrlen(static_buf));
if (len > arrlen(static_buf)) {
buf = alloc(&scratch, wchar_t, len);
len = GetEnvironmentVariableW(k.buf, buf, len);
}
return str_from_str16(arena, str16_init(buf, len));
}
os_env_t *os_get_env(arena_t *arena) {
os_env_t *out = alloc(arena, os_env_t);
out->data = GetEnvironmentStringsW();
return out;
}
oshandle_t os_run_cmd_async(arena_t scratch, os_cmd_t *cmd, os_env_t *optional_env) {
STARTUPINFOW start_info = {
.cb = sizeof(STARTUPINFO),
.hStdError = GetStdHandle(STD_ERROR_HANDLE),
.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE),
.hStdInput = GetStdHandle(STD_INPUT_HANDLE),
.dwFlags = STARTF_USESTDHANDLES,
};
PROCESS_INFORMATION proc_info = {0};
outstream_t cmdline = ostr_init(&scratch);
for_each (cur, cmd->head ? cmd->head : cmd) {
for (int i = 0; i < cur->count; ++i) {
strview_t arg = cur->items[i];
if (strv_contains(arg, ' ')) {
ostr_print(&cmdline, "\"%v\"", arg);
}
else {
ostr_puts(&cmdline, arg);
}
ostr_putc(&cmdline, ' ');
}
}
ostr_pop(&cmdline, 1);
ostr_putc(&cmdline, '\0');
strview_t cmd_view = ostr_as_view(&cmdline);
str16_t command = strv_to_str16(&scratch, cmd_view);
WCHAR *env = optional_env ? optional_env->data : NULL;
BOOL success = CreateProcessW(
NULL,
command.buf,
NULL,
NULL,
TRUE,
0,
env,
NULL,
&start_info,
&proc_info
);
if (env) {
FreeEnvironmentStringsW(env);
optional_env->data = NULL;
}
if (!success) {
err("couldn't create process (%v): %v", cmd_view, os_get_error_string(os_get_last_error()));
return os_handle_zero();
}
CloseHandle(proc_info.hThread);
return (oshandle_t) {
.data = (uptr)proc_info.hProcess
};
}
bool os_process_wait(oshandle_t proc, uint time, int *out_exit) {
if (!os_handle_valid(proc)) {
err("waiting on invalid handle");
return false;
}
DWORD result = WaitForSingleObject((HANDLE)proc.data, (DWORD)time);
if (result == WAIT_TIMEOUT) {
return false;
}
if (result != WAIT_OBJECT_0) {
err("could not wait for proces: %v", os_get_error_string(os_get_last_error()));
return false;
}
DWORD exit_status;
if (!GetExitCodeProcess((HANDLE)proc.data, &exit_status)) {
err("could not get exit status from process: %v", os_get_error_string(os_get_last_error()));
return false;
}
CloseHandle((HANDLE)proc.data);
if (out_exit) {
*out_exit = exit_status;
}
return exit_status == 0;
}
// == VMEM ======================================
void *os_reserve(usize size, usize *out_padded_size) {
usize alloc_size = os_pad_to_page(size);
void *ptr = VirtualAlloc(NULL, alloc_size, MEM_RESERVE, PAGE_NOACCESS);
if (out_padded_size) {
*out_padded_size = alloc_size;
}
return ptr;
}
bool os_commit(void *ptr, usize num_of_pages) {
usize page_size = os_get_system_info().page_size;
void *new_ptr = VirtualAlloc(ptr, num_of_pages * page_size, MEM_COMMIT, PAGE_READWRITE);
return new_ptr != NULL;
}
bool os_release(void *ptr, usize size) {
COLLA_UNUSED(size);
return VirtualFree(ptr, 0, MEM_RELEASE);
}
// == THREAD ====================================
DWORD os__win_thread_entry_point(void *ptr) {
os_entity_t *entity = (os_entity_t *)ptr;
thread_func_t *func = entity->thread.func;
void *userdata = entity->thread.userdata;
u64 id = entity->thread.id;
return func(id, userdata);
}
oshandle_t os_thread_launch(thread_func_t func, void *userdata) {
os_entity_t *entity = os__win_alloc_entity(OS_KIND_THREAD);
entity->thread.func = func;
entity->thread.userdata = userdata;
entity->thread.handle = CreateThread(NULL, 0, os__win_thread_entry_point, entity, 0, &entity->thread.id);
return (oshandle_t){ (uptr)entity };
}
bool os_thread_detach(oshandle_t thread) {
if (!os_handle_valid(thread)) return false;
os_entity_t *entity = (os_entity_t *)thread.data;
BOOL result = CloseHandle(entity->thread.handle);
os__win_free_entity(entity);
return result;
}
bool os_thread_join(oshandle_t thread, int *code) {
if (!os_handle_valid(thread)) return false;
os_entity_t *entity = (os_entity_t *)thread.data;
int return_code = WaitForSingleObject(entity->thread.handle, INFINITE);
if (code) *code = return_code;
BOOL result = CloseHandle(entity->thread.handle);
os__win_free_entity(entity);
return return_code != WAIT_FAILED && result;
}
u64 os_thread_get_id(oshandle_t thread) {
if (!os_handle_valid(thread)) return 0;
os_entity_t *entity = (os_entity_t *)thread.data;
return entity->thread.id;
}
// == MUTEX =====================================
oshandle_t os_mutex_create(void) {
os_entity_t *entity = os__win_alloc_entity(OS_KIND_MUTEX);
InitializeCriticalSection(&entity->mutex);
return (oshandle_t){ (uptr)entity };
}
void os_mutex_free(oshandle_t mutex) {
if (!os_handle_valid(mutex)) return;
os_entity_t *entity = (os_entity_t *)mutex.data;
DeleteCriticalSection(&entity->mutex);
os__win_free_entity(entity);
}
void os_mutex_lock(oshandle_t mutex) {
if (!os_handle_valid(mutex)) return;
os_entity_t *entity = (os_entity_t *)mutex.data;
EnterCriticalSection(&entity->mutex);
}
void os_mutex_unlock(oshandle_t mutex) {
if (!os_handle_valid(mutex)) return;
os_entity_t *entity = (os_entity_t *)mutex.data;
LeaveCriticalSection(&entity->mutex);
}
bool os_mutex_try_lock(oshandle_t mutex) {
if (!os_handle_valid(mutex)) return false;
os_entity_t *entity = (os_entity_t *)mutex.data;
return TryEnterCriticalSection(&entity->mutex);
}
#if !COLLA_NO_CONDITION_VARIABLE
// == CONDITION VARIABLE ========================
oshandle_t os_cond_create(void) {
os_entity_t *entity = os__win_alloc_entity(OS_KIND_CONDITION_VARIABLE);
InitializeConditionVariable(&entity->cv);
return (oshandle_t){ (uptr)entity };
}
void os_cond_free(oshandle_t cond) {
if (!os_handle_valid(cond)) return;
os_entity_t *entity = (os_entity_t *)cond.data;
os__win_free_entity(entity);
}
void os_cond_signal(oshandle_t cond) {
if (!os_handle_valid(cond)) return;
os_entity_t *entity = (os_entity_t *)cond.data;
WakeConditionVariable(&entity->cv);
}
void os_cond_broadcast(oshandle_t cond) {
if (!os_handle_valid(cond)) return;
os_entity_t *entity = (os_entity_t *)cond.data;
WakeAllConditionVariable(&entity->cv);
}
void os_cond_wait(oshandle_t cond, oshandle_t mutex, int milliseconds) {
if (!os_handle_valid(cond)) return;
os_entity_t *entity_cv = (os_entity_t *)cond.data;
os_entity_t *entity_mtx = (os_entity_t *)mutex.data;
SleepConditionVariableCS(&entity_cv->cv, &entity_mtx->mutex, milliseconds);
}
#endif

81
win/str_win32.c Normal file
View file

@ -0,0 +1,81 @@
#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;
}

145
xml.c
View file

@ -1,145 +0,0 @@
#include "xml.h"
#include "file.h"
#include "strstream.h"
#include "tracelog.h"
static xmltag_t *xml__parse_tag(arena_t *arena, instream_t *in);
xml_t xmlParse(arena_t *arena, strview_t filename) {
return xmlParseStr(arena, fileReadWholeStr(arena, filename));
}
xml_t xmlParseStr(arena_t *arena, str_t xmlstr) {
xml_t out = {
.text = xmlstr,
.root = alloc(arena, xmltag_t),
};
return out;
instream_t in = istrInitLen(xmlstr.buf, xmlstr.len);
while (!istrIsFinished(in)) {
xmltag_t *tag = xml__parse_tag(arena, &in);
if (out.tail) out.tail->next = tag;
else out.root->child = tag;
out.tail = tag;
}
return out;
}
xmltag_t *xmlGetTag(xmltag_t *parent, strview_t key, bool recursive) {
xmltag_t *t = parent ? parent->child : NULL;
while (t) {
if (strvEquals(key, t->key)) {
return t;
}
if (recursive && t->child) {
xmltag_t *out = xmlGetTag(t, key, recursive);
if (out) {
return out;
}
}
t = t->next;
}
return NULL;
}
strview_t xmlGetAttribute(xmltag_t *tag, strview_t key) {
xmlattr_t *a = tag ? tag->attributes : NULL;
while (a) {
if (strvEquals(key, a->key)) {
return a->value;
}
a = a->next;
}
return STRV_EMPTY;
}
// == PRIVATE FUNCTIONS ========================================================================
static xmlattr_t *xml__parse_attr(arena_t *arena, instream_t *in) {
if (istrPeek(in) != ' ') {
return NULL;
}
strview_t key = strvTrim(istrGetView(in, '='));
istrSkip(in, 2); // skip = and "
strview_t val = strvTrim(istrGetView(in, '"'));
istrSkip(in, 1); // skip "
if (strvIsEmpty(key) || strvIsEmpty(val)) {
warn("key or value empty");
return NULL;
}
xmlattr_t *attr = alloc(arena, xmlattr_t);
attr->key = key;
attr->value = val;
return attr;
}
static xmltag_t *xml__parse_tag(arena_t *arena, instream_t *in) {
istrSkipWhitespace(in);
// we're either parsing the body, or we have finished the object
if (istrPeek(in) != '<' || istrPeekNext(in) == '/') {
return NULL;
}
istrSkip(in, 1); // skip <
// meta tag, we don't care about these
if (istrPeek(in) == '?') {
istrIgnoreAndSkip(in, '\n');
return NULL;
}
xmltag_t *tag = alloc(arena, xmltag_t);
tag->key = strvTrim(istrGetViewEither(in, strv(" >")));
xmlattr_t *attr = xml__parse_attr(arena, in);
while (attr) {
attr->next = tag->attributes;
tag->attributes = attr;
attr = xml__parse_attr(arena, in);
}
// this tag does not have children, return
if (istrPeek(in) == '/') {
istrSkip(in, 2); // skip / and >
return tag;
}
istrSkip(in, 1); // skip >
xmltag_t *child = xml__parse_tag(arena, in);
while (child) {
if (tag->tail) {
tag->tail->next = child;
tag->tail = child;
}
else {
tag->child = tag->tail = child;
}
child = xml__parse_tag(arena, in);
}
// parse content
istrSkipWhitespace(in);
tag->content = istrGetView(in, '<');
// closing tag
istrSkip(in, 2); // skip < and /
strview_t closing = strvTrim(istrGetView(in, '>'));
if (!strvEquals(tag->key, closing)) {
warn("opening and closing tags are different!: (%v) != (%v)", tag->key, closing);
}
istrSkip(in, 1); // skip >
return tag;
}

31
xml.h
View file

@ -1,31 +0,0 @@
#pragma once
#include "str.h"
#include "arena.h"
typedef struct xmlattr_t {
strview_t key;
strview_t value;
struct xmlattr_t *next;
} xmlattr_t;
typedef struct xmltag_t {
strview_t key;
xmlattr_t *attributes;
strview_t content;
struct xmltag_t *child;
struct xmltag_t *tail;
struct xmltag_t *next;
} xmltag_t;
typedef struct {
str_t text;
xmltag_t *root;
xmltag_t *tail;
} xml_t;
xml_t xmlParse(arena_t *arena, strview_t filename);
xml_t xmlParseStr(arena_t *arena, str_t xmlstr);
xmltag_t *xmlGetTag(xmltag_t *parent, strview_t key, bool recursive);
strview_t xmlGetAttribute(xmltag_t *tag, strview_t key);