.
This commit is contained in:
parent
01f4ad7f62
commit
6d36aa4442
100 changed files with 5138 additions and 13015 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.vscode/
|
||||
200
arena.c
200
arena.c
|
|
@ -1,188 +1,230 @@
|
|||
#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;
|
||||
}
|
||||
|
||||
|
||||
switch (arena->type) {
|
||||
case ARENA_VIRTUAL: arena__free_virtual(arena); break;
|
||||
case ARENA_MALLOC: arena__free_malloc(arena); break;
|
||||
// ARENA_STATIC does not need to be freed
|
||||
case ARENA_STATIC: break;
|
||||
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
77
arena.h
|
|
@ -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
100
base64.c
|
|
@ -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"
|
||||
8
base64.h
8
base64.h
|
|
@ -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
54
bits.h
|
|
@ -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
57
build.c
|
|
@ -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"
|
||||
115
colladefines.h
115
colladefines.h
|
|
@ -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))
|
||||
37
collatypes.h
37
collatypes.h
|
|
@ -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
75
core.c
Normal 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
199
core.h
Normal 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
|
||||
282
cthreads.c
282
cthreads.c
|
|
@ -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
|
||||
57
cthreads.h
57
cthreads.h
|
|
@ -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
82
darr.h
Normal 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
163
dir.c
|
|
@ -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
25
dir.h
|
|
@ -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);
|
||||
128
docs/arena.md
128
docs/arena.md
|
|
@ -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);
|
||||
}
|
||||
```
|
||||
|
|
@ -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);
|
||||
}
|
||||
```
|
||||
|
|
@ -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);
|
||||
}
|
||||
```
|
||||
49
docs/dir.md
49
docs/dir.md
|
|
@ -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);
|
||||
}
|
||||
```
|
||||
BIN
docs/docs.com
BIN
docs/docs.com
Binary file not shown.
38
docs/file.md
38
docs/file.md
|
|
@ -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`
|
||||
|
|
@ -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);
|
||||
}
|
||||
```
|
||||
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
|
@ -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);
|
||||
}
|
||||
```
|
||||
42
docs/html.md
42
docs/html.md
|
|
@ -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;
|
||||
}
|
||||
```
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# HTTP
|
||||
----------
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# Ini
|
||||
----------
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# Json
|
||||
----------
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# Markdown
|
||||
----------
|
||||
|
|
@ -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.
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# Server
|
||||
----------
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# SHA-1
|
||||
----------
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# Socket
|
||||
----------
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# Str
|
||||
----------
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# StrStream
|
||||
----------
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# Tracelog
|
||||
----------
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# UTF-8
|
||||
----------
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# Vec
|
||||
----------
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# VMem
|
||||
----------
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# WebSocket
|
||||
----------
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# Xml
|
||||
----------
|
||||
398
file.c
398
file.c
|
|
@ -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
56
file.h
|
|
@ -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);
|
||||
61
format.c
61
format.c
|
|
@ -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
|
||||
13
format.h
13
format.h
|
|
@ -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);
|
||||
621
highlight.c
621
highlight.c
|
|
@ -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("&"));
|
||||
break;
|
||||
case '<':
|
||||
ostrPuts(out, strv("<"));
|
||||
break;
|
||||
case '>':
|
||||
ostrPuts(out, strv(">"));
|
||||
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]);
|
||||
}
|
||||
49
highlight.h
49
highlight.h
|
|
@ -1,49 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "str.h"
|
||||
|
||||
typedef enum {
|
||||
HL_COLOR_NORMAL,
|
||||
HL_COLOR_PREPROC,
|
||||
HL_COLOR_TYPES,
|
||||
HL_COLOR_CUSTOM_TYPES,
|
||||
HL_COLOR_KEYWORDS,
|
||||
HL_COLOR_NUMBER,
|
||||
HL_COLOR_STRING,
|
||||
HL_COLOR_COMMENT,
|
||||
HL_COLOR_FUNC,
|
||||
HL_COLOR_SYMBOL,
|
||||
HL_COLOR_MACRO,
|
||||
|
||||
HL_COLOR__COUNT,
|
||||
} hl_color_e;
|
||||
|
||||
typedef enum {
|
||||
HL_FLAG_NONE = 0,
|
||||
HL_FLAG_HTML = 1 << 0,
|
||||
} hl_flags_e;
|
||||
|
||||
typedef struct {
|
||||
strview_t keyword;
|
||||
hl_color_e color;
|
||||
} hl_keyword_t;
|
||||
|
||||
typedef struct {
|
||||
usize idx;
|
||||
usize size;
|
||||
} hl_line_t;
|
||||
|
||||
typedef struct {
|
||||
strview_t colors[HL_COLOR__COUNT];
|
||||
hl_keyword_t *extra_kwrds;
|
||||
int kwrds_count;
|
||||
hl_flags_e flags;
|
||||
} hl_config_t;
|
||||
|
||||
typedef struct hl_ctx_t hl_ctx_t;
|
||||
|
||||
hl_ctx_t *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);
|
||||
227
hot_reload.c
227
hot_reload.c
|
|
@ -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
|
||||
31
hot_reload.h
31
hot_reload.h
|
|
@ -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
77
html.h
|
|
@ -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
556
http.c
|
|
@ -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
83
http.h
|
|
@ -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
279
ini.c
|
|
@ -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
54
ini.h
|
|
@ -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
386
json.c
|
|
@ -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
47
json.h
|
|
@ -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)
|
||||
884
lz4/lz4.h
884
lz4/lz4.h
|
|
@ -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
|
||||
504
markdown.c
504
markdown.c
|
|
@ -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("&"));
|
||||
break;
|
||||
case '<':
|
||||
ostrPuts(out, strv("<"));
|
||||
break;
|
||||
case '>':
|
||||
ostrPuts(out, strv(">"));
|
||||
break;
|
||||
default:
|
||||
ostrPutc(out, view.buf[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
59
markdown.h
59
markdown.h
|
|
@ -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]  -> 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
632
net.c
Normal 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
190
net.h
Normal 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
235
os.c
Normal 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
216
os.h
Normal 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
844
parsers.c
Normal 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
156
parsers.h
Normal 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
454
server.c
|
|
@ -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
|
||||
37
server.h
37
server.h
|
|
@ -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
120
sha1.c
|
|
@ -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
18
sha1.h
|
|
@ -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
286
socket.c
|
|
@ -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
|
||||
93
socket.h
93
socket.h
|
|
@ -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);
|
||||
756
str.c
756
str.c
|
|
@ -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) {
|
||||
if (!buf || !len) return STR_EMPTY;
|
||||
|
||||
str_t out = {
|
||||
.buf = alloc(arena, char, len + 1),
|
||||
.len = len
|
||||
};
|
||||
|
||||
memcpy(out.buf, buf, len);
|
||||
|
||||
return out;
|
||||
str_t str_init_len(arena_t *arena, const char *buf, usize len) {
|
||||
if (!buf || !len) return STR_EMPTY;
|
||||
char *str = alloc(arena, char, len + 1);
|
||||
memmove(str, buf, len);
|
||||
return (str_t){ str, len };
|
||||
}
|
||||
|
||||
str_t 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, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
str_t out = strFmtv(arena, fmt, args);
|
||||
va_end(args);
|
||||
return out;
|
||||
str_t str_fmt(arena_t *arena, const char *fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
str_t out = str_fmtv(arena, fmt, args);
|
||||
va_end(args);
|
||||
return out;
|
||||
}
|
||||
|
||||
str_t 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);
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
out.buf = alloc(arena, char, outlen + 1);
|
||||
WideCharToMultiByte(
|
||||
CP_UTF8, 0,
|
||||
src, (int)srclen,
|
||||
out.buf, outlen,
|
||||
NULL, NULL
|
||||
);
|
||||
str_t str_from_str16(arena_t *arena, str16_t src) {
|
||||
if (!src.buf) return STR_EMPTY;
|
||||
if (!src.len) return STR_EMPTY;
|
||||
|
||||
out.len = outlen;
|
||||
|
||||
#elif COLLA_LIN
|
||||
fatal("strFromWChar not implemented yet!");
|
||||
#endif
|
||||
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;
|
||||
}
|
||||
|
||||
#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;
|
||||
str16_t strv_to_str16(arena_t *arena, strview_t src) {
|
||||
return strv_os_to_str16(arena, src);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
303
str.h
303
str.h
|
|
@ -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 \
|
||||
#define str__1(arena, x) \
|
||||
_Generic((x), \
|
||||
const char *: str_init, \
|
||||
char *: str_init, \
|
||||
strview_t: str_init_view \
|
||||
)(arena, x)
|
||||
|
||||
#define str__2(arena, cstr, clen) 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
|
||||
655
strstream.c
655
strstream.c
|
|
@ -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"
|
||||
160
strstream.h
160
strstream.h
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
465
tools/docs.c
465
tools/docs.c
|
|
@ -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
418
tools/nob.c
Normal 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();
|
||||
}
|
||||
212
tracelog.c
212
tracelog.c
|
|
@ -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
|
||||
}
|
||||
59
tracelog.h
59
tracelog.h
|
|
@ -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
172
utf8.c
|
|
@ -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
19
utf8.h
|
|
@ -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
71
vec.h
|
|
@ -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
635
vfs.c
|
|
@ -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
36
vfs.h
|
|
@ -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);
|
||||
34
vfs_file.h
34
vfs_file.h
|
|
@ -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
133
vmem.c
|
|
@ -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
12
vmem.h
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
#if COLLA_CLANG
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
#endif
|
||||
142
websocket.c
142
websocket.c
|
|
@ -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;
|
||||
}
|
||||
13
websocket.h
13
websocket.h
|
|
@ -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
337
win/net_win32.c
Normal 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
693
win/os_win32.c
Normal 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
81
win/str_win32.c
Normal 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
145
xml.c
|
|
@ -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
31
xml.h
|
|
@ -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);
|
||||
Loading…
Add table
Add a link
Reference in a new issue