use arena everywhere + bunch of cool new stuff
This commit is contained in:
parent
8fdecd89a5
commit
ae59f269c2
51 changed files with 5443 additions and 7233 deletions
|
|
@ -1,28 +0,0 @@
|
||||||
add_library(colla STATIC
|
|
||||||
src/collatypes.h
|
|
||||||
src/vec.h
|
|
||||||
src/win32_slim.h
|
|
||||||
src/tracelog.h src/tracelog.c
|
|
||||||
src/str.h src/str.c
|
|
||||||
src/hashmap.h src/hashmap.c
|
|
||||||
src/utf8.h src/utf8.c
|
|
||||||
src/ini.h src/ini.c
|
|
||||||
src/strstream.h src/strstream.c
|
|
||||||
src/os.h src/os.c
|
|
||||||
src/file.h src/file.c
|
|
||||||
src/dir.h src/dir.c
|
|
||||||
src/socket.h src/socket.c
|
|
||||||
src/http.h src/http.c
|
|
||||||
src/cthreads.h src/cthreads.c
|
|
||||||
)
|
|
||||||
|
|
||||||
if(MSVC)
|
|
||||||
target_link_libraries(colla ws2_32.lib)
|
|
||||||
target_compile_options(colla PRIVATE /W4)
|
|
||||||
else()
|
|
||||||
target_link_libraries(colla pthread)
|
|
||||||
target_compile_options(colla PRIVATE -Wall -Wextra -Wpedantic)
|
|
||||||
target_compile_definitions(colla PUBLIC _DEFAULT_SOURCE)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
target_include_directories(colla PUBLIC src)
|
|
||||||
1
build.bat
Normal file
1
build.bat
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
zig cc -o test.exe main.c -lWs2_32 -lWininet -Wall -Wpedantic -Wno-newline-eof
|
||||||
17
build.c
Normal file
17
build.c
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
// remember to link Ws2_32 (sockets, server, http) and Wininet (https) if you want to do networking stuff!
|
||||||
|
|
||||||
|
#include "colla/arena.c"
|
||||||
|
#include "colla/base64.c"
|
||||||
|
#include "colla/cthreads.c"
|
||||||
|
#include "colla/file.c"
|
||||||
|
#include "colla/format.c"
|
||||||
|
#include "colla/http.c"
|
||||||
|
#include "colla/ini.c"
|
||||||
|
#include "colla/json.c"
|
||||||
|
#include "colla/server.c"
|
||||||
|
#include "colla/socket.c"
|
||||||
|
#include "colla/str.c"
|
||||||
|
#include "colla/strstream.c"
|
||||||
|
#include "colla/tracelog.c"
|
||||||
|
#include "colla/utf8.c"
|
||||||
|
#include "colla/vmem.c"
|
||||||
198
colla/arena.c
Normal file
198
colla/arena.c
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
#include "arena.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "vmem.h"
|
||||||
|
#include "tracelog.h"
|
||||||
|
|
||||||
|
static uintptr_t arena__align(uintptr_t ptr, usize align) {
|
||||||
|
return (ptr + (align - 1)) & ~(align - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static arena_t arena__make_virtual(usize size);
|
||||||
|
static arena_t arena__make_malloc(usize size);
|
||||||
|
static arena_t arena__make_static(byte *buf, usize len);
|
||||||
|
|
||||||
|
static void arena__free_virtual(arena_t *arena);
|
||||||
|
static void arena__free_malloc(arena_t *arena);
|
||||||
|
|
||||||
|
arena_t arenaInit(const arena_desc_t *desc) {
|
||||||
|
if (desc) {
|
||||||
|
switch (desc->type) {
|
||||||
|
case ARENA_VIRTUAL: return arena__make_virtual(desc->allocation);
|
||||||
|
case ARENA_MALLOC: return arena__make_malloc(desc->allocation);
|
||||||
|
case ARENA_STATIC: return arena__make_static(desc->static_buffer, desc->allocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug("couldn't init arena: %p %d\n", desc, desc ? desc->type : 0);
|
||||||
|
return (arena_t){0};
|
||||||
|
}
|
||||||
|
|
||||||
|
void arenaCleanup(arena_t *arena) {
|
||||||
|
if (!arena) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (arena->type) {
|
||||||
|
case ARENA_VIRTUAL: arena__free_virtual(arena); break;
|
||||||
|
case ARENA_MALLOC: arena__free_malloc(arena); break;
|
||||||
|
// ARENA_STATIC does not need to be freed
|
||||||
|
case ARENA_STATIC: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(arena, 0, sizeof(arena_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
arena_t arenaScratch(arena_t *arena) {
|
||||||
|
if (!arena) {
|
||||||
|
return (arena_t){0};
|
||||||
|
}
|
||||||
|
|
||||||
|
return (arena_t) {
|
||||||
|
.start = arena->current,
|
||||||
|
.current = arena->current,
|
||||||
|
.end = arena->end,
|
||||||
|
.type = arena->type,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void *arenaAlloc(const arena_alloc_desc_t *desc) {
|
||||||
|
if (!desc || !desc->arena) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
usize total = desc->size * desc->count;
|
||||||
|
arena_t *arena = desc->arena;
|
||||||
|
|
||||||
|
arena->current = (byte *)arena__align((uintptr_t)arena->current, desc->align);
|
||||||
|
|
||||||
|
if (total > arenaRemaining(arena)) {
|
||||||
|
if (desc->flags & ALLOC_SOFT_FAIL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
printf("finished space in arena, tried to allocate %zu bytes out of %zu\n", total, arenaRemaining(arena));
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arena->type == ARENA_VIRTUAL) {
|
||||||
|
usize allocated = arenaTell(arena);
|
||||||
|
usize page_end = vmemPadToPage(allocated);
|
||||||
|
usize new_cur = allocated + total;
|
||||||
|
|
||||||
|
if (new_cur > page_end) {
|
||||||
|
usize extra_mem = vmemPadToPage(new_cur - page_end);
|
||||||
|
usize page_size = vmemGetPageSize();
|
||||||
|
// TODO is this really correct?
|
||||||
|
usize num_of_pages = (extra_mem / page_size) + 1;
|
||||||
|
|
||||||
|
assert(num_of_pages > 0);
|
||||||
|
|
||||||
|
if (!vmemCommit(arena->current, num_of_pages + 1)) {
|
||||||
|
if (desc->flags & ALLOC_SOFT_FAIL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
printf("failed to commit memory for virtual arena, tried to commit %zu pages\n", num_of_pages);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte *ptr = arena->current;
|
||||||
|
arena->current += total;
|
||||||
|
|
||||||
|
if (desc->flags & ALLOC_NOZERO) return ptr;
|
||||||
|
|
||||||
|
memset(ptr, 0, total);
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
|
||||||
|
return desc->flags & ALLOC_NOZERO ? ptr : memset(ptr, 0, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
usize arenaTell(arena_t *arena) {
|
||||||
|
return arena ? arena->current - arena->start : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
usize arenaRemaining(arena_t *arena) {
|
||||||
|
return arena && (arena->current < arena->end) ? arena->end - arena->current : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void arenaRewind(arena_t *arena, usize from_start) {
|
||||||
|
if (!arena) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(arenaTell(arena) >= from_start);
|
||||||
|
|
||||||
|
arena->current = arena->start + from_start;
|
||||||
|
}
|
||||||
|
|
||||||
|
void arenaPop(arena_t *arena, usize amount) {
|
||||||
|
if (!arena) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
usize position = arenaTell(arena);
|
||||||
|
if (!position) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
arenaRewind(arena, position - amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// == VIRTUAL ARENA ====================================================================================================
|
||||||
|
|
||||||
|
static arena_t arena__make_virtual(usize size) {
|
||||||
|
usize alloc_size = 0;
|
||||||
|
byte *ptr = vmemInit(size, &alloc_size);
|
||||||
|
if (!vmemCommit(ptr, 1)) {
|
||||||
|
vmemRelease(ptr);
|
||||||
|
ptr = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (arena_t){
|
||||||
|
.start = ptr,
|
||||||
|
.current = ptr,
|
||||||
|
.end = ptr ? ptr + alloc_size : NULL,
|
||||||
|
.type = ARENA_VIRTUAL,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void arena__free_virtual(arena_t *arena) {
|
||||||
|
if (!arena->start) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = vmemRelease(arena->start);
|
||||||
|
assert(success && "Failed arena free");
|
||||||
|
}
|
||||||
|
|
||||||
|
// == MALLOC ARENA =====================================================================================================
|
||||||
|
|
||||||
|
static arena_t arena__make_malloc(usize size) {
|
||||||
|
byte *ptr = malloc(size);
|
||||||
|
assert(ptr);
|
||||||
|
return (arena_t) {
|
||||||
|
.start = ptr,
|
||||||
|
.current = ptr,
|
||||||
|
.end = ptr ? ptr + size : NULL,
|
||||||
|
.type = ARENA_MALLOC,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void arena__free_malloc(arena_t *arena) {
|
||||||
|
free(arena->start);
|
||||||
|
}
|
||||||
|
|
||||||
|
// == STATIC ARENA =====================================================================================================
|
||||||
|
|
||||||
|
static arena_t arena__make_static(byte *buf, usize len) {
|
||||||
|
return (arena_t) {
|
||||||
|
.start = buf,
|
||||||
|
.current = buf,
|
||||||
|
.end = buf ? buf + len : NULL,
|
||||||
|
.type = ARENA_STATIC,
|
||||||
|
};
|
||||||
|
}
|
||||||
57
colla/arena.h
Normal file
57
colla/arena.h
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "collatypes.h"
|
||||||
|
|
||||||
|
#define alignof _Alignof
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ARENA_VIRTUAL,
|
||||||
|
ARENA_MALLOC,
|
||||||
|
ARENA_STATIC,
|
||||||
|
} arena_type_e;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ALLOC_FLAGS_NONE = 0,
|
||||||
|
ALLOC_NOZERO = 1 << 0,
|
||||||
|
ALLOC_SOFT_FAIL = 1 << 1,
|
||||||
|
} alloc_flags_e;
|
||||||
|
|
||||||
|
typedef struct arena_t {
|
||||||
|
uint8 *start;
|
||||||
|
uint8 *current;
|
||||||
|
uint8 *end;
|
||||||
|
arena_type_e type;
|
||||||
|
} arena_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
arena_type_e type;
|
||||||
|
usize allocation;
|
||||||
|
byte *static_buffer;
|
||||||
|
} arena_desc_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
arena_t *arena;
|
||||||
|
usize count;
|
||||||
|
alloc_flags_e flags;
|
||||||
|
usize size;
|
||||||
|
usize align;
|
||||||
|
} arena_alloc_desc_t;
|
||||||
|
|
||||||
|
#define KB(count) ( (count) * 1024)
|
||||||
|
#define MB(count) (KB(count) * 1024)
|
||||||
|
#define GB(count) (MB(count) * 1024)
|
||||||
|
|
||||||
|
// arena_type_e type, usize allocation, [ byte *static_buffer ]
|
||||||
|
#define arenaMake(...) arenaInit(&(arena_desc_t){ __VA_ARGS__ })
|
||||||
|
|
||||||
|
// arena_t *arena, T type, [ usize count, alloc_flags_e flags, usize size, usize align ]
|
||||||
|
#define alloc(arenaptr, type, ...) arenaAlloc(&(arena_alloc_desc_t){ .size = sizeof(type), .count = 1, .align = alignof(type), .arena = arenaptr, __VA_ARGS__ })
|
||||||
|
|
||||||
|
arena_t arenaInit(const arena_desc_t *desc);
|
||||||
|
void arenaCleanup(arena_t *arena);
|
||||||
|
|
||||||
|
void *arenaAlloc(const arena_alloc_desc_t *desc);
|
||||||
|
usize arenaTell(arena_t *arena);
|
||||||
|
usize arenaRemaining(arena_t *arena);
|
||||||
|
void arenaRewind(arena_t *arena, usize from_start);
|
||||||
|
void arenaPop(arena_t *arena, usize amount);
|
||||||
100
colla/base64.c
Normal file
100
colla/base64.c
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
#include "base64.h"
|
||||||
|
|
||||||
|
#include "warnings/colla_warn_beg.h"
|
||||||
|
|
||||||
|
#include "arena.h"
|
||||||
|
|
||||||
|
static char encoding_table[] = {
|
||||||
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||||
|
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||||
|
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||||
|
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||||
|
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
||||||
|
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||||
|
'w', 'x', 'y', 'z', '0', '1', '2', '3',
|
||||||
|
'4', '5', '6', '7', '8', '9', '+', '/'
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint8 decoding_table[] = {
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63,
|
||||||
|
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
|
||||||
|
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
|
||||||
|
24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31,
|
||||||
|
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
|
||||||
|
44, 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
buffer_t base64Encode(arena_t *arena, buffer_t buffer) {
|
||||||
|
usize outlen = ((buffer.len + 2) / 3) * 4;
|
||||||
|
uint8 *out = alloc(arena, uint8, outlen);
|
||||||
|
|
||||||
|
for (usize i = 0, j = 0; i < buffer.len;) {
|
||||||
|
uint32 a = i < buffer.len ? buffer.data[i++] : 0;
|
||||||
|
uint32 b = i < buffer.len ? buffer.data[i++] : 0;
|
||||||
|
uint32 c = i < buffer.len ? buffer.data[i++] : 0;
|
||||||
|
|
||||||
|
uint32 triple = (a << 16) | (b << 8) | c;
|
||||||
|
|
||||||
|
out[j++] = encoding_table[(triple >> 18) & 0x3F];
|
||||||
|
out[j++] = encoding_table[(triple >> 12) & 0x3F];
|
||||||
|
out[j++] = encoding_table[(triple >> 6) & 0x3F];
|
||||||
|
out[j++] = encoding_table[(triple >> 0) & 0x3F];
|
||||||
|
}
|
||||||
|
|
||||||
|
usize mod = buffer.len % 3;
|
||||||
|
if (mod) {
|
||||||
|
mod = 3 - mod;
|
||||||
|
for (usize i = 0; i < mod; ++i) {
|
||||||
|
out[outlen - 1 - i] = '=';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (buffer_t){
|
||||||
|
.data = out,
|
||||||
|
.len = outlen
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer_t base64Decode(arena_t *arena, buffer_t buffer) {
|
||||||
|
uint8 *out = arena->current;
|
||||||
|
usize start = arenaTell(arena);
|
||||||
|
|
||||||
|
for (usize i = 0; i < buffer.len; i += 4) {
|
||||||
|
uint8 a = decoding_table[buffer.data[i + 0]];
|
||||||
|
uint8 b = decoding_table[buffer.data[i + 1]];
|
||||||
|
uint8 c = decoding_table[buffer.data[i + 2]];
|
||||||
|
uint8 d = decoding_table[buffer.data[i + 3]];
|
||||||
|
|
||||||
|
uint32 triple =
|
||||||
|
((uint32)a << 18) |
|
||||||
|
((uint32)b << 12) |
|
||||||
|
((uint32)c << 6) |
|
||||||
|
((uint32)d);
|
||||||
|
|
||||||
|
uint8 *bytes = alloc(arena, uint8, 3);
|
||||||
|
|
||||||
|
bytes[0] = (triple >> 16) & 0xFF;
|
||||||
|
bytes[1] = (triple >> 8) & 0xFF;
|
||||||
|
bytes[2] = (triple >> 0) & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
usize outlen = arenaTell(arena) - start;
|
||||||
|
|
||||||
|
return (buffer_t){
|
||||||
|
.data = out,
|
||||||
|
.len = outlen,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "warnings/colla_warn_end.h"
|
||||||
9
colla/base64.h
Normal file
9
colla/base64.h
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "collatypes.h"
|
||||||
|
#include "str.h"
|
||||||
|
|
||||||
|
typedef struct arena_t arena_t;
|
||||||
|
|
||||||
|
buffer_t base64Encode(arena_t *arena, buffer_t buffer);
|
||||||
|
buffer_t base64Decode(arena_t *arena, buffer_t buffer);
|
||||||
79
colla/colladefines.h
Normal file
79
colla/colladefines.h
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define arrlen(a) (sizeof(a) / sizeof((a)[0]))
|
||||||
|
|
||||||
|
#if defined(_DEBUG) || !defined(NDEBUG)
|
||||||
|
#define COLLA_DEBUG 1
|
||||||
|
#define COLLA_RELEASE 0
|
||||||
|
#else
|
||||||
|
#define COLLA_DEBUG 0
|
||||||
|
#define COLLA_RELEASE 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
|
||||||
|
#define COLLA_WIN 1
|
||||||
|
#define COLLA_OSX 0
|
||||||
|
#define COLLA_LIN 0
|
||||||
|
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
|
||||||
|
#define COLLA_WIN 0
|
||||||
|
#define COLLA_OSX 1
|
||||||
|
#define COLLA_LIN 0
|
||||||
|
|
||||||
|
#elif defined(__linux__)
|
||||||
|
|
||||||
|
#define COLLA_WIN 0
|
||||||
|
#define COLLA_OSX 0
|
||||||
|
#define COLLA_LIN 1
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__clang__)
|
||||||
|
|
||||||
|
#define COLLA_CLANG 1
|
||||||
|
#define COLLA_MSVC 0
|
||||||
|
#define COLLA_TCC 0
|
||||||
|
#define COLLA_GCC 0
|
||||||
|
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
|
||||||
|
#define COLLA_CLANG 0
|
||||||
|
#define COLLA_MSVC 1
|
||||||
|
#define COLLA_TCC 0
|
||||||
|
#define COLLA_GCC 0
|
||||||
|
|
||||||
|
#elif defined(__TINYC__)
|
||||||
|
|
||||||
|
#define COLLA_CLANG 0
|
||||||
|
#define COLLA_MSVC 0
|
||||||
|
#define COLLA_TCC 1
|
||||||
|
#define COLLA_GCC 0
|
||||||
|
|
||||||
|
#elif defined(__GNUC__)
|
||||||
|
|
||||||
|
#define COLLA_CLANG 0
|
||||||
|
#define COLLA_MSVC 0
|
||||||
|
#define COLLA_TCC 0
|
||||||
|
#define COLLA_GCC 1
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if COLLA_CLANG
|
||||||
|
|
||||||
|
#define COLLA_CMT_LIB 0
|
||||||
|
|
||||||
|
#elif COLLA_MSVC
|
||||||
|
|
||||||
|
#define COLLA_CMT_LIB 1
|
||||||
|
|
||||||
|
#elif COLLA_TCC
|
||||||
|
|
||||||
|
#define COLLA_CMT_LIB 1
|
||||||
|
|
||||||
|
#elif COLLA_GCC
|
||||||
|
|
||||||
|
#define COLLA_CMT_LIB 0
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "colladefines.h"
|
||||||
|
|
||||||
typedef unsigned char uchar;
|
typedef unsigned char uchar;
|
||||||
typedef unsigned short ushort;
|
typedef unsigned short ushort;
|
||||||
|
|
@ -19,3 +22,16 @@ typedef int64_t int64;
|
||||||
|
|
||||||
typedef size_t usize;
|
typedef size_t usize;
|
||||||
typedef ptrdiff_t isize;
|
typedef ptrdiff_t isize;
|
||||||
|
|
||||||
|
typedef uint8 byte;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8 *data;
|
||||||
|
usize len;
|
||||||
|
} buffer_t;
|
||||||
|
|
||||||
|
#if COLLA_WIN && defined(UNICODE)
|
||||||
|
typedef wchar_t TCHAR;
|
||||||
|
#else
|
||||||
|
typedef char TCHAR;
|
||||||
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,23 @@
|
||||||
#include "cthreads.h"
|
#include "cthreads.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
cthread_func_t func;
|
cthread_func_t func;
|
||||||
void *arg;
|
void *arg;
|
||||||
} _thr_internal_t;
|
} _thr_internal_t;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#if COLLA_WIN
|
||||||
#include "win32_slim.h"
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#include <stdlib.h>
|
#include <handleapi.h>
|
||||||
|
#include <processthreadsapi.h>
|
||||||
|
#include <synchapi.h>
|
||||||
|
|
||||||
|
#undef INFINITE
|
||||||
|
#undef WAIT_FAILED
|
||||||
|
// couple of defines to avoid including windows.h
|
||||||
|
#define INFINITE 0xFFFFFFFF // Infinite timeout
|
||||||
|
#define WAIT_FAILED ((DWORD)0xFFFFFFFF)
|
||||||
|
|
||||||
// == THREAD ===========================================
|
// == THREAD ===========================================
|
||||||
|
|
||||||
|
|
@ -21,7 +31,7 @@ static DWORD _thrFuncInternal(void *arg) {
|
||||||
|
|
||||||
cthread_t thrCreate(cthread_func_t func, void *arg) {
|
cthread_t thrCreate(cthread_func_t func, void *arg) {
|
||||||
HANDLE thread = INVALID_HANDLE_VALUE;
|
HANDLE thread = INVALID_HANDLE_VALUE;
|
||||||
_thr_internal_t *params = malloc(sizeof(_thr_internal_t));
|
_thr_internal_t *params = calloc(1, sizeof(_thr_internal_t));
|
||||||
|
|
||||||
if(params) {
|
if(params) {
|
||||||
params->func = func;
|
params->func = func;
|
||||||
|
|
@ -68,7 +78,7 @@ bool thrJoin(cthread_t ctx, int *code) {
|
||||||
// == MUTEX ============================================
|
// == MUTEX ============================================
|
||||||
|
|
||||||
cmutex_t mtxInit(void) {
|
cmutex_t mtxInit(void) {
|
||||||
CRITICAL_SECTION *crit_sec = malloc(sizeof(CRITICAL_SECTION));
|
CRITICAL_SECTION *crit_sec = calloc(1, sizeof(CRITICAL_SECTION));
|
||||||
if(crit_sec) {
|
if(crit_sec) {
|
||||||
InitializeCriticalSection(crit_sec);
|
InitializeCriticalSection(crit_sec);
|
||||||
}
|
}
|
||||||
|
|
@ -100,10 +110,10 @@ bool mtxUnlock(cmutex_t ctx) {
|
||||||
|
|
||||||
// == CONDITION VARIABLE ===============================
|
// == CONDITION VARIABLE ===============================
|
||||||
|
|
||||||
#include <tracelog.h>
|
#include "tracelog.h"
|
||||||
|
|
||||||
condvar_t condInit(void) {
|
condvar_t condInit(void) {
|
||||||
CONDITION_VARIABLE *cond = malloc(sizeof(CONDITION_VARIABLE));
|
CONDITION_VARIABLE *cond = calloc(1, sizeof(CONDITION_VARIABLE));
|
||||||
InitializeConditionVariable(cond);
|
InitializeConditionVariable(cond);
|
||||||
return (condvar_t)cond;
|
return (condvar_t)cond;
|
||||||
}
|
}
|
||||||
|
|
@ -121,7 +131,7 @@ void condWakeAll(condvar_t cond) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void condWait(condvar_t cond, cmutex_t mtx) {
|
void condWait(condvar_t cond, cmutex_t mtx) {
|
||||||
BOOL res = SleepConditionVariableCS((CONDITION_VARIABLE *)cond, (CRITICAL_SECTION *)mtx, INFINITE);
|
SleepConditionVariableCS((CONDITION_VARIABLE *)cond, (CRITICAL_SECTION *)mtx, INFINITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) {
|
void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) {
|
||||||
|
|
@ -130,7 +140,6 @@ void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds) {
|
||||||
|
|
||||||
#else
|
#else
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/syscall.h>
|
#include <sys/syscall.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
@ -150,7 +159,7 @@ static void *_thrFuncInternal(void *arg) {
|
||||||
cthread_t thrCreate(cthread_func_t func, void *arg) {
|
cthread_t thrCreate(cthread_func_t func, void *arg) {
|
||||||
pthread_t handle = (pthread_t)NULL;
|
pthread_t handle = (pthread_t)NULL;
|
||||||
|
|
||||||
_thr_internal_t *params = malloc(sizeof(_thr_internal_t));
|
_thr_internal_t *params = calloc(1, sizeof(_thr_internal_t));
|
||||||
|
|
||||||
if(params) {
|
if(params) {
|
||||||
params->func = func;
|
params->func = func;
|
||||||
|
|
@ -195,7 +204,7 @@ bool thrJoin(cthread_t ctx, int *code) {
|
||||||
// == MUTEX ============================================
|
// == MUTEX ============================================
|
||||||
|
|
||||||
cmutex_t mtxInit(void) {
|
cmutex_t mtxInit(void) {
|
||||||
pthread_mutex_t *mutex = malloc(sizeof(pthread_mutex_t));
|
pthread_mutex_t *mutex = calloc(1, sizeof(pthread_mutex_t));
|
||||||
|
|
||||||
if(mutex) {
|
if(mutex) {
|
||||||
if(pthread_mutex_init(mutex, NULL)) {
|
if(pthread_mutex_init(mutex, NULL)) {
|
||||||
|
|
@ -230,7 +239,7 @@ bool mtxUnlock(cmutex_t ctx) {
|
||||||
// == CONDITION VARIABLE ===============================
|
// == CONDITION VARIABLE ===============================
|
||||||
|
|
||||||
condvar_t condInit(void) {
|
condvar_t condInit(void) {
|
||||||
pthread_cond_t *cond = malloc(sizeof(pthread_cond_t));
|
pthread_cond_t *cond = calloc(1, sizeof(pthread_cond_t));
|
||||||
|
|
||||||
if(cond) {
|
if(cond) {
|
||||||
if(pthread_cond_init(cond, NULL)) {
|
if(pthread_cond_init(cond, NULL)) {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#include "collatypes.h"
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
// == THREAD ===========================================
|
// == THREAD ===========================================
|
||||||
|
|
||||||
|
|
@ -37,31 +32,6 @@ bool mtxLock(cmutex_t ctx);
|
||||||
bool mtxTryLock(cmutex_t ctx);
|
bool mtxTryLock(cmutex_t ctx);
|
||||||
bool mtxUnlock(cmutex_t ctx);
|
bool mtxUnlock(cmutex_t ctx);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
// small c++ class to make mutexes easier to use
|
|
||||||
struct lock_t {
|
|
||||||
inline lock_t(cmutex_t mutex)
|
|
||||||
: mutex(mutex) {
|
|
||||||
if (mtxValid(mutex)) {
|
|
||||||
mtxLock(mutex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline ~lock_t() {
|
|
||||||
unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void unlock() {
|
|
||||||
if (mtxValid(mutex)) {
|
|
||||||
mtxUnlock(mutex);
|
|
||||||
}
|
|
||||||
mutex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
cmutex_t mutex;
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// == CONDITION VARIABLE ===============================
|
// == CONDITION VARIABLE ===============================
|
||||||
|
|
||||||
typedef uintptr_t condvar_t;
|
typedef uintptr_t condvar_t;
|
||||||
|
|
@ -76,7 +46,3 @@ void condWakeAll(condvar_t cond);
|
||||||
|
|
||||||
void condWait(condvar_t cond, cmutex_t mtx);
|
void condWait(condvar_t cond, cmutex_t mtx);
|
||||||
void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds);
|
void condWaitTimed(condvar_t cond, cmutex_t mtx, int milliseconds);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
} // extern "C"
|
|
||||||
#endif
|
|
||||||
|
|
|
||||||
487
colla/file.c
487
colla/file.c
|
|
@ -1,311 +1,288 @@
|
||||||
#include "file.h"
|
#include "file.h"
|
||||||
|
|
||||||
|
#include "warnings/colla_warn_beg.h"
|
||||||
|
|
||||||
#include "tracelog.h"
|
#include "tracelog.h"
|
||||||
|
#include "format.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#if COLLA_WIN
|
||||||
#include "win32_slim.h"
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
static DWORD _toWin32Access(int mode) {
|
#define WIN32_LEAN_AND_MEAN
|
||||||
if(mode & FILE_READ) return GENERIC_READ;
|
#include <fileapi.h>
|
||||||
if(mode & FILE_WRITE) return GENERIC_WRITE;
|
#include <handleapi.h>
|
||||||
if(mode & FILE_BOTH) return GENERIC_READ | GENERIC_WRITE;
|
|
||||||
fatal("unrecognized access mode: %d", mode);
|
#undef FILE_BEGIN
|
||||||
return 0;
|
#undef FILE_CURRENT
|
||||||
|
#undef FILE_END
|
||||||
|
|
||||||
|
#define FILE_BEGIN 0
|
||||||
|
#define FILE_CURRENT 1
|
||||||
|
#define FILE_END 2
|
||||||
|
|
||||||
|
static DWORD file__mode_to_access(filemode_e mode) {
|
||||||
|
if (mode & FILE_APPEND) return FILE_APPEND_DATA;
|
||||||
|
|
||||||
|
DWORD out = 0;
|
||||||
|
if (mode & FILE_READ) out |= GENERIC_READ;
|
||||||
|
if (mode & FILE_WRITE) out |= GENERIC_WRITE;
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
static DWORD _toWin32Creation(filemode_t mode) {
|
static DWORD file__mode_to_creation(filemode_e mode) {
|
||||||
if(mode & FILE_READ) return OPEN_EXISTING;
|
if (mode == FILE_READ) return OPEN_EXISTING;
|
||||||
if(mode == (FILE_WRITE | FILE_CLEAR)) return CREATE_ALWAYS;
|
if (mode == FILE_WRITE) return CREATE_ALWAYS;
|
||||||
if(mode & FILE_WRITE) return OPEN_ALWAYS;
|
return OPEN_ALWAYS;
|
||||||
if(mode & FILE_BOTH) return OPEN_ALWAYS;
|
|
||||||
fatal("unrecognized creation mode: %d", mode);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fileExists(const char *fname) {
|
bool fileExists(const char *name) {
|
||||||
return GetFileAttributesA(fname) != INVALID_FILE_ATTRIBUTES;
|
return GetFileAttributesA(name) != INVALID_FILE_ATTRIBUTES;
|
||||||
}
|
}
|
||||||
|
|
||||||
file_t fileOpen(const char *fname, filemode_t mode) {
|
file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode) {
|
||||||
return (file_t)CreateFileA(
|
TCHAR long_path_prefix[] = TEXT("\\\\?\\");
|
||||||
fname,
|
const usize prefix_len = arrlen(long_path_prefix) - 1;
|
||||||
_toWin32Access(mode),
|
|
||||||
|
TCHAR *rel_path = strvToTChar(&scratch, name);
|
||||||
|
DWORD pathlen = GetFullPathName(rel_path, 0, NULL, NULL);
|
||||||
|
|
||||||
|
TCHAR *full_path = alloc(&scratch, TCHAR, pathlen + prefix_len + 1);
|
||||||
|
memcpy(full_path, long_path_prefix, prefix_len * sizeof(TCHAR));
|
||||||
|
|
||||||
|
GetFullPathName(rel_path, pathlen + 1, full_path + prefix_len, NULL);
|
||||||
|
|
||||||
|
HANDLE handle = CreateFile(
|
||||||
|
full_path,
|
||||||
|
file__mode_to_access(mode),
|
||||||
0,
|
0,
|
||||||
NULL,
|
NULL,
|
||||||
_toWin32Creation(mode),
|
file__mode_to_creation(mode),
|
||||||
FILE_ATTRIBUTE_NORMAL,
|
FILE_ATTRIBUTE_NORMAL,
|
||||||
NULL
|
NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return (file_t){
|
||||||
|
.handle = (uintptr_t)handle,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void fileClose(file_t ctx) {
|
void fileClose(file_t ctx) {
|
||||||
if (ctx) {
|
if (!fileIsValid(ctx)) return;
|
||||||
CloseHandle((HANDLE)ctx);
|
CloseHandle((HANDLE)ctx.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fileIsValid(file_t ctx) {
|
||||||
|
return (HANDLE)ctx.handle != 0 &&
|
||||||
|
(HANDLE)ctx.handle != INVALID_HANDLE_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
usize fileRead(file_t ctx, void *buf, usize len) {
|
||||||
|
if (!fileIsValid(ctx)) return 0;
|
||||||
|
DWORD read = 0;
|
||||||
|
ReadFile((HANDLE)ctx.handle, buf, len, &read, NULL);
|
||||||
|
return (usize)read;
|
||||||
|
}
|
||||||
|
|
||||||
|
usize fileWrite(file_t ctx, const void *buf, usize len) {
|
||||||
|
if (!fileIsValid(ctx)) return 0;
|
||||||
|
DWORD written = 0;
|
||||||
|
WriteFile((HANDLE)ctx.handle, buf, len, &written, NULL);
|
||||||
|
return (usize)written;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fileSeekEnd(file_t ctx) {
|
||||||
|
if (!fileIsValid(ctx)) return false;
|
||||||
|
DWORD result = SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_END);
|
||||||
|
return result != INVALID_SET_FILE_POINTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fileRewind(file_t ctx) {
|
||||||
|
if (!fileIsValid(ctx)) return;
|
||||||
|
SetFilePointer((HANDLE)ctx.handle, 0, NULL, FILE_BEGIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
usize fileTell(file_t ctx) {
|
||||||
|
if (!fileIsValid(ctx)) return 0;
|
||||||
|
LARGE_INTEGER tell = {0};
|
||||||
|
BOOL result = SetFilePointerEx((HANDLE)ctx.handle, (LARGE_INTEGER){0}, &tell, FILE_CURRENT);
|
||||||
|
return result == TRUE ? (usize)tell.QuadPart : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
usize fileSize(file_t ctx) {
|
||||||
|
if (!fileIsValid(ctx)) return 0;
|
||||||
|
LARGE_INTEGER size = {0};
|
||||||
|
BOOL result = GetFileSizeEx((HANDLE)ctx.handle, &size);
|
||||||
|
return result == TRUE ? (usize)size.QuadPart : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64 fileGetTimeFP(file_t ctx) {
|
||||||
|
if (!fileIsValid(ctx)) return 0;
|
||||||
|
FILETIME time = {0};
|
||||||
|
GetFileTime((HANDLE)ctx.handle, NULL, NULL, &time);
|
||||||
|
ULARGE_INTEGER utime = {
|
||||||
|
.HighPart = time.dwHighDateTime,
|
||||||
|
.LowPart = time.dwLowDateTime,
|
||||||
|
};
|
||||||
|
return (uint64)utime.QuadPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static const char *file__mode_to_stdio(filemode_e mode) {
|
||||||
|
if (mode == FILE_READ) return "rb";
|
||||||
|
if (mode == FILE_WRITE) return "wb";
|
||||||
|
if (mode == FILE_APPEND) return "ab";
|
||||||
|
if (mode == (FILE_READ | FILE_WRITE)) return "rb+";
|
||||||
|
|
||||||
|
return "ab+";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fileExists(const char *name) {
|
||||||
|
FILE *fp = fopen(name, "rb");
|
||||||
|
bool exists = fp != NULL;
|
||||||
|
fclose(fp);
|
||||||
|
return exists;
|
||||||
|
}
|
||||||
|
|
||||||
|
file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode) {
|
||||||
|
str_t filename = str(&scratch, name);
|
||||||
|
return (file_t) {
|
||||||
|
.handle = (uintptr_t)fopen(filename.buf, file__mode_to_stdio(mode))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void fileClose(file_t ctx) {
|
||||||
|
FILE *fp = (FILE *)ctx.handle;
|
||||||
|
if (fp) {
|
||||||
|
fclose(fp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fileIsValid(file_t ctx) {
|
bool fileIsValid(file_t ctx) {
|
||||||
return (HANDLE)ctx != INVALID_HANDLE_VALUE;
|
return (FILE *)ctx.handle != NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usize fileRead(file_t ctx, void *buf, usize len) {
|
||||||
|
if (!fileIsValid(ctx)) return 0;
|
||||||
|
return fread(buf, 1, len, (FILE *)ctx.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
usize fileWrite(file_t ctx, const void *buf, usize len) {
|
||||||
|
if (!fileIsValid(ctx)) return 0;
|
||||||
|
return fwrite(buf, 1, len, (FILE *)ctx.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fileSeekEnd(file_t ctx) {
|
||||||
|
if (!fileIsValid(ctx)) return false;
|
||||||
|
return fseek((FILE *)ctx.handle, 0, SEEK_END) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fileRewind(file_t ctx) {
|
||||||
|
if (!fileIsValid(ctx)) return;
|
||||||
|
return fseek((FILE *)ctx.handle, 0, SEEK_SET) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
usize fileTell(file_t ctx) {
|
||||||
|
if (!fileIsValid(ctx)) return 0;
|
||||||
|
return ftell((FILE *)ctx.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
usize fileSize(file_t ctx) {
|
||||||
|
if (!fileIsValid(ctx)) return 0;
|
||||||
|
FILE *fp = (FILE *)ctx.handle;
|
||||||
|
fseek(fp, 0, SEEK_END);
|
||||||
|
long len = ftell(fp);
|
||||||
|
fseek(fp, 0, SEEK_SET);
|
||||||
|
return (usize)len;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64 fileGetTimeFP(file_t ctx) {
|
||||||
|
#if COLLA_LIN
|
||||||
|
#else
|
||||||
|
fatal("fileGetTime not implemented yet outside of linux and windows");
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
bool filePutc(file_t ctx, char c) {
|
bool filePutc(file_t ctx, char c) {
|
||||||
return fileWrite(ctx, &c, 1) == 1;
|
return fileWrite(ctx, &c, 1) == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool filePuts(file_t ctx, const char *str) {
|
bool filePuts(file_t ctx, strview_t v) {
|
||||||
usize len = strlen(str);
|
return fileWrite(ctx, v.buf, v.len) == v.len;
|
||||||
return fileWrite(ctx, str, len) == len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool filePutstr(file_t ctx, str_t str) {
|
bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...) {
|
||||||
return fileWrite(ctx, str.buf, str.len) == str.len;
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
bool result = filePrintfv(scratch, ctx, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool filePutview(file_t ctx, strview_t view) {
|
bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args) {
|
||||||
return fileWrite(ctx, view.buf, view.len) == view.len;
|
str_t string = strFmtv(&scratch, fmt, args);
|
||||||
|
return fileWrite(ctx, string.buf, string.len) == string.len;
|
||||||
}
|
}
|
||||||
|
|
||||||
usize fileRead(file_t ctx, void *buf, usize len) {
|
buffer_t fileReadWhole(arena_t *arena, arena_t scratch, strview_t name) {
|
||||||
DWORD bytes_read = 0;
|
return fileReadWholeFP(arena, fileOpen(scratch, name, FILE_READ));
|
||||||
BOOL result = ReadFile((HANDLE)ctx, buf, (DWORD)len, &bytes_read, NULL);
|
|
||||||
return result == TRUE ? (usize)bytes_read : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
usize fileWrite(file_t ctx, const void *buf, usize len) {
|
buffer_t fileReadWholeFP(arena_t *arena, file_t ctx) {
|
||||||
DWORD bytes_read = 0;
|
if (!fileIsValid(ctx)) return (buffer_t){0};
|
||||||
BOOL result = WriteFile((HANDLE)ctx, buf, (DWORD)len, &bytes_read, NULL);
|
buffer_t out = {0};
|
||||||
return result == TRUE ? (usize)bytes_read : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fileSeekEnd(file_t ctx) {
|
out.len = fileSize(ctx);
|
||||||
return SetFilePointerEx((HANDLE)ctx, (LARGE_INTEGER){0}, NULL, FILE_END) == TRUE;
|
out.data = alloc(arena, uint8, out.len);
|
||||||
}
|
usize read = fileRead(ctx, out.data, out.len);
|
||||||
|
|
||||||
void fileRewind(file_t ctx) {
|
if (read != out.len) {
|
||||||
SetFilePointerEx((HANDLE)ctx, (LARGE_INTEGER){0}, NULL, FILE_BEGIN);
|
err("fileReadWholeFP: fileRead failed, should be %zu but is %zu", out.len, read);
|
||||||
}
|
arenaPop(arena, out.len);
|
||||||
|
return (buffer_t){0};
|
||||||
uint64 fileTell(file_t ctx) {
|
|
||||||
LARGE_INTEGER tell;
|
|
||||||
BOOL result = SetFilePointerEx((HANDLE)ctx, (LARGE_INTEGER){0}, &tell, FILE_CURRENT);
|
|
||||||
return result == TRUE ? (uint64)tell.QuadPart : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64 fileGetTime(file_t ctx) {
|
|
||||||
uint64 fp_time = 0;
|
|
||||||
GetFileTime((HANDLE)ctx, NULL, NULL, (FILETIME *)&fp_time);
|
|
||||||
return fp_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
const char *_toStdioMode(filemode_t mode) {
|
|
||||||
switch(mode) {
|
|
||||||
case FILE_READ: return "rb";
|
|
||||||
case FILE_BOTH: return "r+b";
|
|
||||||
case FILE_WRITE: return "wb";
|
|
||||||
default: fatal("mode not recognized: %d", mode); return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fileExists(const char *fname) {
|
|
||||||
return access(fname, F_OK) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
file_t fileOpen(const char *fname, filemode_t mode) {
|
|
||||||
return (file_t)(void*) fopen(fname, _toStdioMode(mode));
|
|
||||||
}
|
|
||||||
|
|
||||||
void fileClose(file_t ctx) {
|
|
||||||
if(ctx) {
|
|
||||||
fclose((FILE*)ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fileIsValid(file_t ctx) {
|
|
||||||
return (FILE *)ctx != NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool filePutc(file_t ctx, char c) {
|
|
||||||
return fputc(c, (FILE*)ctx) == c;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool filePuts(file_t ctx, const char *str) {
|
|
||||||
return fputs(str, (FILE*)ctx) != EOF;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool filePutstr(file_t ctx, str_t str) {
|
|
||||||
return fileWrite(ctx, str.buf, str.len) == str.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool filePutview(file_t ctx, strview_t view) {
|
|
||||||
return fileWrite(ctx, view.buf, view.len) == view.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize fileRead(file_t ctx, void *buf, usize len) {
|
|
||||||
return fread(buf, 1, len, (FILE*)ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
usize fileWrite(file_t ctx, const void *buf, usize len) {
|
|
||||||
return fwrite(buf, 1, len, (FILE*)ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fileSeekEnd(file_t ctx) {
|
|
||||||
return fseek((FILE*)ctx, 0, SEEK_END) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void fileRewind(file_t ctx) {
|
|
||||||
rewind((FILE*)ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64 fileTell(file_t ctx) {
|
|
||||||
return (uint64)ftell((FILE*)ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64 fileGetTime(file_t ctx) {
|
|
||||||
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static str_t _readWholeInternalStr(file_t ctx) {
|
|
||||||
str_t contents = strInit();
|
|
||||||
uint64 fsize = 0;
|
|
||||||
usize read = 0;
|
|
||||||
|
|
||||||
if(!fileSeekEnd(ctx)) {
|
|
||||||
err("file: couldn't read until end");
|
|
||||||
goto failed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fsize = fileTell(ctx);
|
return out;
|
||||||
fileRewind(ctx);
|
|
||||||
|
|
||||||
contents.buf = (char *)malloc(fsize + 1);
|
|
||||||
contents.len = fsize;
|
|
||||||
if(!contents.buf) {
|
|
||||||
err("file: couldn't allocate buffer");
|
|
||||||
goto failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
read = fileRead(ctx, contents.buf, fsize);
|
|
||||||
if(read != fsize) {
|
|
||||||
err("file: read wrong amount of bytes: %zu instead of %zu", read, fsize);
|
|
||||||
goto failed_free;
|
|
||||||
}
|
|
||||||
|
|
||||||
contents.buf[contents.len] = '\0';
|
|
||||||
|
|
||||||
failed:
|
|
||||||
return contents;
|
|
||||||
failed_free:
|
|
||||||
strFree(contents);
|
|
||||||
return strInit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static vec(uint8) _readWholeInternalVec(file_t ctx) {
|
str_t fileReadWholeStr(arena_t *arena, arena_t scratch, strview_t name) {
|
||||||
vec(uint8) contents = NULL;
|
return fileReadWholeStrFP(arena, fileOpen(scratch, name, FILE_READ));
|
||||||
uint64 fsize = 0;
|
}
|
||||||
usize read = 0;
|
|
||||||
|
|
||||||
if(!fileSeekEnd(ctx)) {
|
str_t fileReadWholeStrFP(arena_t *arena, file_t ctx) {
|
||||||
err("file: couldn't read until end");
|
if (!fileIsValid(ctx)) return (str_t){0};
|
||||||
goto failed;
|
|
||||||
|
str_t out = {0};
|
||||||
|
|
||||||
|
out.len = fileSize(ctx);
|
||||||
|
out.buf = alloc(arena, uint8, out.len + 1);
|
||||||
|
usize read = fileRead(ctx, out.buf, out.len);
|
||||||
|
|
||||||
|
if (read != out.len) {
|
||||||
|
err("fileReadWholeFP: fileRead failed, should be %zu but is %zu", out.len, read);
|
||||||
|
arenaPop(arena, out.len + 1);
|
||||||
|
return (str_t){0};
|
||||||
}
|
}
|
||||||
|
|
||||||
fsize = fileTell(ctx);
|
return out;
|
||||||
fileRewind(ctx);
|
|
||||||
|
|
||||||
vecReserve(contents, fsize);
|
|
||||||
if(!contents) {
|
|
||||||
err("file: couldn't allocate buffer");
|
|
||||||
goto failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
read = fileRead(ctx, contents, fsize);
|
|
||||||
if(read != fsize) {
|
|
||||||
err("file: read wrong amount of bytes: %zu instead of %zu", read, fsize);
|
|
||||||
goto failed_free;
|
|
||||||
}
|
|
||||||
|
|
||||||
_veclen(contents) = read;
|
|
||||||
|
|
||||||
failed:
|
|
||||||
return contents;
|
|
||||||
failed_free:
|
|
||||||
vecFree(contents);
|
|
||||||
return contents;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vec(uint8) fileReadWhole(const char *fname) {
|
bool fileWriteWhole(arena_t scratch, strview_t name, const void *buf, usize len) {
|
||||||
file_t fp = fileOpen(fname, FILE_READ);
|
file_t fp = fileOpen(scratch, name, FILE_WRITE);
|
||||||
if (!fileIsValid(fp)) return NULL;
|
|
||||||
vec(uint8) contents = fileReadWholeFP(fp);
|
|
||||||
fileClose(fp);
|
|
||||||
return contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
vec(uint8) fileReadWholeFP(file_t ctx) {
|
|
||||||
return _readWholeInternalVec(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t fileReadWholeText(const char *fname) {
|
|
||||||
file_t fp = fileOpen(fname, FILE_READ);
|
|
||||||
if(!fileIsValid(fp)) {
|
|
||||||
err("couldn't open file %s", fname);
|
|
||||||
return strInit();
|
|
||||||
}
|
|
||||||
str_t contents = fileReadWholeTextFP(fp);
|
|
||||||
fileClose(fp);
|
|
||||||
return contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t fileReadWholeTextFP(file_t ctx) {
|
|
||||||
return _readWholeInternalStr(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fileWriteWhole(const char *fname, filebuf_t data) {
|
|
||||||
file_t fp = fileOpen(fname, FILE_WRITE);
|
|
||||||
if (!fileIsValid(fp)) {
|
if (!fileIsValid(fp)) {
|
||||||
err("couldn't open file %s", fname);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bool res = fileWriteWholeFP(fp, data);
|
usize written = fileWrite(fp, buf, len);
|
||||||
fileClose(fp);
|
fileClose(fp);
|
||||||
return res;
|
return written == len;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fileWriteWholeFP(file_t ctx, filebuf_t data) {
|
uint64 fileGetTime(arena_t scratch, strview_t name) {
|
||||||
usize written = fileWrite(ctx, data.buf, data.len);
|
return fileGetTimeFP(fileOpen(scratch, name, FILE_READ));
|
||||||
return written == data.len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fileWriteWholeText(const char *fname, strview_t string) {
|
#include "warnings/colla_warn_end.h"
|
||||||
file_t fp = fileOpen(fname, FILE_WRITE);
|
|
||||||
if (!fileIsValid(fp)) {
|
|
||||||
err("couldn't open file %s", fname);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
bool res = fileWriteWholeTextFP(fp, string);
|
|
||||||
fileClose(fp);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fileWriteWholeTextFP(file_t ctx, strview_t string) {
|
|
||||||
return fileWriteWholeFP(ctx, (filebuf_t){ (uint8 *)string.buf, string.len });
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64 fileGetTimePath(const char *path) {
|
|
||||||
file_t fp = fileOpen(path, FILE_READ);
|
|
||||||
if (!fileIsValid(fp)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
uint64 fp_time = fileGetTime(fp);
|
|
||||||
fileClose(fp);
|
|
||||||
return fp_time;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
53
colla/file.h
53
colla/file.h
|
|
@ -1,38 +1,32 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#include <stdarg.h>
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "collatypes.h"
|
#include "collatypes.h"
|
||||||
#include "str.h"
|
#include "str.h"
|
||||||
#include "vec.h"
|
#include "arena.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
FILE_READ = 1 << 0,
|
FILE_READ = 1 << 0,
|
||||||
FILE_WRITE = 1 << 1,
|
FILE_WRITE = 1 << 1,
|
||||||
FILE_CLEAR = 1 << 2,
|
FILE_APPEND = 1 << 2,
|
||||||
FILE_BOTH = 1 << 3
|
} filemode_e;
|
||||||
} filemode_t;
|
|
||||||
|
|
||||||
typedef uintptr_t file_t;
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const uint8 *buf;
|
uintptr_t handle;
|
||||||
usize len;
|
} file_t;
|
||||||
} filebuf_t;
|
|
||||||
|
|
||||||
bool fileExists(const char *fname);
|
bool fileExists(const char *name);
|
||||||
|
|
||||||
file_t fileOpen(const char *fname, filemode_t mode);
|
file_t fileOpen(arena_t scratch, strview_t name, filemode_e mode);
|
||||||
void fileClose(file_t ctx);
|
void fileClose(file_t ctx);
|
||||||
|
|
||||||
bool fileIsValid(file_t ctx);
|
bool fileIsValid(file_t ctx);
|
||||||
|
|
||||||
bool filePutc(file_t ctx, char c);
|
bool filePutc(file_t ctx, char c);
|
||||||
bool filePuts(file_t ctx, const char *str);
|
bool filePuts(file_t ctx, strview_t v);
|
||||||
bool filePutstr(file_t ctx, str_t str);
|
bool filePrintf(arena_t scratch, file_t ctx, const char *fmt, ...);
|
||||||
bool filePutview(file_t ctx, strview_t view);
|
bool filePrintfv(arena_t scratch, file_t ctx, const char *fmt, va_list args);
|
||||||
|
|
||||||
usize fileRead(file_t ctx, void *buf, usize len);
|
usize fileRead(file_t ctx, void *buf, usize len);
|
||||||
usize fileWrite(file_t ctx, const void *buf, usize len);
|
usize fileWrite(file_t ctx, const void *buf, usize len);
|
||||||
|
|
@ -40,23 +34,16 @@ usize fileWrite(file_t ctx, const void *buf, usize len);
|
||||||
bool fileSeekEnd(file_t ctx);
|
bool fileSeekEnd(file_t ctx);
|
||||||
void fileRewind(file_t ctx);
|
void fileRewind(file_t ctx);
|
||||||
|
|
||||||
uint64 fileTell(file_t ctx);
|
usize fileTell(file_t ctx);
|
||||||
|
usize fileSize(file_t ctx);
|
||||||
|
|
||||||
vec(uint8) fileReadWhole(const char *fname);
|
buffer_t fileReadWhole(arena_t *arena, arena_t scratch, strview_t name);
|
||||||
vec(uint8) fileReadWholeFP(file_t ctx);
|
buffer_t fileReadWholeFP(arena_t *arena, file_t ctx);
|
||||||
|
|
||||||
str_t fileReadWholeText(const char *fname);
|
str_t fileReadWholeStr(arena_t *arena, arena_t scratch, strview_t name);
|
||||||
str_t fileReadWholeTextFP(file_t ctx);
|
str_t fileReadWholeStrFP(arena_t *arena, file_t ctx);
|
||||||
|
|
||||||
bool fileWriteWhole(const char *fname, filebuf_t data);
|
bool fileWriteWhole(arena_t scratch, strview_t name, const void *buf, usize len);
|
||||||
bool fileWriteWholeFP(file_t ctx, filebuf_t data);
|
|
||||||
|
|
||||||
bool fileWriteWholeText(const char *fname, strview_t string);
|
uint64 fileGetTime(arena_t scratch, strview_t name);
|
||||||
bool fileWriteWholeTextFP(file_t ctx, strview_t string);
|
uint64 fileGetTimeFP(file_t ctx);
|
||||||
|
|
||||||
uint64 fileGetTime(file_t ctx);
|
|
||||||
uint64 fileGetTimePath(const char *path);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
} // extern "C"
|
|
||||||
#endif
|
|
||||||
60
colla/format.c
Normal file
60
colla/format.c
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
#include "format.h"
|
||||||
|
|
||||||
|
#define STB_SPRINTF_DECORATE(name) stb_##name
|
||||||
|
#define STB_SPRINTF_NOUNALIGNED
|
||||||
|
#define STB_SPRINTF_IMPLEMENTATION
|
||||||
|
#include "stb/stb_sprintf.h"
|
||||||
|
|
||||||
|
#include "arena.h"
|
||||||
|
|
||||||
|
static char *fmt__stb_callback(const char *buf, void *ud, int len) {
|
||||||
|
printf("%s", buf);
|
||||||
|
return (char *)ud;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fmtPrint(const char *fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
int out = fmtPrintv(fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fmtPrintv(const char *fmt, va_list args) {
|
||||||
|
char buffer[STB_SPRINTF_MIN];
|
||||||
|
return stb_vsprintfcb(fmt__stb_callback, buffer, buffer, fmt, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
int fmtBuffer(char *buffer, usize buflen, const char *fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
int out = fmtBufferv(buffer, buflen, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fmtBufferv(char *buffer, usize buflen, const char *fmt, va_list args) {
|
||||||
|
return stb_vsnprintf(buffer, (int)buflen, fmt, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
str_t fmtStr(Arena *arena, const char *fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
str_t out = fmtStrv(arena, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
str_t fmtStrv(Arena *arena, const char *fmt, va_list args) {
|
||||||
|
va_list vcopy;
|
||||||
|
va_copy(vcopy, args);
|
||||||
|
int len = stb_vsnprintf(NULL, 0, fmt, vcopy);
|
||||||
|
va_end(vcopy);
|
||||||
|
|
||||||
|
char *buffer = alloc(arena, char, len + 1);
|
||||||
|
stb_vsnprintf(buffer, len + 1, fmt, args);
|
||||||
|
|
||||||
|
return (str_t){ .buf = buffer, .len = (usize)len };
|
||||||
|
}
|
||||||
|
#endif
|
||||||
11
colla/format.h
Normal file
11
colla/format.h
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
typedef struct arena_t arena_t;
|
||||||
|
|
||||||
|
int fmtPrint(const char *fmt, ...);
|
||||||
|
int fmtPrintv(const char *fmt, va_list args);
|
||||||
|
|
||||||
|
int fmtBuffer(char *buffer, usize buflen, const char *fmt, ...);
|
||||||
|
int fmtBufferv(char *buffer, usize buflen, const char *fmt, va_list args);
|
||||||
735
colla/http.c
735
colla/http.c
|
|
@ -1,474 +1,507 @@
|
||||||
#include "http.h"
|
#include "http.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include "warnings/colla_warn_beg.h"
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
// #include "os.h"
|
#include "arena.h"
|
||||||
|
#include "strstream.h"
|
||||||
|
#include "format.h"
|
||||||
|
#include "socket.h"
|
||||||
#include "tracelog.h"
|
#include "tracelog.h"
|
||||||
|
|
||||||
#include "vec.h"
|
#if COLLA_WIN
|
||||||
|
#if COLLA_CMT_LIB
|
||||||
|
#pragma comment(lib, "Wininet")
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef _WIN32
|
#include <windows.h>
|
||||||
#define stricmp _stricmp
|
#include <wininet.h>
|
||||||
#else
|
|
||||||
#include <strings.h> // strcasecmp
|
|
||||||
#define stricmp strcasecmp
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// == INTERNAL ================================================================
|
static const TCHAR *https__get_method_str(http_method_e method);
|
||||||
|
|
||||||
static void _setField(vec(http_field_t) *fields_vec, const char *key, const char *value) {
|
static http_header_t *http__parse_headers(arena_t *arena, instream_t *in) {
|
||||||
vec(http_field_t) fields = *fields_vec;
|
http_header_t *head = NULL;
|
||||||
|
strview_t line = (strview_t){0};
|
||||||
for (uint32 i = 0; i < vecLen(fields); ++i) {
|
|
||||||
if (stricmp(fields[i].key, key) == 0) {
|
|
||||||
char **curval = &fields[i].value;
|
|
||||||
usize curlen = strlen(*curval);
|
|
||||||
usize newlen = strlen(value);
|
|
||||||
if(newlen > curlen) {
|
|
||||||
*curval = (char *)realloc(*curval, newlen + 1);
|
|
||||||
}
|
|
||||||
memcpy(*curval, value, newlen);
|
|
||||||
(*curval)[newlen] = '\0';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise, add it to the list
|
|
||||||
http_field_t field;
|
|
||||||
usize klen = strlen(key);
|
|
||||||
usize vlen = strlen(value);
|
|
||||||
field.key = (char *)malloc(klen + 1);
|
|
||||||
field.value = (char *)malloc(vlen + 1);
|
|
||||||
memcpy(field.key, key, klen);
|
|
||||||
memcpy(field.value, value, vlen);
|
|
||||||
field.key[klen] = field.value[vlen] = '\0';
|
|
||||||
|
|
||||||
vecAppend(*fields_vec, field);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _parseFields(vec(http_field_t) *fields, str_istream_t *in) {
|
|
||||||
strview_t line;
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
line = istrGetview(in, '\r');
|
line = istrGetView(in, '\r');
|
||||||
|
|
||||||
usize pos = strvFind(line, ':', 0);
|
usize pos = strvFind(line, ':', 0);
|
||||||
if(pos != STRV_NOT_FOUND) {
|
if (pos != STR_NONE) {
|
||||||
strview_t key = strvSub(line, 0, pos);
|
http_header_t *h = alloc(arena, http_header_t);
|
||||||
strview_t value = strvSub(line, pos + 2, SIZE_MAX);
|
|
||||||
|
|
||||||
char *key_str = NULL;
|
h->key = strvSub(line, 0, pos);
|
||||||
char *value_str = NULL;
|
h->value = strvSub(line, pos + 2, SIZE_MAX);
|
||||||
|
|
||||||
key_str = strvCopy(key).buf;
|
h->next = head;
|
||||||
value_str = strvCopy(value).buf;
|
head = h;
|
||||||
|
|
||||||
_setField(fields, key_str, value_str);
|
|
||||||
|
|
||||||
free(key_str);
|
|
||||||
free(value_str);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
istrSkip(in, 2); // skip \r\n
|
istrSkip(in, 2); // skip \r\n
|
||||||
} while(line.len > 2);
|
} while (line.len > 2); // while line != "\r\n"
|
||||||
|
|
||||||
|
return head;
|
||||||
}
|
}
|
||||||
|
|
||||||
// == HTTP STATUS =============================================================
|
const char *httpGetStatusString(int status) {
|
||||||
|
|
||||||
const char *httpGetStatusString(resstatus_t status) {
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case STATUS_OK: return "OK";
|
case 200: return "OK";
|
||||||
case STATUS_CREATED: return "CREATED";
|
case 201: return "CREATED";
|
||||||
case STATUS_ACCEPTED: return "ACCEPTED";
|
case 202: return "ACCEPTED";
|
||||||
case STATUS_NO_CONTENT: return "NO CONTENT";
|
case 204: return "NO CONTENT";
|
||||||
case STATUS_RESET_CONTENT: return "RESET CONTENT";
|
case 205: return "RESET CONTENT";
|
||||||
case STATUS_PARTIAL_CONTENT: return "PARTIAL CONTENT";
|
case 206: return "PARTIAL CONTENT";
|
||||||
case STATUS_MULTIPLE_CHOICES: return "MULTIPLE CHOICES";
|
|
||||||
case STATUS_MOVED_PERMANENTLY: return "MOVED PERMANENTLY";
|
case 300: return "MULTIPLE CHOICES";
|
||||||
case STATUS_MOVED_TEMPORARILY: return "MOVED TEMPORARILY";
|
case 301: return "MOVED PERMANENTLY";
|
||||||
case STATUS_NOT_MODIFIED: return "NOT MODIFIED";
|
case 302: return "MOVED TEMPORARILY";
|
||||||
case STATUS_BAD_REQUEST: return "BAD REQUEST";
|
case 304: return "NOT MODIFIED";
|
||||||
case STATUS_UNAUTHORIZED: return "UNAUTHORIZED";
|
|
||||||
case STATUS_FORBIDDEN: return "FORBIDDEN";
|
case 400: return "BAD REQUEST";
|
||||||
case STATUS_NOT_FOUND: return "NOT FOUND";
|
case 401: return "UNAUTHORIZED";
|
||||||
case STATUS_RANGE_NOT_SATISFIABLE: return "RANGE NOT SATISFIABLE";
|
case 403: return "FORBIDDEN";
|
||||||
case STATUS_INTERNAL_SERVER_ERROR: return "INTERNAL SERVER_ERROR";
|
case 404: return "NOT FOUND";
|
||||||
case STATUS_NOT_IMPLEMENTED: return "NOT IMPLEMENTED";
|
case 407: return "RANGE NOT SATISFIABLE";
|
||||||
case STATUS_BAD_GATEWAY: return "BAD GATEWAY";
|
|
||||||
case STATUS_SERVICE_NOT_AVAILABLE: return "SERVICE NOT AVAILABLE";
|
case 500: return "INTERNAL SERVER_ERROR";
|
||||||
case STATUS_GATEWAY_TIMEOUT: return "GATEWAY TIMEOUT";
|
case 501: return "NOT IMPLEMENTED";
|
||||||
case STATUS_VERSION_NOT_SUPPORTED: return "VERSION NOT SUPPORTED";
|
case 502: return "BAD GATEWAY";
|
||||||
|
case 503: return "SERVICE NOT AVAILABLE";
|
||||||
|
case 504: return "GATEWAY TIMEOUT";
|
||||||
|
case 505: return "VERSION NOT SUPPORTED";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
|
|
||||||
// == HTTP VERSION ============================================================
|
|
||||||
|
|
||||||
int httpVerNumber(http_version_t ver) {
|
int httpVerNumber(http_version_t ver) {
|
||||||
return (ver.major * 10) + ver.minor;
|
return (ver.major * 10) + ver.minor;
|
||||||
}
|
}
|
||||||
|
|
||||||
// == HTTP REQUEST ============================================================
|
http_req_t httpParseReq(arena_t *arena, strview_t request) {
|
||||||
|
http_req_t req = {0};
|
||||||
|
instream_t in = istrInitLen(request.buf, request.len);
|
||||||
|
|
||||||
http_request_t reqInit() {
|
strview_t method = strvTrim(istrGetView(&in, '/'));
|
||||||
http_request_t req = {0};
|
|
||||||
reqSetUri(&req, strvInit(""));
|
|
||||||
req.version = (http_version_t){1, 1};
|
|
||||||
return req;
|
|
||||||
}
|
|
||||||
|
|
||||||
http_request_t reqParse(const char *request) {
|
|
||||||
http_request_t req = {0};
|
|
||||||
str_istream_t in = istrInit(request);
|
|
||||||
|
|
||||||
// get data
|
|
||||||
|
|
||||||
strview_t method = strvTrim(istrGetview(&in, '/'));
|
|
||||||
istrSkip(&in, 1); // skip /
|
istrSkip(&in, 1); // skip /
|
||||||
strview_t page = strvTrim(istrGetview(&in, ' '));
|
req.url = strvTrim(istrGetView(&in, ' '));
|
||||||
strview_t http = strvTrim(istrGetview(&in, '\n'));
|
strview_t http = strvTrim(istrGetView(&in, '\n'));
|
||||||
|
|
||||||
istrSkip(&in, 1); // skip \n
|
istrSkip(&in, 1); // skip \n
|
||||||
|
|
||||||
_parseFields(&req.fields, &in);
|
req.headers = http__parse_headers(arena, &in);
|
||||||
|
|
||||||
strview_t body = strvTrim(istrGetviewLen(&in, 0, SIZE_MAX));
|
req.body = strvTrim(istrGetViewLen(&in, SIZE_MAX));
|
||||||
|
|
||||||
// parse data
|
strview_t methods[] = { strv("GET"), strv("POST"), strv("HEAD"), strv("PUT"), strv("DELETE") };
|
||||||
|
usize methods_count = arrlen(methods);
|
||||||
|
|
||||||
// -- method
|
for (usize i = 0; i < methods_count; ++i) {
|
||||||
const char *methods[] = { "GET", "POST", "HEAD", "PUT", "DELETE" };
|
if (strvEquals(method, methods[i])) {
|
||||||
const int methods_count = sizeof(methods) / sizeof(*methods);
|
req.method = (http_method_e)i;
|
||||||
|
break;
|
||||||
for (int i = 0; i < methods_count; ++i) {
|
|
||||||
if (strvCompare(method, strvInit(methods[i])) == 0) {
|
|
||||||
req.method = (reqtype_t)i;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- page
|
|
||||||
req.uri = strvCopy(page).buf;
|
|
||||||
|
|
||||||
// -- http
|
|
||||||
in = istrInitLen(http.buf, http.len);
|
in = istrInitLen(http.buf, http.len);
|
||||||
istrIgnoreAndSkip(&in, '/'); // skip HTTP/
|
istrIgnoreAndSkip(&in, '/'); // skip HTTP/
|
||||||
istrGetu8(&in, &req.version.major);
|
istrGetU8(&in, &req.version.major);
|
||||||
istrSkip(&in, 1); // skip .
|
istrSkip(&in, 1); // skip .
|
||||||
istrGetu8(&in, &req.version.minor);
|
istrGetU8(&in, &req.version.minor);
|
||||||
|
|
||||||
// -- body
|
|
||||||
req.body = strvCopy(body).buf;
|
|
||||||
|
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
void reqFree(http_request_t ctx) {
|
http_res_t httpParseRes(arena_t *arena, strview_t response) {
|
||||||
for (http_field_t *it = ctx.fields; it != vecEnd(ctx.fields); ++it) {
|
http_res_t res = {0};
|
||||||
free(it->key);
|
instream_t in = istrInitLen(response.buf, response.len);
|
||||||
free(it->value);
|
|
||||||
}
|
|
||||||
vecFree(ctx.fields);
|
|
||||||
free(ctx.uri);
|
|
||||||
free(ctx.body);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool reqHasField(http_request_t *ctx, const char *key) {
|
strview_t http = istrGetViewLen(&in, 5);
|
||||||
for(uint32 i = 0; i < vecLen(ctx->fields); ++i) {
|
if (!strvEquals(http, strv("HTTP"))) {
|
||||||
if(stricmp(ctx->fields[i].key, key) == 0) {
|
err("response doesn't start with 'HTTP', instead with %v", http);
|
||||||
return true;
|
return (http_res_t){0};
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void reqSetField(http_request_t *ctx, const char *key, const char *value) {
|
|
||||||
_setField(&ctx->fields, key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void reqSetUri(http_request_t *ctx, strview_t uri) {
|
|
||||||
if (strvIsEmpty(uri)) return;
|
|
||||||
free(ctx->uri);
|
|
||||||
if (uri.buf[0] == '/') {
|
|
||||||
strvRemovePrefix(uri, 1);
|
|
||||||
}
|
|
||||||
ctx->uri = strvCopy(uri).buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
str_ostream_t reqPrepare(http_request_t *ctx) {
|
|
||||||
str_ostream_t out = ostrInitLen(1024);
|
|
||||||
|
|
||||||
const char *method = NULL;
|
|
||||||
switch(ctx->method) {
|
|
||||||
case REQ_GET: method = "GET"; break;
|
|
||||||
case REQ_POST: method = "POST"; break;
|
|
||||||
case REQ_HEAD: method = "HEAD"; break;
|
|
||||||
case REQ_PUT: method = "PUT"; break;
|
|
||||||
case REQ_DELETE: method = "DELETE"; break;
|
|
||||||
default: err("unrecognized method: %d", method); goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
ostrPrintf(&out, "%s /%s HTTP/%hhu.%hhu\r\n",
|
|
||||||
method, ctx->uri, ctx->version.major, ctx->version.minor
|
|
||||||
);
|
|
||||||
|
|
||||||
for(uint32 i = 0; i < vecLen(ctx->fields); ++i) {
|
|
||||||
ostrPrintf(&out, "%s: %s\r\n", ctx->fields[i].key, ctx->fields[i].value);
|
|
||||||
}
|
|
||||||
|
|
||||||
ostrAppendview(&out, strvInit("\r\n"));
|
|
||||||
if(ctx->body) {
|
|
||||||
ostrAppendview(&out, strvInit(ctx->body));
|
|
||||||
}
|
|
||||||
|
|
||||||
error:
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t reqString(http_request_t *ctx) {
|
|
||||||
str_ostream_t out = reqPrepare(ctx);
|
|
||||||
return ostrAsStr(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
// == HTTP RESPONSE ===========================================================
|
|
||||||
|
|
||||||
http_response_t resParse(const char *data) {
|
|
||||||
http_response_t ctx = {0};
|
|
||||||
str_istream_t in = istrInit(data);
|
|
||||||
|
|
||||||
char hp[5];
|
|
||||||
istrGetstringBuf(&in, hp, 5);
|
|
||||||
if(stricmp(hp, "http") != 0) {
|
|
||||||
err("response doesn't start with 'HTTP', instead with %c%c%c%c", hp[0], hp[1], hp[2], hp[3]);
|
|
||||||
return ctx;
|
|
||||||
}
|
}
|
||||||
istrSkip(&in, 1); // skip /
|
istrSkip(&in, 1); // skip /
|
||||||
istrGetu8(&in, &ctx.version.major);
|
istrGetU8(&in, &res.version.major);
|
||||||
istrSkip(&in, 1); // skip .
|
istrSkip(&in, 1); // skip .
|
||||||
istrGetu8(&in, &ctx.version.minor);
|
istrGetU8(&in, &res.version.minor);
|
||||||
istrGeti32(&in, (int32*)&ctx.status_code);
|
istrGetI32(&in, (int32*)&res.status_code);
|
||||||
|
|
||||||
istrIgnore(&in, '\n');
|
istrIgnore(&in, '\n');
|
||||||
istrSkip(&in, 1); // skip \n
|
istrSkip(&in, 1); // skip \n
|
||||||
|
|
||||||
resParseFields(&ctx, &in);
|
res.headers = http__parse_headers(arena, &in);
|
||||||
|
|
||||||
const char *tran_encoding = resGetField(&ctx, "transfer-encoding");
|
strview_t encoding = httpGetHeader(res.headers, strv("transfer-encoding"));
|
||||||
if(tran_encoding == NULL || stricmp(tran_encoding, "chunked") != 0) {
|
if (!strvEquals(encoding, strv("chunked"))) {
|
||||||
strview_t body = istrGetviewLen(&in, 0, SIZE_MAX);
|
res.body = istrGetViewLen(&in, SIZE_MAX);
|
||||||
vecClear(ctx.body);
|
|
||||||
vecReserve(ctx.body, body.len);
|
|
||||||
memcpy(ctx.body, body.buf, body.len);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// fatal("chunked encoding not implemented yet");
|
err("chunked encoding not implemented yet! body ignored");
|
||||||
err("chunked encoding not implemented yet");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
void resFree(http_response_t ctx) {
|
str_t httpReqToStr(arena_t *arena, http_req_t *req) {
|
||||||
for (http_field_t *it = ctx.fields; it != vecEnd(ctx.fields); ++it) {
|
outstream_t out = ostrInit(arena);
|
||||||
free(it->key);
|
|
||||||
free(it->value);
|
const char *method = NULL;
|
||||||
|
switch (req->method) {
|
||||||
|
case HTTP_GET: method = "GET"; break;
|
||||||
|
case HTTP_POST: method = "POST"; break;
|
||||||
|
case HTTP_HEAD: method = "HEAD"; break;
|
||||||
|
case HTTP_PUT: method = "PUT"; break;
|
||||||
|
case HTTP_DELETE: method = "DELETE"; break;
|
||||||
|
default: err("unrecognised method: %d", method); return (str_t){0};
|
||||||
}
|
}
|
||||||
vecFree(ctx.fields);
|
|
||||||
vecFree(ctx.body);
|
ostrPrintf(
|
||||||
|
&out,
|
||||||
|
"%s /%v HTTP/%hhu.%hhu\r\n",
|
||||||
|
method, req->url, req->version.major, req->version.minor
|
||||||
|
);
|
||||||
|
|
||||||
|
http_header_t *h = req->headers;
|
||||||
|
while (h) {
|
||||||
|
ostrPrintf(&out, "%v: %v\r\n", h->key, h->value);
|
||||||
|
h = h->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
ostrPuts(&out, strv("\r\n"));
|
||||||
|
ostrPuts(&out, req->body);
|
||||||
|
|
||||||
|
return ostrAsStr(&out);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool resHasField(http_response_t *ctx, const char *key) {
|
str_t httpResToStr(arena_t *arena, http_res_t *res) {
|
||||||
for(uint32 i = 0; i < vecLen(ctx->fields); ++i) {
|
outstream_t out = ostrInit(arena);
|
||||||
if(stricmp(ctx->fields[i].key, key) == 0) {
|
|
||||||
|
ostrPrintf(
|
||||||
|
&out,
|
||||||
|
"HTTP/%hhu.%hhu %d %s\r\n",
|
||||||
|
res->version.major,
|
||||||
|
res->version.minor,
|
||||||
|
res->status_code,
|
||||||
|
httpGetStatusString(res->status_code)
|
||||||
|
);
|
||||||
|
ostrPuts(&out, strv("\r\n"));
|
||||||
|
ostrPuts(&out, res->body);
|
||||||
|
|
||||||
|
return ostrAsStr(&out);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool httpHasHeader(http_header_t *headers, strview_t key) {
|
||||||
|
http_header_t *h = headers;
|
||||||
|
while (h) {
|
||||||
|
if (strvEquals(h->key, key)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
h = h->next;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void resSetField(http_response_t *ctx, const char *key, const char *value) {
|
void httpSetHeader(http_header_t *headers, strview_t key, strview_t value) {
|
||||||
_setField(&ctx->fields, key, value);
|
http_header_t *h = headers;
|
||||||
|
while (h) {
|
||||||
|
if (strvEquals(h->key, key)) {
|
||||||
|
h->value = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
h = h->next;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *resGetField(http_response_t *ctx, const char *field) {
|
strview_t httpGetHeader(http_header_t *headers, strview_t key) {
|
||||||
for(uint32 i = 0; i < vecLen(ctx->fields); ++i) {
|
http_header_t *h = headers;
|
||||||
if(stricmp(ctx->fields[i].key, field) == 0) {
|
while (h) {
|
||||||
return ctx->fields[i].value;
|
if (strvEquals(h->key, key)) {
|
||||||
|
return h->value;
|
||||||
}
|
}
|
||||||
|
h = h->next;
|
||||||
}
|
}
|
||||||
return NULL;
|
return (strview_t){0};
|
||||||
}
|
}
|
||||||
|
|
||||||
void resParseFields(http_response_t *ctx, str_istream_t *in) {
|
str_t httpMakeUrlSafe(arena_t *arena, strview_t string) {
|
||||||
_parseFields(&ctx->fields, in);
|
strview_t chars = strv(" !\"#$%%&'()*+,/:;=?@[]");
|
||||||
}
|
usize final_len = string.len;
|
||||||
|
|
||||||
str_ostream_t resPrepare(http_response_t *ctx) {
|
// find final string length first
|
||||||
str_ostream_t out = ostrInitLen(1024);
|
for (usize i = 0; i < string.len; ++i) {
|
||||||
|
if (strvContains(chars, string.buf[i])) {
|
||||||
ostrPrintf(
|
final_len += 2;
|
||||||
&out, "HTTP/%hhu.%hhu %d %s\r\n",
|
}
|
||||||
ctx->version.major, ctx->version.minor,
|
}
|
||||||
ctx->status_code, httpGetStatusString(ctx->status_code)
|
|
||||||
);
|
str_t out = {
|
||||||
for (http_field_t *field = ctx->fields; field != vecEnd(ctx->fields); ++field) {
|
.buf = alloc(arena, char, final_len + 1),
|
||||||
ostrPrintf(&out, "%s: %s\r\n", field->key, field->value);
|
.len = final_len
|
||||||
|
};
|
||||||
|
usize cur = 0;
|
||||||
|
// substitute characters
|
||||||
|
for (usize i = 0; i < string.len; ++i) {
|
||||||
|
if (strvContains(chars, string.buf[i])) {
|
||||||
|
fmtBuffer(out.buf + cur, 4, "%%%X", string.buf[i]);
|
||||||
|
cur += 3;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
out.buf[cur++] = string.buf[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ostrPuts(&out, "\r\n");
|
|
||||||
ostrAppendview(&out, strvInitLen(ctx->body, vecLen(ctx->body)));
|
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
str_t resString(http_response_t *ctx) {
|
http_url_t httpSplitUrl(strview_t url) {
|
||||||
str_ostream_t out = resPrepare(ctx);
|
http_url_t out = {0};
|
||||||
return ostrAsStr(out);
|
|
||||||
|
if (strvStartsWithView(url, strv("https://"))) {
|
||||||
|
url = strvRemovePrefix(url, 8);
|
||||||
|
}
|
||||||
|
else if (strvStartsWithView(url, strv("http://"))) {
|
||||||
|
url = strvRemovePrefix(url, 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.host = strvSub(url, 0, strvFind(url, '/', 0));
|
||||||
|
out.uri = strvSub(url, out.host.len, SIZE_MAX);
|
||||||
|
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
// == HTTP CLIENT =============================================================
|
http_res_t httpRequest(http_request_desc_t *request) {
|
||||||
|
usize arena_begin = arenaTell(request->arena);
|
||||||
|
|
||||||
http_client_t hcliInit() {
|
http_req_t req = {
|
||||||
return (http_client_t) {
|
.version = (http_version_t){ 1, 1 },
|
||||||
.port = 80,
|
.url = request->url,
|
||||||
|
.body = request->body,
|
||||||
|
.method = request->request_type,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
void hcliFree(http_client_t ctx) {
|
http_header_t *h = NULL;
|
||||||
strFree(ctx.host_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
void hcliSetHost(http_client_t *ctx, strview_t hostname) {
|
for (int i = 0; i < request->header_count; ++i) {
|
||||||
// if the hostname starts with http:// (case insensitive)
|
http_header_t *header = request->headers + i;
|
||||||
if(strvICompare(strvSub(hostname, 0, 7), strvInit("http://")) == 0) {
|
header->next = h;
|
||||||
ctx->host_name = strvCopy(strvSub(hostname, 7, SIZE_MAX));
|
h = header;
|
||||||
}
|
|
||||||
else if(strvICompare(strvSub(hostname, 0, 8), strvInit("https://")) == 0) {
|
|
||||||
err("HTTPS protocol not yet supported");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// undefined protocol, use HTTP
|
|
||||||
ctx->host_name = strvCopy(hostname);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *req) {
|
|
||||||
if (strBack(ctx->host_name) == '/') {
|
|
||||||
ctx->host_name.buf[--ctx->host_name.len] = '\0';
|
|
||||||
}
|
|
||||||
if(!reqHasField(req, "Host")) {
|
|
||||||
reqSetField(req, "Host", ctx->host_name.buf);
|
|
||||||
}
|
|
||||||
if(!reqHasField(req, "Content-Length")) {
|
|
||||||
if(req->body) {
|
|
||||||
str_ostream_t out = ostrInitLen(20);
|
|
||||||
ostrAppendu64(&out, strlen(req->body));
|
|
||||||
reqSetField(req, "Content-Length", out.buf);
|
|
||||||
ostrFree(out);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
reqSetField(req, "Content-Length", "0");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(req->method == REQ_POST && !reqHasField(req, "Content-Type")) {
|
|
||||||
reqSetField(req, "Content-Type", "application/x-www-form-urlencoded");
|
|
||||||
}
|
|
||||||
if(httpVerNumber(req->version) >= 11 && !reqHasField(req, "Connection")) {
|
|
||||||
reqSetField(req, "Connection", "close");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
http_response_t res = {0};
|
req.headers = h;
|
||||||
str_t req_str = strInit();
|
|
||||||
str_ostream_t received = ostrInitLen(1024);
|
|
||||||
|
|
||||||
if(!skInit()) {
|
http_url_t url = httpSplitUrl(req.url);
|
||||||
err("couldn't initialize sockets %s", skGetErrorString());
|
|
||||||
goto skopen_error;
|
if (strvEndsWith(url.host, '/')) {
|
||||||
|
url.host = strvRemoveSuffix(url.host, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx->socket = skOpen(SOCK_TCP);
|
if (!httpHasHeader(req.headers, strv("Host"))) {
|
||||||
if(ctx->socket == INVALID_SOCKET) {
|
httpSetHeader(req.headers, strv("Host"), url.host);
|
||||||
err("couldn't open socket %s", skGetErrorString());
|
}
|
||||||
|
if (!httpHasHeader(req.headers, strv("Content-Length"))) {
|
||||||
|
char tmp[16] = {0};
|
||||||
|
fmtBuffer(tmp, arrlen(tmp), "%zu", req.body.len);
|
||||||
|
httpSetHeader(req.headers, strv("Content-Length"), strv(tmp));
|
||||||
|
}
|
||||||
|
if (req.method == HTTP_POST && !httpHasHeader(req.headers, strv("Content-Type"))) {
|
||||||
|
httpSetHeader(req.headers, strv("Content-Type"), strv("application/x-www-form-urlencoded"));
|
||||||
|
}
|
||||||
|
if (!httpHasHeader(req.headers, strv("Connection"))) {
|
||||||
|
httpSetHeader(req.headers, strv("Connection"), strv("close"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!skInit()) {
|
||||||
|
err("couldn't initialise sockets: %s", skGetErrorString());
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(skConnect(ctx->socket, ctx->host_name.buf, ctx->port)) {
|
socket_t sock = skOpen(SOCK_TCP);
|
||||||
req_str = reqString(req);
|
if (!skIsValid(sock)) {
|
||||||
if(req_str.len == 0) {
|
err("couldn't open socket: %s", skGetErrorString());
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
char hostname[64] = {0};
|
||||||
|
assert(url.host.len < arrlen(hostname));
|
||||||
|
memcpy(hostname, url.host.buf, url.host.len);
|
||||||
|
|
||||||
|
const int DEFAULT_HTTP_PORT = 80;
|
||||||
|
if (!skConnect(sock, hostname, DEFAULT_HTTP_PORT)) {
|
||||||
|
err("Couldn't connect to host %s: %s", hostname, skGetErrorString());
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
str_t reqstr = httpReqToStr(request->arena, &req);
|
||||||
|
if (strIsEmpty(reqstr)) {
|
||||||
err("couldn't get string from request");
|
err("couldn't get string from request");
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(skSend(ctx->socket, req_str.buf, (int)req_str.len) == SOCKET_ERROR) {
|
if (skSend(sock, reqstr.buf, (int)reqstr.len) == -1) {
|
||||||
err("couldn't send request to socket: %s", skGetErrorString());
|
err("couldn't send request to socket: %s", skGetErrorString());
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
char buffer[1024];
|
outstream_t response = ostrInit(request->arena);
|
||||||
|
char buffer[4096];
|
||||||
int read = 0;
|
int read = 0;
|
||||||
do {
|
do {
|
||||||
read = skReceive(ctx->socket, buffer, sizeof(buffer));
|
read = skReceive(sock, buffer, arrlen(buffer));
|
||||||
if(read == -1) {
|
if (read == -1) {
|
||||||
err("couldn't get the data from the server: %s", skGetErrorString());
|
err("couldn't get the data from the server: %s", skGetErrorString());
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
ostrAppendview(&received, strvInitLen(buffer, read));
|
ostrPuts(&response, strv(buffer, read));
|
||||||
} while(read != 0);
|
} while (read != 0);
|
||||||
|
|
||||||
// if the data received is not null terminated
|
if (!skClose(sock)) {
|
||||||
if(*(received.buf + received.len) != '\0') {
|
err("couldn't close socket: %s", skGetErrorString());
|
||||||
ostrPutc(&received, '\0');
|
|
||||||
received.len--;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res = resParse(received.buf);
|
if (!skCleanup()) {
|
||||||
}
|
err("couldn't clean up sockets: %s", skGetErrorString());
|
||||||
else {
|
|
||||||
err("Couldn't connect to host %s -> %s", ctx->host_name, skGetErrorString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!skClose(ctx->socket)) {
|
return httpParseRes(request->arena, ostrAsView(&response));
|
||||||
err("Couldn't close socket");
|
|
||||||
}
|
|
||||||
|
|
||||||
error:
|
error:
|
||||||
if(!skCleanup()) {
|
arenaRewind(request->arena, arena_begin);
|
||||||
err("couldn't clean up sockets %s", skGetErrorString());
|
skCleanup();
|
||||||
}
|
return (http_res_t){0};
|
||||||
skopen_error:
|
|
||||||
strFree(req_str);
|
|
||||||
ostrFree(received);
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
http_response_t httpGet(strview_t hostname, strview_t uri) {
|
#if COLLA_WIN
|
||||||
http_request_t request = reqInit();
|
|
||||||
request.method = REQ_GET;
|
|
||||||
reqSetUri(&request, uri);
|
|
||||||
|
|
||||||
http_client_t client = hcliInit();
|
buffer_t httpsRequest(http_request_desc_t *req) {
|
||||||
hcliSetHost(&client, hostname);
|
HINTERNET internet = InternetOpen(
|
||||||
|
TEXT("COLLA"),
|
||||||
http_response_t res = hcliSendRequest(&client, &request);
|
INTERNET_OPEN_TYPE_PRECONFIG,
|
||||||
|
NULL,
|
||||||
reqFree(request);
|
NULL,
|
||||||
hcliFree(client);
|
0
|
||||||
|
);
|
||||||
return res;
|
if (!internet) {
|
||||||
}
|
fatal("call to InternetOpen failed: %u", GetLastError());
|
||||||
|
|
||||||
url_split_t urlSplit(strview_t uri) {
|
|
||||||
url_split_t out = {0};
|
|
||||||
|
|
||||||
if (strvStartsWithView(uri, strvInit("https://"))) {
|
|
||||||
uri = strvRemovePrefix(uri, 8);
|
|
||||||
}
|
|
||||||
else if (strvStartsWithView(uri, strvInit("http://"))) {
|
|
||||||
uri = strvRemovePrefix(uri, 7);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
out.host = strvSub(uri, 0, strvFind(uri, '/', 0));
|
http_url_t split = httpSplitUrl(req->url);
|
||||||
out.uri = strvSub(uri, out.host.len, SIZE_MAX);
|
strview_t server = split.host;
|
||||||
return out;
|
strview_t page = split.uri;
|
||||||
|
|
||||||
|
if (strvStartsWithView(server, strv("http://"))) {
|
||||||
|
server = strvRemovePrefix(server, 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strvStartsWithView(server, strv("https://"))) {
|
||||||
|
server = strvRemovePrefix(server, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
arena_t scratch = *req->arena;
|
||||||
|
const TCHAR *tserver = strvToTChar(&scratch, server);
|
||||||
|
const TCHAR *tpage = strvToTChar(&scratch, page);
|
||||||
|
|
||||||
|
HINTERNET connection = InternetConnect(
|
||||||
|
internet,
|
||||||
|
tserver,
|
||||||
|
INTERNET_DEFAULT_HTTPS_PORT,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
INTERNET_SERVICE_HTTP,
|
||||||
|
0,
|
||||||
|
(DWORD_PTR)NULL // userdata
|
||||||
|
);
|
||||||
|
if (!connection) {
|
||||||
|
fatal("call to InternetConnect failed: %u", GetLastError());
|
||||||
|
}
|
||||||
|
|
||||||
|
const TCHAR *accepted_types[] = { TEXT("*/*"), NULL };
|
||||||
|
|
||||||
|
HINTERNET request = HttpOpenRequest(
|
||||||
|
connection,
|
||||||
|
https__get_method_str(req->request_type),
|
||||||
|
tpage,
|
||||||
|
TEXT("HTTP/1.1"),
|
||||||
|
NULL,
|
||||||
|
accepted_types,
|
||||||
|
INTERNET_FLAG_SECURE,
|
||||||
|
(DWORD_PTR)NULL // userdata
|
||||||
|
);
|
||||||
|
if (!request) {
|
||||||
|
fatal("call to HttpOpenRequest failed: %u", GetLastError());
|
||||||
|
}
|
||||||
|
|
||||||
|
outstream_t header = ostrInit(&scratch);
|
||||||
|
|
||||||
|
for (int i = 0; i < req->header_count; ++i) {
|
||||||
|
http_header_t *h = &req->headers[i];
|
||||||
|
ostrClear(&header);
|
||||||
|
ostrPrintf(
|
||||||
|
&header,
|
||||||
|
"%.*s: %.*s\r\n",
|
||||||
|
h->key.len, h->key.buf,
|
||||||
|
h->value.len, h->value.buf
|
||||||
|
);
|
||||||
|
str_t header_str = ostrAsStr(&header);
|
||||||
|
HttpAddRequestHeadersA(
|
||||||
|
request,
|
||||||
|
header_str.buf,
|
||||||
|
header_str.len,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL request_sent = HttpSendRequest(
|
||||||
|
request,
|
||||||
|
NULL,
|
||||||
|
0,
|
||||||
|
(void *)req->body.buf,
|
||||||
|
req->body.len
|
||||||
|
);
|
||||||
|
if (!request_sent) {
|
||||||
|
fatal("call to HttpSendRequest failed: %u", GetLastError());
|
||||||
|
}
|
||||||
|
|
||||||
|
outstream_t out = ostrInit(req->arena);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
DWORD bytes_read = 0;
|
||||||
|
char buffer[4096];
|
||||||
|
BOOL read = InternetReadFile(
|
||||||
|
request,
|
||||||
|
buffer,
|
||||||
|
sizeof(buffer),
|
||||||
|
&bytes_read
|
||||||
|
);
|
||||||
|
if (!read || bytes_read == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ostrPuts(&out, strv(buffer, bytes_read));
|
||||||
|
}
|
||||||
|
|
||||||
|
InternetCloseHandle(request);
|
||||||
|
InternetCloseHandle(connection);
|
||||||
|
InternetCloseHandle(internet);
|
||||||
|
|
||||||
|
str_t outstr = ostrAsStr(&out);
|
||||||
|
|
||||||
|
return (buffer_t) {
|
||||||
|
.data = (uint8 *)outstr.buf,
|
||||||
|
.len = outstr.len
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const TCHAR *https__get_method_str(http_method_e method) {
|
||||||
|
switch (method) {
|
||||||
|
case HTTP_GET: return TEXT("GET");
|
||||||
|
case HTTP_POST: return TEXT("POST");
|
||||||
|
case HTTP_HEAD: return TEXT("HEAD");
|
||||||
|
case HTTP_PUT: return TEXT("PUT");
|
||||||
|
case HTTP_DELETE: return TEXT("DELETE");
|
||||||
|
}
|
||||||
|
// default GET
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "warnings/colla_warn_end.h"
|
||||||
|
|
|
||||||
161
colla/http.h
161
colla/http.h
|
|
@ -1,57 +1,20 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include "collatypes.h"
|
#include "collatypes.h"
|
||||||
#include "str.h"
|
#include "str.h"
|
||||||
#include "strstream.h"
|
|
||||||
#include "socket.h"
|
typedef struct arena_t arena_t;
|
||||||
|
typedef uintptr_t socket_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
REQ_GET,
|
HTTP_GET,
|
||||||
REQ_POST,
|
HTTP_POST,
|
||||||
REQ_HEAD,
|
HTTP_HEAD,
|
||||||
REQ_PUT,
|
HTTP_PUT,
|
||||||
REQ_DELETE
|
HTTP_DELETE
|
||||||
} reqtype_t;
|
} http_method_e;
|
||||||
|
|
||||||
typedef enum {
|
const char *httpGetStatusString(int status);
|
||||||
// 2xx: success
|
|
||||||
STATUS_OK = 200,
|
|
||||||
STATUS_CREATED = 201,
|
|
||||||
STATUS_ACCEPTED = 202,
|
|
||||||
STATUS_NO_CONTENT = 204,
|
|
||||||
STATUS_RESET_CONTENT = 205,
|
|
||||||
STATUS_PARTIAL_CONTENT = 206,
|
|
||||||
|
|
||||||
// 3xx: redirection
|
|
||||||
STATUS_MULTIPLE_CHOICES = 300,
|
|
||||||
STATUS_MOVED_PERMANENTLY = 301,
|
|
||||||
STATUS_MOVED_TEMPORARILY = 302,
|
|
||||||
STATUS_NOT_MODIFIED = 304,
|
|
||||||
|
|
||||||
// 4xx: client error
|
|
||||||
STATUS_BAD_REQUEST = 400,
|
|
||||||
STATUS_UNAUTHORIZED = 401,
|
|
||||||
STATUS_FORBIDDEN = 403,
|
|
||||||
STATUS_NOT_FOUND = 404,
|
|
||||||
STATUS_RANGE_NOT_SATISFIABLE = 407,
|
|
||||||
|
|
||||||
// 5xx: server error
|
|
||||||
STATUS_INTERNAL_SERVER_ERROR = 500,
|
|
||||||
STATUS_NOT_IMPLEMENTED = 501,
|
|
||||||
STATUS_BAD_GATEWAY = 502,
|
|
||||||
STATUS_SERVICE_NOT_AVAILABLE = 503,
|
|
||||||
STATUS_GATEWAY_TIMEOUT = 504,
|
|
||||||
STATUS_VERSION_NOT_SUPPORTED = 505,
|
|
||||||
} resstatus_t;
|
|
||||||
|
|
||||||
const char *httpGetStatusString(resstatus_t status);
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8 major;
|
uint8 major;
|
||||||
|
|
@ -61,83 +24,59 @@ typedef struct {
|
||||||
// translates a http_version_t to a single readable number (e.g. 1.1 -> 11, 1.0 -> 10, etc)
|
// translates a http_version_t to a single readable number (e.g. 1.1 -> 11, 1.0 -> 10, etc)
|
||||||
int httpVerNumber(http_version_t ver);
|
int httpVerNumber(http_version_t ver);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct http_header_t {
|
||||||
char *key;
|
strview_t key;
|
||||||
char *value;
|
strview_t value;
|
||||||
} http_field_t;
|
struct http_header_t *next;
|
||||||
|
} http_header_t;
|
||||||
#include "vec.h"
|
|
||||||
|
|
||||||
// == HTTP REQUEST ============================================================
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
reqtype_t method;
|
http_method_e method;
|
||||||
http_version_t version;
|
http_version_t version;
|
||||||
vec(http_field_t) fields;
|
http_header_t *headers;
|
||||||
char *uri;
|
strview_t url;
|
||||||
char *body;
|
strview_t body;
|
||||||
} http_request_t;
|
} http_req_t;
|
||||||
|
|
||||||
http_request_t reqInit(void);
|
|
||||||
http_request_t reqParse(const char *request);
|
|
||||||
void reqFree(http_request_t ctx);
|
|
||||||
|
|
||||||
bool reqHasField(http_request_t *ctx, const char *key);
|
|
||||||
|
|
||||||
void reqSetField(http_request_t *ctx, const char *key, const char *value);
|
|
||||||
void reqSetUri(http_request_t *ctx, strview_t uri);
|
|
||||||
|
|
||||||
str_ostream_t reqPrepare(http_request_t *ctx);
|
|
||||||
str_t reqString(http_request_t *ctx);
|
|
||||||
|
|
||||||
// == HTTP RESPONSE ===========================================================
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
resstatus_t status_code;
|
int status_code;
|
||||||
vec(http_field_t) fields;
|
|
||||||
http_version_t version;
|
http_version_t version;
|
||||||
vec(uint8) body;
|
http_header_t *headers;
|
||||||
} http_response_t;
|
strview_t body;
|
||||||
|
} http_res_t;
|
||||||
|
|
||||||
http_response_t resParse(const char *data);
|
// strview_t request needs to be valid for http_req_t to be valid!
|
||||||
void resFree(http_response_t ctx);
|
http_req_t httpParseReq(arena_t *arena, strview_t request);
|
||||||
|
http_res_t httpParseRes(arena_t *arena, strview_t response);
|
||||||
|
|
||||||
bool resHasField(http_response_t *ctx, const char *key);
|
str_t httpReqToStr(arena_t *arena, http_req_t *req);
|
||||||
void resSetField(http_response_t *ctx, const char *key, const char *value);
|
str_t httpResToStr(arena_t *arena, http_res_t *res);
|
||||||
const char *resGetField(http_response_t *ctx, const char *field);
|
|
||||||
|
|
||||||
// void resParse(http_response_t *ctx, const char *data);
|
bool httpHasHeader(http_header_t *headers, strview_t key);
|
||||||
void resParseFields(http_response_t *ctx, str_istream_t *in);
|
void httpSetHeader(http_header_t *headers, strview_t key, strview_t value);
|
||||||
str_ostream_t resPrepare(http_response_t *ctx);
|
strview_t httpGetHeader(http_header_t *headers, strview_t key);
|
||||||
str_t resString(http_response_t *ctx);
|
|
||||||
|
|
||||||
// == HTTP CLIENT =============================================================
|
str_t httpMakeUrlSafe(arena_t *arena, strview_t string);
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
str_t host_name;
|
|
||||||
uint16 port;
|
|
||||||
socket_t socket;
|
|
||||||
} http_client_t;
|
|
||||||
|
|
||||||
http_client_t hcliInit(void);
|
|
||||||
void hcliFree(http_client_t ctx);
|
|
||||||
|
|
||||||
void hcliSetHost(http_client_t *ctx, strview_t hostname);
|
|
||||||
http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *request);
|
|
||||||
|
|
||||||
// == HTTP ====================================================================
|
|
||||||
|
|
||||||
http_response_t httpGet(strview_t hostname, strview_t uri);
|
|
||||||
|
|
||||||
// == URL =====================================================================
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
strview_t host;
|
strview_t host;
|
||||||
strview_t uri;
|
strview_t uri;
|
||||||
} url_split_t;
|
} http_url_t;
|
||||||
|
|
||||||
url_split_t urlSplit(strview_t uri);
|
http_url_t httpSplitUrl(strview_t url);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
typedef struct {
|
||||||
} // extern "C"
|
arena_t *arena;
|
||||||
#endif
|
strview_t url;
|
||||||
|
http_method_e request_type;
|
||||||
|
http_header_t *headers;
|
||||||
|
int header_count;
|
||||||
|
strview_t body;
|
||||||
|
} http_request_desc_t;
|
||||||
|
|
||||||
|
// arena_t *arena, strview_t url, [ http_header_t *headers, int header_count, strview_t body ]
|
||||||
|
#define httpGet(arena, url, ...) httpRequest(&(http_request_desc_t){ arena, url, .request_type = HTTP_GET, __VA_ARGS__ })
|
||||||
|
#define httpsGet(arena, url, ...) httpsRequest(&(http_request_desc_t){ arena, url, .request_type = HTTP_GET, __VA_ARGS__ })
|
||||||
|
|
||||||
|
http_res_t httpRequest(http_request_desc_t *request);
|
||||||
|
buffer_t httpsRequest(http_request_desc_t *request);
|
||||||
|
|
|
||||||
524
colla/ini.c
524
colla/ini.c
|
|
@ -1,347 +1,267 @@
|
||||||
#include "ini.h"
|
#include "ini.h"
|
||||||
|
|
||||||
|
#include "warnings/colla_warn_beg.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
#include "strstream.h"
|
#include "strstream.h"
|
||||||
#include "file.h"
|
|
||||||
#include "tracelog.h"
|
|
||||||
|
|
||||||
// == INI READER ========================================================================
|
static void ini__parse(arena_t *arena, ini_t *ini, const iniopts_t *options);
|
||||||
|
|
||||||
static const iniopts_t default_opts = {
|
ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options) {
|
||||||
.key_value_divider = '='
|
file_t fp = fileOpen(*arena, filename, FILE_READ);
|
||||||
};
|
ini_t out = iniParseFile(arena, fp, options);
|
||||||
|
fileClose(fp);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
static iniopts_t setDefaultOptions(const iniopts_t *options);
|
ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options) {
|
||||||
static initable_t *findTable(ini_t *ctx, strview_t name);
|
str_t data = fileReadWholeStrFP(arena, file);
|
||||||
static inivalue_t *findValue(vec(inivalue_t) values, strview_t key);
|
return iniParseStr(arena, strv(data), options);
|
||||||
static void addTable(ini_t *ctx, str_istream_t *in, const iniopts_t *options);
|
}
|
||||||
static void addValue(initable_t *table, str_istream_t *in, const iniopts_t *options);
|
|
||||||
|
|
||||||
void _iniParseInternal(ini_t *ini, const iniopts_t *options) {
|
ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options) {
|
||||||
// add root table
|
ini_t out = {
|
||||||
vecAppend(ini->tables, (initable_t){0});
|
.text = str,
|
||||||
str_istream_t in = istrInitLen(ini->text.buf, ini->text.len);
|
.tables = NULL,
|
||||||
istrSkipWhitespace(&in);
|
};
|
||||||
while (!istrIsFinished(in)) {
|
ini__parse(arena, &out, options);
|
||||||
switch(*in.cur) {
|
return out;
|
||||||
case '[':
|
}
|
||||||
addTable(ini, &in, options);
|
|
||||||
break;
|
initable_t *iniGetTable(ini_t *ctx, strview_t name) {
|
||||||
case '#': case ';':
|
initable_t *t = ctx ? ctx->tables : NULL;
|
||||||
istrIgnore(&in, '\n');
|
while (t) {
|
||||||
break;
|
if (strvEquals(t->name, name)) {
|
||||||
default:
|
return t;
|
||||||
addValue(&ini->tables[0], &in, options);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
istrSkipWhitespace(&in);
|
t = t->next;
|
||||||
}
|
}
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ini_t iniParse(const char *filename, const iniopts_t *options) {
|
inivalue_t *iniGet(initable_t *ctx, strview_t key) {
|
||||||
ini_t ini = { .text = fileReadWholeText(filename) };
|
inivalue_t *v = ctx ? ctx->values : NULL;
|
||||||
if (strIsEmpty(ini.text)) return ini;
|
while (v) {
|
||||||
iniopts_t opts = setDefaultOptions(options);
|
if (strvEquals(v->key, key)) {
|
||||||
_iniParseInternal(&ini, &opts);
|
return v;
|
||||||
return ini;
|
|
||||||
}
|
|
||||||
|
|
||||||
ini_t iniParseString(const char *inistr, const iniopts_t *options) {
|
|
||||||
ini_t ini = { .text = strFromStr(inistr) };
|
|
||||||
if (!options) options = &default_opts;
|
|
||||||
_iniParseInternal(&ini, options);
|
|
||||||
return ini;
|
|
||||||
}
|
|
||||||
|
|
||||||
void iniFree(ini_t ctx) {
|
|
||||||
strFree(ctx.text);
|
|
||||||
for (uint32 i = 0; i < vecLen(ctx.tables); ++i) {
|
|
||||||
vecFree(ctx.tables[i].values);
|
|
||||||
}
|
}
|
||||||
vecFree(ctx.tables);
|
v = v->next;
|
||||||
}
|
|
||||||
|
|
||||||
initable_t *iniGetTable(ini_t *ctx, const char *name) {
|
|
||||||
if (!name) {
|
|
||||||
return &ctx->tables[0];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return findTable(ctx, strvInit(name));
|
|
||||||
}
|
}
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
inivalue_t *iniGet(initable_t *ctx, const char *key) {
|
iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim) {
|
||||||
return ctx ? findValue(ctx->values, strvInit(key)) : NULL;
|
strview_t v = value ? value->value : (strview_t){0};
|
||||||
}
|
|
||||||
|
|
||||||
vec(strview_t) iniAsArray(const inivalue_t *value, char delim) {
|
|
||||||
if (!value) return NULL;
|
|
||||||
if (!delim) delim = ' ';
|
if (!delim) delim = ' ';
|
||||||
|
|
||||||
vec(strview_t) out = NULL;
|
strview_t *beg = (strview_t *)arena->current;
|
||||||
strview_t v = value->value;
|
usize count = 0;
|
||||||
|
|
||||||
usize start = 0;
|
usize start = 0;
|
||||||
for (usize i = 0; i < v.len; ++i) {
|
for (usize i = 0; i < v.len; ++i) {
|
||||||
if (v.buf[i] == delim) {
|
if (v.buf[i] == delim) {
|
||||||
strview_t arr_val = strvTrim(strvSub(v, start, i));
|
strview_t arrval = strvTrim(strvSub(v, start, i));
|
||||||
if (!strvIsEmpty(arr_val)) vecAppend(out, arr_val);
|
if (!strvIsEmpty(arrval)) {
|
||||||
|
strview_t *newval = alloc(arena, strview_t);
|
||||||
|
*newval = arrval;
|
||||||
|
++count;
|
||||||
|
}
|
||||||
start = i + 1;
|
start = i + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
strview_t last = strvTrim(strvSub(v, start, SIZE_MAX));
|
strview_t last = strvTrim(strvSub(v, start, SIZE_MAX));
|
||||||
if (!strvIsEmpty(last)) vecAppend(out, last);
|
if (!strvIsEmpty(last)) {
|
||||||
|
strview_t *newval = alloc(arena, strview_t);
|
||||||
|
*newval = last;
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (iniarray_t){
|
||||||
|
.values = beg,
|
||||||
|
.count = count,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64 iniAsUInt(inivalue_t *value) {
|
||||||
|
strview_t v = value ? value->value : (strview_t){0};
|
||||||
|
instream_t in = istrInitLen(v.buf, v.len);
|
||||||
|
uint64 out = 0;
|
||||||
|
if (!istrGetU64(&in, &out)) {
|
||||||
|
out = 0;
|
||||||
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
vec(strview_t) iniAsArrayU8(const inivalue_t *value, const char *delim) {
|
int64 iniAsInt(inivalue_t *value) {
|
||||||
if (!value || !delim) return NULL;
|
strview_t v = value ? value->value : (strview_t){0};
|
||||||
|
instream_t in = istrInitLen(v.buf, v.len);
|
||||||
rune cpdelim = utf8Decode(&delim);
|
int64 out = 0;
|
||||||
vec(strview_t) out = NULL;
|
if (!istrGetI64(&in, &out)) {
|
||||||
strview_t v = value->value;
|
out = 0;
|
||||||
|
|
||||||
const char *start = v.buf;
|
|
||||||
const char *buf = v.buf;
|
|
||||||
const char *prevbuf = buf;
|
|
||||||
|
|
||||||
for(rune cp = utf8Decode(&buf);
|
|
||||||
buf != (v.buf + v.len);
|
|
||||||
cp = utf8Decode(&buf)
|
|
||||||
) {
|
|
||||||
if (cp == cpdelim) {
|
|
||||||
usize start_pos = start - v.buf;
|
|
||||||
usize end_pos = prevbuf - v.buf;
|
|
||||||
strview_t arr_val = strvTrim(strvSub(v, start_pos, end_pos));
|
|
||||||
if (!strvIsEmpty(arr_val)) vecAppend(out, arr_val);
|
|
||||||
// buf has already gone to the next codepoint, skipping the delimiter
|
|
||||||
start = buf;
|
|
||||||
}
|
}
|
||||||
prevbuf = buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
strview_t last = strvTrim(strvSub(v, start - v.buf, SIZE_MAX));
|
|
||||||
if (!strvIsEmpty(last)) vecAppend(out, last);
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64 iniAsUInt(const inivalue_t *value) {
|
double iniAsNum(inivalue_t *value) {
|
||||||
if (!value) return 0;
|
strview_t v = value ? value->value : (strview_t){0};
|
||||||
str_istream_t in = istrInitLen(value->value.buf, value->value.len);
|
instream_t in = istrInitLen(v.buf, v.len);
|
||||||
uint64 val = 0;
|
double out = 0;
|
||||||
if (!istrGetu64(&in, &val)) val = 0;
|
if (!istrGetDouble(&in, &out)) {
|
||||||
return val;
|
out = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64 iniAsInt(const inivalue_t *value) {
|
|
||||||
if (!value) return 0;
|
|
||||||
str_istream_t in = istrInitLen(value->value.buf, value->value.len);
|
|
||||||
int64 val = 0;
|
|
||||||
if (!istrGeti64(&in, &val)) val = 0;
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
double iniAsNum(const inivalue_t *value) {
|
|
||||||
if (!value) return 0.f;
|
|
||||||
str_istream_t in = istrInitLen(value->value.buf, value->value.len);
|
|
||||||
double val = 0;
|
|
||||||
if (!istrGetdouble(&in, &val)) val = 0;
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool iniAsBool(const inivalue_t *value) {
|
|
||||||
if (!value) return false;
|
|
||||||
return strvCompare(value->value, strvInit("true")) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// == INI WRITER ========================================================================
|
|
||||||
|
|
||||||
#include "strstream.h"
|
|
||||||
#include "file.h"
|
|
||||||
|
|
||||||
static const winiopts_t default_wopts = {0};
|
|
||||||
|
|
||||||
iniwriter_t winiInit() {
|
|
||||||
iniwriter_t out = {0};
|
|
||||||
vecAppend(out.tables, (winitable_t){0});
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
void winiFree(iniwriter_t ctx) {
|
bool iniAsBool(inivalue_t *value) {
|
||||||
for (winitable_t *tab = ctx.tables; tab != vecEnd(ctx.tables); ++tab) {
|
strview_t v = value ? value->value : (strview_t){0};
|
||||||
strFree(tab->key);
|
instream_t in = istrInitLen(v.buf, v.len);
|
||||||
for (winivalue_t *val = tab->values; val != vecEnd(tab->values); ++val) {
|
bool out = 0;
|
||||||
strFree(val->key);
|
if (!istrGetBool(&in, &out)) {
|
||||||
strFree(val->value);
|
out = false;
|
||||||
}
|
}
|
||||||
vecFree(tab->values);
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// == PRIVATE FUNCTIONS ==============================================================================
|
||||||
|
|
||||||
|
#define INIPUSH(head, tail, val) \
|
||||||
|
do { \
|
||||||
|
if (!head) { \
|
||||||
|
head = val; \
|
||||||
|
tail = val; \
|
||||||
|
} \
|
||||||
|
else { \
|
||||||
|
tail->next = val; \
|
||||||
|
val = tail; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
static iniopts_t ini__get_options(const iniopts_t *options) {
|
||||||
|
iniopts_t out = {
|
||||||
|
.key_value_divider = '=',
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SETOPT(v) out.v = options->v ? options->v : out.v
|
||||||
|
|
||||||
|
if (options) {
|
||||||
|
SETOPT(key_value_divider);
|
||||||
|
SETOPT(merge_duplicate_keys);
|
||||||
|
SETOPT(merge_duplicate_tables);
|
||||||
}
|
}
|
||||||
vecFree(ctx.tables);
|
|
||||||
|
#undef SETOPT
|
||||||
|
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
str_t winiToString(iniwriter_t ctx, const winiopts_t *options) {
|
static void ini__add_value(arena_t *arena, initable_t *table, instream_t *in, iniopts_t *opts) {
|
||||||
if (!options) options = &default_wopts;
|
assert(table);
|
||||||
|
|
||||||
str_ostream_t out = ostrInitLen(1024 * 20);
|
strview_t key = strvTrim(istrGetView(in, opts->key_value_divider));
|
||||||
if (!options->no_discalimer) ostrPuts(&out, "# auto-generated by colla's ini.h, do not modify!\n");
|
|
||||||
// add root values
|
|
||||||
winitable_t *root = &ctx.tables[0];
|
|
||||||
for (winivalue_t *val = root->values; val != vecEnd(root->values); ++val) {
|
|
||||||
ostrPrintf(&out, "%s = %s\n", val->key.buf, val->value.buf);
|
|
||||||
}
|
|
||||||
if (root->values) ostrPuts(&out, "\n");
|
|
||||||
// add each table
|
|
||||||
for (usize i = 1; i < vecLen(ctx.tables); ++i) {
|
|
||||||
winitable_t *tab = &ctx.tables[i];
|
|
||||||
ostrPrintf(&out, "[%s]\n", tab->key.buf);
|
|
||||||
for (winivalue_t *val = tab->values; val != vecEnd(tab->values); ++val) {
|
|
||||||
ostrPrintf(&out, "%s = %s\n", val->key.buf, val->value.buf);
|
|
||||||
}
|
|
||||||
if ((i + 1) < vecLen(ctx.tables)) ostrPuts(&out, "\n");
|
|
||||||
}
|
|
||||||
return ostrAsStr(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
void winiToFile(iniwriter_t ctx, const char *filename, const winiopts_t *options) {
|
|
||||||
if (!options) options = &default_wopts;
|
|
||||||
|
|
||||||
file_t fp = fileOpen(filename, FILE_WRITE);
|
|
||||||
if (!fileIsValid(fp)) {
|
|
||||||
err("couldn't write ini to file %s", filename);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
str_t string = winiToString(ctx, options);
|
|
||||||
fileWriteWholeTextFP(fp, strvInitStr(string));
|
|
||||||
strFree(string);
|
|
||||||
fileClose(fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
winivalue_t *winiAddValEmpty(winitable_t *table) {
|
|
||||||
if (!table) return NULL;
|
|
||||||
vecAppend(table->values, (winivalue_t){0});
|
|
||||||
return &vecBack(table->values);
|
|
||||||
}
|
|
||||||
|
|
||||||
winivalue_t *winiAddVal(winitable_t *table, const char *key, const char *value) {
|
|
||||||
if (!table) return NULL;
|
|
||||||
winivalue_t val = { .key = strFromStr(key), .value = strFromStr(value) };
|
|
||||||
vecAppend(table->values, val);
|
|
||||||
return &vecBack(table->values);
|
|
||||||
}
|
|
||||||
|
|
||||||
winivalue_t *winiAddValStr(winitable_t *table, str_t key, str_t value) {
|
|
||||||
if (!table) return NULL;
|
|
||||||
winivalue_t val = { .key = key, .value = value };
|
|
||||||
vecAppend(table->values, val);
|
|
||||||
return &vecBack(table->values);
|
|
||||||
}
|
|
||||||
|
|
||||||
winivalue_t *winiAddValView(winitable_t *table, strview_t key, strview_t value) {
|
|
||||||
if (!table) return NULL;
|
|
||||||
winivalue_t val = { .key = strFromView(key), .value = strFromView(value) };
|
|
||||||
vecAppend(table->values, val);
|
|
||||||
return &vecBack(table->values);
|
|
||||||
}
|
|
||||||
|
|
||||||
winitable_t *winiAddTablEmpty(iniwriter_t *ctx) {
|
|
||||||
vecAppend(ctx->tables, (winitable_t){0});
|
|
||||||
return &vecBack(ctx->tables);
|
|
||||||
}
|
|
||||||
|
|
||||||
winitable_t *winiAddTab(iniwriter_t *ctx, const char *name) {
|
|
||||||
winitable_t tab = { .key = strFromStr(name) };
|
|
||||||
vecAppend(ctx->tables, tab);
|
|
||||||
return &vecBack(ctx->tables);
|
|
||||||
}
|
|
||||||
|
|
||||||
winitable_t *winiAddTabStr(iniwriter_t *ctx, str_t name) {
|
|
||||||
winitable_t tab = { .key = name };
|
|
||||||
vecAppend(ctx->tables, tab);
|
|
||||||
return &vecBack(ctx->tables);
|
|
||||||
}
|
|
||||||
|
|
||||||
winitable_t *winiAddTabView(iniwriter_t *ctx, strview_t name) {
|
|
||||||
winitable_t tab = { .key = strFromView(name) };
|
|
||||||
vecAppend(ctx->tables, tab);
|
|
||||||
return &vecBack(ctx->tables);
|
|
||||||
}
|
|
||||||
|
|
||||||
// == PRIVATE FUNCTIONS ========================================================
|
|
||||||
|
|
||||||
static iniopts_t setDefaultOptions(const iniopts_t *options) {
|
|
||||||
if (!options) return default_opts;
|
|
||||||
|
|
||||||
iniopts_t opts = default_opts;
|
|
||||||
|
|
||||||
if (options->merge_duplicate_keys)
|
|
||||||
opts.merge_duplicate_keys = options->merge_duplicate_keys;
|
|
||||||
|
|
||||||
if (options->merge_duplicate_tables)
|
|
||||||
opts.merge_duplicate_tables = options->merge_duplicate_tables;
|
|
||||||
|
|
||||||
if (options->key_value_divider)
|
|
||||||
opts.key_value_divider = options->key_value_divider;
|
|
||||||
|
|
||||||
return opts;
|
|
||||||
}
|
|
||||||
|
|
||||||
static initable_t *findTable(ini_t *ctx, strview_t name) {
|
|
||||||
if (strvIsEmpty(name)) return NULL;
|
|
||||||
for (uint32 i = 1; i < vecLen(ctx->tables); ++i) {
|
|
||||||
if (strvCompare(ctx->tables[i].name, name) == 0) {
|
|
||||||
return &ctx->tables[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inivalue_t *findValue(vec(inivalue_t) values, strview_t key) {
|
|
||||||
if (strvIsEmpty(key)) return NULL;
|
|
||||||
for (uint32 i = 0; i < vecLen(values); ++i) {
|
|
||||||
if (strvCompare(values[i].key, key) == 0) {
|
|
||||||
return &values[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void addTable(ini_t *ctx, str_istream_t *in, const iniopts_t *options) {
|
|
||||||
istrSkip(in, 1); // skip [
|
|
||||||
strview_t name = istrGetview(in, ']');
|
|
||||||
istrSkip(in, 1); // skip ]
|
|
||||||
initable_t *table = options->merge_duplicate_tables ? findTable(ctx, name) : NULL;
|
|
||||||
if (!table) {
|
|
||||||
vecAppend(ctx->tables, (initable_t){ name });
|
|
||||||
table = &vecBack(ctx->tables);
|
|
||||||
}
|
|
||||||
istrIgnore(in, '\n'); istrSkip(in, 1);
|
|
||||||
while (!istrIsFinished(*in)) {
|
|
||||||
switch (*in->cur) {
|
|
||||||
case '\n': case '\r':
|
|
||||||
return;
|
|
||||||
case '#': case ';':
|
|
||||||
istrIgnore(in, '\n');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
addValue(table, in, options);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void addValue(initable_t *table, str_istream_t *in, const iniopts_t *options) {
|
|
||||||
if (!table) fatal("table is null");
|
|
||||||
|
|
||||||
strview_t key = strvTrim(istrGetview(in, options->key_value_divider));
|
|
||||||
istrSkip(in, 1);
|
istrSkip(in, 1);
|
||||||
strview_t value = strvTrim(istrGetview(in, '\n'));
|
strview_t value = strvTrim(istrGetView(in, '\n'));
|
||||||
// value might be until EOF, in that case no use in skipping
|
istrSkip(in, 1);
|
||||||
if (!istrIsFinished(*in)) istrSkip(in, 1); // skip newline
|
inivalue_t *newval = NULL;
|
||||||
inivalue_t *new_value = options->merge_duplicate_keys ? findValue(table->values, key) : NULL;
|
|
||||||
if (!new_value) {
|
if (opts->merge_duplicate_keys) {
|
||||||
inivalue_t ini_val = (inivalue_t){ key, value };
|
newval = table->values;
|
||||||
vecAppend(table->values, ini_val);
|
while (newval) {
|
||||||
|
if (strvEquals(newval->key, key)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
newval = newval->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newval) {
|
||||||
|
newval->value = value;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
new_value->value = value;
|
newval = alloc(arena, inivalue_t);
|
||||||
|
newval->key = key;
|
||||||
|
newval->value = value;
|
||||||
|
|
||||||
|
if (!table->values) {
|
||||||
|
table->values = newval;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
table->tail->next = newval;
|
||||||
|
}
|
||||||
|
|
||||||
|
table->tail = newval;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ini__add_table(arena_t *arena, ini_t *ctx, instream_t *in, iniopts_t *options) {
|
||||||
|
istrSkip(in, 1); // skip [
|
||||||
|
strview_t name = istrGetView(in, ']');
|
||||||
|
istrSkip(in, 1); // skip ]
|
||||||
|
initable_t *table = NULL;
|
||||||
|
|
||||||
|
if (options->merge_duplicate_tables) {
|
||||||
|
table = ctx->tables;
|
||||||
|
while (table) {
|
||||||
|
if (strvEquals(table->name, name)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
table = table->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!table) {
|
||||||
|
table = alloc(arena, initable_t);
|
||||||
|
|
||||||
|
if (!ctx->tables) {
|
||||||
|
ctx->tables = table;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ctx->tail->next = table;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->tail = table;
|
||||||
|
}
|
||||||
|
|
||||||
|
istrIgnoreAndSkip(in, '\n');
|
||||||
|
while (!istrIsFinished(*in)) {
|
||||||
|
switch (istrPeek(in)) {
|
||||||
|
case '\n': // fallthrough
|
||||||
|
case '\r':
|
||||||
|
return;
|
||||||
|
case '#': // fallthrough
|
||||||
|
case ';':
|
||||||
|
istrIgnoreAndSkip(in, '\n');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ini__add_value(arena, table, in, options);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ini__parse(arena_t *arena, ini_t *ini, const iniopts_t *options) {
|
||||||
|
iniopts_t opts = ini__get_options(options);
|
||||||
|
|
||||||
|
instream_t in = istrInitLen(ini->text.buf, ini->text.len);
|
||||||
|
|
||||||
|
while (!istrIsFinished(in)) {
|
||||||
|
istrSkipWhitespace(&in);
|
||||||
|
switch (istrPeek(&in)) {
|
||||||
|
case '[':
|
||||||
|
ini__add_table(arena, ini, &in, &opts);
|
||||||
|
break;
|
||||||
|
case '#': // fallthrough
|
||||||
|
case ';':
|
||||||
|
istrIgnoreAndSkip(&in, '\n');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ini__add_value(arena, ini->tables, &in, &opts);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef INIPUSH
|
||||||
|
|
||||||
|
#include "warnings/colla_warn_end.h"
|
||||||
|
|
|
||||||
89
colla/ini.h
89
colla/ini.h
|
|
@ -1,29 +1,28 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include "collatypes.h"
|
||||||
#include "str.h"
|
#include "str.h"
|
||||||
#include "vec.h"
|
#include "file.h"
|
||||||
#include "utf8.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
typedef struct arena_t arena_t;
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// == INI READER ========================================================================
|
typedef struct inivalue_t {
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
strview_t key;
|
strview_t key;
|
||||||
strview_t value;
|
strview_t value;
|
||||||
|
struct inivalue_t *next;
|
||||||
} inivalue_t;
|
} inivalue_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct initable_t {
|
||||||
strview_t name;
|
strview_t name;
|
||||||
vec(inivalue_t) values;
|
inivalue_t *values;
|
||||||
|
inivalue_t *tail;
|
||||||
|
struct initable_t *next;
|
||||||
} initable_t;
|
} initable_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
str_t text;
|
strview_t text;
|
||||||
vec(initable_t) tables;
|
initable_t *tables;
|
||||||
|
initable_t *tail;
|
||||||
} ini_t;
|
} ini_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
@ -32,58 +31,22 @@ typedef struct {
|
||||||
char key_value_divider; // default =
|
char key_value_divider; // default =
|
||||||
} iniopts_t;
|
} iniopts_t;
|
||||||
|
|
||||||
ini_t iniParse(const char *filename, const iniopts_t *options);
|
ini_t iniParse(arena_t *arena, strview_t filename, const iniopts_t *options);
|
||||||
ini_t iniParseString(const char *inistr, const iniopts_t *options);
|
ini_t iniParseFile(arena_t *arena, file_t file, const iniopts_t *options);
|
||||||
void iniFree(ini_t ctx);
|
ini_t iniParseStr(arena_t *arena, strview_t str, const iniopts_t *options);
|
||||||
|
|
||||||
initable_t *iniGetTable(ini_t *ctx, const char *name);
|
#define INI_ROOT strv("__ROOT__")
|
||||||
inivalue_t *iniGet(initable_t *ctx, const char *key);
|
|
||||||
|
|
||||||
vec(strview_t) iniAsArray(const inivalue_t *value, char delim);
|
initable_t *iniGetTable(ini_t *ctx, strview_t name);
|
||||||
// delim is expected to be a single utf8 character
|
inivalue_t *iniGet(initable_t *ctx, strview_t key);
|
||||||
vec(strview_t) iniAsArrayU8(const inivalue_t *value, const char *delim);
|
|
||||||
uint64 iniAsUInt(const inivalue_t *value);
|
|
||||||
int64 iniAsInt(const inivalue_t *value);
|
|
||||||
double iniAsNum(const inivalue_t *value);
|
|
||||||
bool iniAsBool(const inivalue_t *value);
|
|
||||||
|
|
||||||
// == INI WRITER ========================================================================
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
str_t key;
|
strview_t *values;
|
||||||
str_t value;
|
usize count;
|
||||||
} winivalue_t;
|
} iniarray_t;
|
||||||
|
|
||||||
typedef struct {
|
iniarray_t iniAsArr(arena_t *arena, inivalue_t *value, char delim);
|
||||||
str_t key;
|
uint64 iniAsUInt(inivalue_t *value);
|
||||||
vec(winivalue_t) values;
|
int64 iniAsInt(inivalue_t *value);
|
||||||
} winitable_t;
|
double iniAsNum(inivalue_t *value);
|
||||||
|
bool iniAsBool(inivalue_t *value);
|
||||||
typedef struct {
|
|
||||||
vec(winitable_t) tables;
|
|
||||||
} iniwriter_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
bool no_discalimer; // default false
|
|
||||||
char key_value_divider; // default =
|
|
||||||
} winiopts_t;
|
|
||||||
|
|
||||||
iniwriter_t winiInit();
|
|
||||||
void winiFree(iniwriter_t ctx);
|
|
||||||
|
|
||||||
str_t winiToString(iniwriter_t ctx, const winiopts_t *options);
|
|
||||||
void winiToFile(iniwriter_t ctx, const char *filename, const winiopts_t *options);
|
|
||||||
|
|
||||||
winivalue_t *winiAddValEmpty(winitable_t *table);
|
|
||||||
winivalue_t *winiAddVal(winitable_t *table, const char *key, const char *value);
|
|
||||||
winivalue_t *winiAddValStr(winitable_t *table, str_t key, str_t value);
|
|
||||||
winivalue_t *winiAddValView(winitable_t *table, strview_t key, strview_t value);
|
|
||||||
|
|
||||||
winitable_t *winiAddTablEmpty(iniwriter_t *ctx);
|
|
||||||
winitable_t *winiAddTab(iniwriter_t *ctx, const char *name);
|
|
||||||
winitable_t *winiAddTabStr(iniwriter_t *ctx, str_t name);
|
|
||||||
winitable_t *winiAddTabView(iniwriter_t *ctx, strview_t name);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
} // extern "C"
|
|
||||||
#endif
|
|
||||||
288
colla/json.c
Normal file
288
colla/json.c
Normal file
|
|
@ -0,0 +1,288 @@
|
||||||
|
#include "json.h"
|
||||||
|
|
||||||
|
#include "warnings/colla_warn_beg.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "strstream.h"
|
||||||
|
#include "tracelog.h"
|
||||||
|
|
||||||
|
#define json__ensure(c) \
|
||||||
|
if (istrGet(in) != (c)) { \
|
||||||
|
istrRewindN(in, 1); \
|
||||||
|
fatal("wrong character at %zu, should be " #c " but is %c", istrTell(*in), istrPeek(in));\
|
||||||
|
}
|
||||||
|
|
||||||
|
str_t json__read_whole(arena_t *arena, const char *filename) {
|
||||||
|
str_t out = {0};
|
||||||
|
FILE *fp = fopen(filename, "rb");
|
||||||
|
if (!fp) {
|
||||||
|
err("could not open %s", filename);
|
||||||
|
return (str_t){0};
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek(fp, 0, SEEK_END);
|
||||||
|
long len = ftell(fp);
|
||||||
|
fseek(fp, 0, SEEK_SET);
|
||||||
|
|
||||||
|
out.buf = alloc(arena, char, len + 1);
|
||||||
|
out.len = len;
|
||||||
|
|
||||||
|
fread(out.buf, 1, len, fp);
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonval_t *json__parse_pair(arena_t *arena, instream_t *in, jsonflags_e flags);
|
||||||
|
jsonval_t *json__parse_value(arena_t *arena, instream_t *in, jsonflags_e flags);
|
||||||
|
|
||||||
|
bool json__is_value_finished(instream_t *in) {
|
||||||
|
usize old_pos = istrTell(*in);
|
||||||
|
|
||||||
|
istrSkipWhitespace(in);
|
||||||
|
switch(istrPeek(in)) {
|
||||||
|
case '}': // fallthrough
|
||||||
|
case ']': // fallthrough
|
||||||
|
case ',':
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
in->cur = in->start + old_pos;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void json__parse_null(instream_t *in) {
|
||||||
|
strview_t null_view = istrGetViewLen(in, 4);
|
||||||
|
|
||||||
|
if (!strvEquals(null_view, strv("null"))) {
|
||||||
|
fatal("should be null but is: (%.*s) at %zu", null_view.len, null_view.buf, istrTell(*in));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!json__is_value_finished(in)) {
|
||||||
|
fatal("null, should be finished, but isn't at %zu", istrTell(*in));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonval_t *json__parse_array(arena_t *arena, instream_t *in, jsonflags_e flags) {
|
||||||
|
json__ensure('[');
|
||||||
|
|
||||||
|
istrSkipWhitespace(in);
|
||||||
|
|
||||||
|
// if it is an empty array
|
||||||
|
if (istrPeek(in) == ']') {
|
||||||
|
istrSkip(in, 1);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonval_t *head = json__parse_value(arena, in, flags);
|
||||||
|
jsonval_t *cur = head;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
istrSkipWhitespace(in);
|
||||||
|
switch (istrGet(in)) {
|
||||||
|
case ']':
|
||||||
|
return head;
|
||||||
|
case ',':
|
||||||
|
{
|
||||||
|
istrSkipWhitespace(in);
|
||||||
|
// trailing comma
|
||||||
|
if (!(flags & JSON_NO_TRAILING_COMMAS) && istrPeek(in) == ']') {
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonval_t *next = json__parse_value(arena, in, flags);
|
||||||
|
cur->next = next;
|
||||||
|
next->prev = cur;
|
||||||
|
cur = next;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
istrRewindN(in, 1);
|
||||||
|
fatal("unknown char after array at %zu: (%c)(%d)", istrTell(*in), *in->cur, *in->cur);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
str_t json__parse_string(arena_t *arena, instream_t *in, jsonflags_e flags) {
|
||||||
|
istrSkipWhitespace(in);
|
||||||
|
|
||||||
|
json__ensure('"');
|
||||||
|
|
||||||
|
const char *from = in->cur;
|
||||||
|
|
||||||
|
for (; !istrIsFinished(*in) && *in->cur != '"'; ++in->cur) {
|
||||||
|
if (istrPeek(in) == '\\') {
|
||||||
|
++in->cur;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
usize len = in->cur - from;
|
||||||
|
|
||||||
|
str_t out = str(arena, from, len);
|
||||||
|
|
||||||
|
json__ensure('"');
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
double json__parse_number(instream_t *in, jsonflags_e flags) {
|
||||||
|
double value = 0.0;
|
||||||
|
istrGetDouble(in, &value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool json__parse_bool(instream_t *in, jsonflags_e flags) {
|
||||||
|
size_t remaining = istrRemaining(*in);
|
||||||
|
if (remaining >= 4 && memcmp(in->cur, "true", 4) == 0) {
|
||||||
|
istrSkip(in, 4);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (remaining >= 5 && memcmp(in->cur, "false", 5) == 0) {
|
||||||
|
istrSkip(in, 5);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fatal("unknown boolean at %zu: %.10s", istrTell(*in), in->cur);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonval_t *json__parse_obj(arena_t *arena, instream_t *in, jsonflags_e flags) {
|
||||||
|
json__ensure('{');
|
||||||
|
|
||||||
|
istrSkipWhitespace(in);
|
||||||
|
|
||||||
|
// if it is an empty object
|
||||||
|
if (istrPeek(in) == '}') {
|
||||||
|
istrSkip(in, 1);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonval_t *head = json__parse_pair(arena, in, flags);
|
||||||
|
jsonval_t *cur = head;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
istrSkipWhitespace(in);
|
||||||
|
switch (istrGet(in)) {
|
||||||
|
case '}':
|
||||||
|
return head;
|
||||||
|
case ',':
|
||||||
|
{
|
||||||
|
istrSkipWhitespace(in);
|
||||||
|
// trailing commas
|
||||||
|
if (!(flags & JSON_NO_TRAILING_COMMAS) && istrPeek(in) == '}') {
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonval_t *next = json__parse_pair(arena, in, flags);
|
||||||
|
cur->next = next;
|
||||||
|
next->prev = cur;
|
||||||
|
cur = next;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
istrRewindN(in, 1);
|
||||||
|
fatal("unknown char after object at %zu: (%c)(%d)", istrTell(*in), *in->cur, *in->cur);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonval_t *json__parse_pair(arena_t *arena, instream_t *in, jsonflags_e flags) {
|
||||||
|
str_t key = json__parse_string(arena, in, flags);
|
||||||
|
|
||||||
|
// skip preamble
|
||||||
|
istrSkipWhitespace(in);
|
||||||
|
json__ensure(':');
|
||||||
|
|
||||||
|
jsonval_t *out = json__parse_value(arena, in, flags);
|
||||||
|
out->key = key;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonval_t *json__parse_value(arena_t *arena, instream_t *in, jsonflags_e flags) {
|
||||||
|
jsonval_t *out = alloc(arena, jsonval_t);
|
||||||
|
|
||||||
|
istrSkipWhitespace(in);
|
||||||
|
|
||||||
|
switch (istrPeek(in)) {
|
||||||
|
// object
|
||||||
|
case '{':
|
||||||
|
out->object = json__parse_obj(arena, in, flags);
|
||||||
|
out->type = JSON_OBJECT;
|
||||||
|
break;
|
||||||
|
// array
|
||||||
|
case '[':
|
||||||
|
out->array = json__parse_array(arena, in, flags);
|
||||||
|
out->type = JSON_ARRAY;
|
||||||
|
break;
|
||||||
|
// string
|
||||||
|
case '"':
|
||||||
|
out->string = json__parse_string(arena, in, flags);
|
||||||
|
out->type = JSON_STRING;
|
||||||
|
break;
|
||||||
|
// boolean
|
||||||
|
case 't': // fallthrough
|
||||||
|
case 'f':
|
||||||
|
out->boolean = json__parse_bool(in, flags);
|
||||||
|
out->type = JSON_BOOL;
|
||||||
|
break;
|
||||||
|
// null
|
||||||
|
case 'n':
|
||||||
|
json__parse_null(in);
|
||||||
|
out->type = JSON_NULL;
|
||||||
|
break;
|
||||||
|
// comment
|
||||||
|
case '/':
|
||||||
|
fatal("TODO comments");
|
||||||
|
break;
|
||||||
|
// number
|
||||||
|
default:
|
||||||
|
out->number = json__parse_number(in, flags);
|
||||||
|
out->type = JSON_NUMBER;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
json_t jsonParse(arena_t *arena, arena_t scratch, const char *filename, jsonflags_e flags) {
|
||||||
|
str_t data = json__read_whole(&scratch, filename);
|
||||||
|
json_t json = jsonParseStr(arena, strv(data), flags);
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
json_t jsonParseStr(arena_t *arena, strview_t jsonstr, jsonflags_e flags) {
|
||||||
|
jsonval_t *root = alloc(arena, jsonval_t);
|
||||||
|
root->type = JSON_OBJECT;
|
||||||
|
|
||||||
|
instream_t in = istrInitLen(jsonstr.buf, jsonstr.len);
|
||||||
|
root->object = json__parse_obj(arena, &in, flags);
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonval_t *jsonGet(jsonval_t *node, strview_t key) {
|
||||||
|
if (!node) return NULL;
|
||||||
|
|
||||||
|
if (node->type != JSON_OBJECT) {
|
||||||
|
err("passed type is not an object");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
node = node->object;
|
||||||
|
|
||||||
|
while (node) {
|
||||||
|
if (strvEquals(strv(node->key), key)) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
node = node->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "warnings/colla_warn_end.h"
|
||||||
47
colla/json.h
Normal file
47
colla/json.h
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "str.h"
|
||||||
|
#include "arena.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
JSON_NULL,
|
||||||
|
JSON_ARRAY,
|
||||||
|
JSON_STRING,
|
||||||
|
JSON_NUMBER,
|
||||||
|
JSON_BOOL,
|
||||||
|
JSON_OBJECT,
|
||||||
|
} jsontype_e;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
JSON_DEFAULT = 0,
|
||||||
|
JSON_NO_TRAILING_COMMAS = 1 << 0,
|
||||||
|
JSON_NO_COMMENTS = 1 << 1,
|
||||||
|
} jsonflags_e;
|
||||||
|
|
||||||
|
typedef struct jsonval_t jsonval_t;
|
||||||
|
|
||||||
|
typedef struct jsonval_t {
|
||||||
|
jsonval_t *next;
|
||||||
|
jsonval_t *prev;
|
||||||
|
|
||||||
|
str_t key;
|
||||||
|
|
||||||
|
union {
|
||||||
|
jsonval_t *array;
|
||||||
|
str_t string;
|
||||||
|
double number;
|
||||||
|
bool boolean;
|
||||||
|
jsonval_t *object;
|
||||||
|
};
|
||||||
|
jsontype_e type;
|
||||||
|
} jsonval_t;
|
||||||
|
|
||||||
|
typedef jsonval_t *json_t;
|
||||||
|
|
||||||
|
json_t jsonParse(arena_t *arena, arena_t scratch, const char *filename, jsonflags_e flags);
|
||||||
|
json_t jsonParseStr(arena_t *arena, strview_t jsonstr, jsonflags_e flags);
|
||||||
|
|
||||||
|
jsonval_t *jsonGet(jsonval_t *node, strview_t key);
|
||||||
|
|
||||||
|
#define json_for(name, arr) for (jsonval_t *name = arr->array; name; name = name->next)
|
||||||
|
#define json_check(val, js_type) ((val) && (val)->type == js_type)
|
||||||
382
colla/server.c
Normal file
382
colla/server.c
Normal file
|
|
@ -0,0 +1,382 @@
|
||||||
|
#include "server.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "socket.h"
|
||||||
|
#include "tracelog.h"
|
||||||
|
#include "strstream.h"
|
||||||
|
#include "arena.h"
|
||||||
|
|
||||||
|
#define SERVER_BUFSZ 4096
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
PARSE_REQ_BEGIN,
|
||||||
|
PARSE_REQ_PAGE,
|
||||||
|
PARSE_REQ_VERSION,
|
||||||
|
PARSE_REQ_FIELDS,
|
||||||
|
PARSE_REQ_FINISHED,
|
||||||
|
} server__req_status_e;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
server_req_t req;
|
||||||
|
server__req_status_e status;
|
||||||
|
char fullbuf[SERVER_BUFSZ * 2];
|
||||||
|
usize prevbuf_len;
|
||||||
|
} server__req_ctx_t;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct server__route_t {
|
||||||
|
str_t page;
|
||||||
|
server_route_f fn;
|
||||||
|
void *userdata;
|
||||||
|
struct server__route_t *next;
|
||||||
|
} server__route_t;
|
||||||
|
|
||||||
|
typedef struct server_t {
|
||||||
|
socket_t socket;
|
||||||
|
server__route_t *routes;
|
||||||
|
server__route_t *routes_default;
|
||||||
|
bool should_stop;
|
||||||
|
} server_t;
|
||||||
|
|
||||||
|
bool server__parse_chunk(arena_t *arena, server__req_ctx_t *ctx, char buffer[SERVER_BUFSZ], usize buflen) {
|
||||||
|
memcpy(ctx->fullbuf + ctx->prevbuf_len, buffer, buflen);
|
||||||
|
usize fulllen = ctx->prevbuf_len + buflen;
|
||||||
|
|
||||||
|
instream_t in = istrInitLen(ctx->fullbuf, fulllen);
|
||||||
|
|
||||||
|
#define RESET_STREAM() in.cur = in.start + begin
|
||||||
|
#define BEGIN_STREAM() begin = istrTell(in)
|
||||||
|
|
||||||
|
usize begin = istrTell(in);
|
||||||
|
|
||||||
|
switch (ctx->status) {
|
||||||
|
case PARSE_REQ_BEGIN:
|
||||||
|
{
|
||||||
|
BEGIN_STREAM();
|
||||||
|
|
||||||
|
strview_t method = istrGetView(&in, ' ');
|
||||||
|
if (istrGet(&in) != ' ') {
|
||||||
|
RESET_STREAM();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strvEquals(method, strv("GET"))) {
|
||||||
|
ctx->req.method = HTTP_GET;
|
||||||
|
}
|
||||||
|
else if(strvEquals(method, strv("POST"))) {
|
||||||
|
ctx->req.method = HTTP_POST;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fatal("unknown method: (%.*s)", method.len, method.buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
info("parsed method: %.*s", method.len, method.buf);
|
||||||
|
|
||||||
|
ctx->status = PARSE_REQ_PAGE;
|
||||||
|
}
|
||||||
|
// fallthrough
|
||||||
|
case PARSE_REQ_PAGE:
|
||||||
|
{
|
||||||
|
BEGIN_STREAM();
|
||||||
|
|
||||||
|
strview_t page = istrGetView(&in, ' ');
|
||||||
|
if (istrGet(&in) != ' ') {
|
||||||
|
RESET_STREAM();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->req.page = str(arena, page);
|
||||||
|
|
||||||
|
info("parsed page: %.*s", page.len, page.buf);
|
||||||
|
|
||||||
|
ctx->status = PARSE_REQ_VERSION;
|
||||||
|
}
|
||||||
|
// fallthrough
|
||||||
|
case PARSE_REQ_VERSION:
|
||||||
|
{
|
||||||
|
BEGIN_STREAM();
|
||||||
|
|
||||||
|
strview_t version = istrGetView(&in, '\n');
|
||||||
|
if (istrGet(&in) != '\n') {
|
||||||
|
RESET_STREAM();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version.len < 8) {
|
||||||
|
fatal("version too short: (%.*s)", version.len, version.buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strvEquals(strvSub(version, 0, 4), strv("HTTP"))) {
|
||||||
|
fatal("version does not start with HTTP: (%.4s)", version.buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip HTTP
|
||||||
|
version = strvRemovePrefix(version, 4);
|
||||||
|
|
||||||
|
uint8 major, minor;
|
||||||
|
int scanned = sscanf(version.buf, "/%hhu.%hhu", &major, &minor);
|
||||||
|
|
||||||
|
if (scanned != 2) {
|
||||||
|
fatal("could not scan both major and minor from version: (%.*s)", version.len, version.buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->req.version_major = major;
|
||||||
|
ctx->req.version_minor = minor;
|
||||||
|
|
||||||
|
ctx->status = PARSE_REQ_FIELDS;
|
||||||
|
}
|
||||||
|
// fallthrough
|
||||||
|
case PARSE_REQ_FIELDS:
|
||||||
|
{
|
||||||
|
bool finished_parsing = false;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
BEGIN_STREAM();
|
||||||
|
|
||||||
|
strview_t field = istrGetView(&in, '\n');
|
||||||
|
if (istrGet(&in) != '\n') {
|
||||||
|
RESET_STREAM();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
instream_t field_in = istrInitLen(field.buf, field.len);
|
||||||
|
|
||||||
|
strview_t key = istrGetView(&field_in, ':');
|
||||||
|
if (istrGet(&field_in) != ':') {
|
||||||
|
fatal("field does not have ':' (%.*s)", field.len, field.buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
istrSkipWhitespace(&field_in);
|
||||||
|
|
||||||
|
strview_t value = istrGetView(&field_in, '\r');
|
||||||
|
if (istrGet(&field_in) != '\r') {
|
||||||
|
warn("field does not finish with \\r: (%.*s)", field.len, field.buf);
|
||||||
|
RESET_STREAM();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
server_field_t *new_field = alloc(arena, server_field_t);
|
||||||
|
new_field->key = str(arena, key);
|
||||||
|
new_field->value = str(arena, value);
|
||||||
|
|
||||||
|
if (!ctx->req.fields) {
|
||||||
|
ctx->req.fields = new_field;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->req.fields_tail) {
|
||||||
|
ctx->req.fields_tail->next = new_field;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->req.fields_tail = new_field;
|
||||||
|
|
||||||
|
// check if we finished parsing the fields
|
||||||
|
if (istrGet(&in) == '\r') {
|
||||||
|
if (istrGet(&in) == '\n') {
|
||||||
|
finished_parsing = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
istrRewindN(&in, 2);
|
||||||
|
warn("should have finished parsing field, but apparently not?: (%.*s)", in.cur, istrRemaining(in));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
istrRewindN(&in, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!finished_parsing) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->status = PARSE_REQ_FINISHED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef RESET_STREAM
|
||||||
|
|
||||||
|
ctx->prevbuf_len = istrRemaining(in);
|
||||||
|
|
||||||
|
memmove(ctx->fullbuf, ctx->fullbuf + istrTell(in), ctx->prevbuf_len);
|
||||||
|
|
||||||
|
return ctx->status == PARSE_REQ_FINISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
void server__parse_req_url(arena_t *arena, server_req_t *req) {
|
||||||
|
instream_t in = istrInitLen(req->page.buf, req->page.len);
|
||||||
|
istrIgnore(&in, '?');
|
||||||
|
// no fields in url
|
||||||
|
if (istrGet(&in) != '?') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
req->page.len = istrTell(in) - 1;
|
||||||
|
req->page.buf[req->page.len] = '\0';
|
||||||
|
|
||||||
|
while (!istrIsFinished(in)) {
|
||||||
|
strview_t field = istrGetView(&in, '&');
|
||||||
|
istrSkip(&in, 1); // skip &
|
||||||
|
usize pos = strvFind(field, '=', 0);
|
||||||
|
if (pos == SIZE_MAX) {
|
||||||
|
fatal("url parameter does not include =: %.*s", field.buf, field.len);
|
||||||
|
}
|
||||||
|
strview_t key = strvSub(field, 0, pos);
|
||||||
|
strview_t val = strvSub(field, pos + 1, SIZE_MAX);
|
||||||
|
|
||||||
|
server_field_t *f = alloc(arena, server_field_t);
|
||||||
|
f->key = str(arena, key);
|
||||||
|
f->value = str(arena, val);
|
||||||
|
f->next = req->page_fields;
|
||||||
|
req->page_fields = f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server_t *serverSetup(arena_t *arena, uint16 port) {
|
||||||
|
if (!skInit()) {
|
||||||
|
fatal("couldn't initialise sockets: %s", skGetErrorString());
|
||||||
|
}
|
||||||
|
|
||||||
|
socket_t sk = skOpen(SOCK_TCP);
|
||||||
|
if (!skIsValid(sk)) {
|
||||||
|
fatal("couldn't open socket: %s", skGetErrorString());
|
||||||
|
}
|
||||||
|
|
||||||
|
skaddrin_t addr = {
|
||||||
|
.sin_family = AF_INET,
|
||||||
|
.sin_addr.s_addr = INADDR_ANY,
|
||||||
|
.sin_port = htons(port),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!skBindPro(sk, (skaddr_t *)&addr, sizeof(addr))) {
|
||||||
|
fatal("could not bind socket: %s", skGetErrorString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!skListenPro(sk, 10)) {
|
||||||
|
fatal("could not listen on socket: %s", skGetErrorString());
|
||||||
|
}
|
||||||
|
|
||||||
|
server_t *server = alloc(arena, server_t);
|
||||||
|
|
||||||
|
server->socket = sk;
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
void serverRoute(arena_t *arena, server_t *server, strview_t page, server_route_f cb, void *userdata) {
|
||||||
|
// check if a route for that page already exists
|
||||||
|
server__route_t *r = server->routes;
|
||||||
|
|
||||||
|
while (r) {
|
||||||
|
if (strvEquals(strv(r->page), page)) {
|
||||||
|
r->fn = cb;
|
||||||
|
r->userdata = userdata;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
r = r->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no route found, make a new one
|
||||||
|
if (!r) {
|
||||||
|
r = alloc(arena, server__route_t);
|
||||||
|
r->next = server->routes;
|
||||||
|
server->routes = r;
|
||||||
|
|
||||||
|
r->page = str(arena, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
r->fn = cb;
|
||||||
|
r->userdata = userdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
void serverRouteDefault(arena_t *arena, server_t *server, server_route_f cb, void *userdata) {
|
||||||
|
server__route_t *r = server->routes_default;
|
||||||
|
|
||||||
|
if (!r) {
|
||||||
|
r = alloc(arena, server__route_t);
|
||||||
|
server->routes_default = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
r->fn = cb;
|
||||||
|
r->userdata = userdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
void serverStart(arena_t scratch, server_t *server) {
|
||||||
|
usize start = arenaTell(&scratch);
|
||||||
|
|
||||||
|
while (!server->should_stop) {
|
||||||
|
socket_t client = skAccept(server->socket);
|
||||||
|
if (!skIsValid(client)) {
|
||||||
|
err("accept failed");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
info("received connection: %zu", client);
|
||||||
|
|
||||||
|
arenaRewind(&scratch, start);
|
||||||
|
|
||||||
|
server__req_ctx_t req_ctx = {0};
|
||||||
|
|
||||||
|
char buffer[SERVER_BUFSZ];
|
||||||
|
int read = 0;
|
||||||
|
do {
|
||||||
|
read = skReceive(client, buffer, sizeof(buffer));
|
||||||
|
if(read == -1) {
|
||||||
|
fatal("couldn't get the data from the server: %s", skGetErrorString());
|
||||||
|
}
|
||||||
|
if (server__parse_chunk(&scratch, &req_ctx, buffer, read)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while(read != 0);
|
||||||
|
|
||||||
|
info("parsed request");
|
||||||
|
|
||||||
|
server_req_t req = req_ctx.req;
|
||||||
|
|
||||||
|
server__parse_req_url(&scratch, &req);
|
||||||
|
|
||||||
|
server__route_t *route = server->routes;
|
||||||
|
while (route) {
|
||||||
|
if (strEquals(route->page, req.page)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
route = route->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!route) {
|
||||||
|
route = server->routes_default;
|
||||||
|
}
|
||||||
|
|
||||||
|
str_t response = route->fn(scratch, server, &req, route->userdata);
|
||||||
|
|
||||||
|
skSend(client, response.buf, response.len);
|
||||||
|
skClose(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!skClose(server->socket)) {
|
||||||
|
fatal("couldn't close socket: %s", skGetErrorString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void serverStop(server_t *server) {
|
||||||
|
server->should_stop = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
str_t serverMakeResponse(arena_t *arena, int status_code, strview_t content_type, strview_t body) {
|
||||||
|
return strFmt(
|
||||||
|
arena,
|
||||||
|
|
||||||
|
"HTTP/1.1 %d %s\r\n"
|
||||||
|
"Content-Type: %.*s\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"%.*s",
|
||||||
|
|
||||||
|
status_code, httpGetStatusString(status_code),
|
||||||
|
content_type.len, content_type.buf,
|
||||||
|
body.len, body.buf
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef SERVER_BUFSZ
|
||||||
36
colla/server.h
Normal file
36
colla/server.h
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "collatypes.h"
|
||||||
|
#include "str.h"
|
||||||
|
#include "http.h"
|
||||||
|
|
||||||
|
typedef struct arena_t arena_t;
|
||||||
|
typedef struct server_t server_t;
|
||||||
|
|
||||||
|
typedef struct server_field_t {
|
||||||
|
str_t key;
|
||||||
|
str_t value;
|
||||||
|
struct server_field_t *next;
|
||||||
|
} server_field_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
http_method_e method;
|
||||||
|
str_t page;
|
||||||
|
server_field_t *page_fields;
|
||||||
|
uint8 version_minor;
|
||||||
|
uint8 version_major;
|
||||||
|
server_field_t *fields;
|
||||||
|
server_field_t *fields_tail;
|
||||||
|
// buffer_t body;
|
||||||
|
} server_req_t;
|
||||||
|
|
||||||
|
typedef str_t (*server_route_f)(arena_t scratch, server_t *server, server_req_t *req, void *userdata);
|
||||||
|
|
||||||
|
|
||||||
|
server_t *serverSetup(arena_t *arena, uint16 port);
|
||||||
|
void serverRoute(arena_t *arena, server_t *server, strview_t page, server_route_f cb, void *userdata);
|
||||||
|
void serverRouteDefault(arena_t *arena, server_t *server, server_route_f cb, void *userdata);
|
||||||
|
void serverStart(arena_t scratch, server_t *server);
|
||||||
|
void serverStop(server_t *server);
|
||||||
|
|
||||||
|
str_t serverMakeResponse(arena_t *arena, int status_code, strview_t content_type, strview_t body);
|
||||||
231
colla/socket.c
231
colla/socket.c
|
|
@ -1,55 +1,71 @@
|
||||||
#include "socket.h"
|
#include "socket.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#if COLLA_WIN && COLLA_CMT_LIB
|
||||||
#include "tracelog.h"
|
#pragma comment(lib, "Ws2_32")
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
// VERY MUCH NOT THREAD SAFE
|
|
||||||
static int initialize_count = 0;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if SOCK_WINDOWS
|
#if COLLA_WIN
|
||||||
static bool _win_skInit();
|
#include <winsock2.h>
|
||||||
static bool _win_skCleanup();
|
|
||||||
static int _win_skGetError();
|
|
||||||
static const char *_win_skGetErrorString();
|
|
||||||
|
|
||||||
#define SOCK_CALL(fun) _win_##fun
|
bool skInit(void) {
|
||||||
|
WSADATA w;
|
||||||
|
int error = WSAStartup(0x0202, &w);
|
||||||
|
return error == 0;
|
||||||
|
}
|
||||||
|
|
||||||
#elif SOCK_POSIX
|
bool skCleanup(void) {
|
||||||
|
return WSACleanup() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool skClose(socket_t sock) {
|
||||||
|
return closesocket(sock) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) {
|
||||||
|
return WSAPoll(to_poll, num_to_poll, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
int skGetError(void) {
|
||||||
|
return WSAGetLastError();
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <string.h> // strerror
|
#include <string.h> // strerror
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
|
|
||||||
#define INVALID_SOCKET (-1)
|
bool skInit(void) {
|
||||||
#define SOCKET_ERROR (-1)
|
return true;
|
||||||
|
|
||||||
static bool _posix_skInit();
|
|
||||||
static bool _posix_skCleanup();
|
|
||||||
static int _posix_skGetError();
|
|
||||||
static const char *_posix_skGetErrorString();
|
|
||||||
|
|
||||||
#define SOCK_CALL(fun) _posix_##fun
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool skInit() {
|
|
||||||
#ifndef NDEBUG
|
|
||||||
++initialize_count;
|
|
||||||
#endif
|
|
||||||
return SOCK_CALL(skInit());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool skCleanup() {
|
bool skCleanup(void) {
|
||||||
#ifndef NDEBUG
|
return true;
|
||||||
--initialize_count;
|
|
||||||
#endif
|
|
||||||
return SOCK_CALL(skCleanup());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
socket_t skOpen(sktype_t type) {
|
bool skClose(socket_t sock) {
|
||||||
|
return close(sock) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) {
|
||||||
|
return poll(to_poll, num_to_poll, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
int skGetError(void) {
|
||||||
|
return errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *skGetErrorString(void) {
|
||||||
|
return strerror(errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
socket_t skOpen(sktype_e type) {
|
||||||
int sock_type = 0;
|
int sock_type = 0;
|
||||||
|
|
||||||
switch(type) {
|
switch(type) {
|
||||||
|
|
@ -62,58 +78,37 @@ socket_t skOpen(sktype_t type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
socket_t skOpenEx(const char *protocol) {
|
socket_t skOpenEx(const char *protocol) {
|
||||||
#ifndef NDEBUG
|
|
||||||
if(initialize_count <= 0) {
|
|
||||||
fatal("skInit has not been called");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
struct protoent *proto = getprotobyname(protocol);
|
struct protoent *proto = getprotobyname(protocol);
|
||||||
if(!proto) {
|
if(!proto) {
|
||||||
return INVALID_SOCKET;
|
return (socket_t){0};
|
||||||
}
|
}
|
||||||
return skOpenPro(AF_INET, SOCK_STREAM, proto->p_proto);
|
return skOpenPro(AF_INET, SOCK_STREAM, proto->p_proto);
|
||||||
}
|
}
|
||||||
|
|
||||||
socket_t skOpenPro(int af, int type, int protocol) {
|
socket_t skOpenPro(int af, int type, int protocol) {
|
||||||
#ifndef NDEBUG
|
|
||||||
if(initialize_count <= 0) {
|
|
||||||
fatal("skInit has not been called");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return socket(af, type, protocol);
|
return socket(af, type, protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
sk_addrin_t skAddrinInit(const char *ip, uint16_t port) {
|
bool skIsValid(socket_t sock) {
|
||||||
sk_addrin_t addr;
|
return sock != (socket_t)-1;
|
||||||
addr.sin_family = AF_INET;
|
|
||||||
addr.sin_port = htons(port);
|
|
||||||
addr.sin_addr.s_addr = inet_addr(ip);
|
|
||||||
return addr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool skClose(socket_t sock) {
|
skaddrin_t skAddrinInit(const char *ip, uint16_t port) {
|
||||||
#if SOCK_WINDOWS
|
return (skaddrin_t){
|
||||||
int error = closesocket(sock);
|
.sin_family = AF_INET,
|
||||||
#elif SOCK_POSIX
|
.sin_port = htons(port),
|
||||||
int error = close(sock);
|
// TODO use inet_pton instead
|
||||||
#endif
|
.sin_addr.s_addr = inet_addr(ip),
|
||||||
sock = INVALID_SOCKET;
|
};
|
||||||
return error != SOCKET_ERROR;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool skBind(socket_t sock, const char *ip, uint16_t port) {
|
bool skBind(socket_t sock, const char *ip, uint16_t port) {
|
||||||
sk_addrin_t addr;
|
skaddrin_t addr = skAddrinInit(ip, port);
|
||||||
addr.sin_family = AF_INET;
|
return skBindPro(sock, (skaddr_t *)&addr, sizeof(addr));
|
||||||
// TODO use inet_pton instead
|
|
||||||
addr.sin_addr.s_addr = inet_addr(ip);
|
|
||||||
|
|
||||||
addr.sin_port = htons(port);
|
|
||||||
|
|
||||||
return skBindPro(sock, (sk_addr_t *) &addr, sizeof(addr));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool skBindPro(socket_t sock, const sk_addr_t *name, sk_len_t namelen) {
|
bool skBindPro(socket_t sock, const skaddr_t *name, int namelen) {
|
||||||
return bind(sock, name, namelen) != SOCKET_ERROR;
|
return bind(sock, name, namelen) != -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool skListen(socket_t sock) {
|
bool skListen(socket_t sock) {
|
||||||
|
|
@ -121,16 +116,16 @@ bool skListen(socket_t sock) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool skListenPro(socket_t sock, int backlog) {
|
bool skListenPro(socket_t sock, int backlog) {
|
||||||
return listen(sock, backlog) != SOCKET_ERROR;
|
return listen(sock, backlog) != -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
socket_t skAccept(socket_t sock) {
|
socket_t skAccept(socket_t sock) {
|
||||||
sk_addrin_t addr;
|
skaddrin_t addr = {0};
|
||||||
sk_len_t addr_size = (sk_len_t)sizeof(addr);
|
int addr_size = sizeof(addr);
|
||||||
return skAcceptPro(sock, (sk_addr_t *) &addr, &addr_size);
|
return skAcceptPro(sock, (skaddr_t *)&addr, &addr_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
socket_t skAcceptPro(socket_t sock, sk_addr_t *addr, sk_len_t *addrlen) {
|
socket_t skAcceptPro(socket_t sock, skaddr_t *addr, int *addrlen) {
|
||||||
return accept(sock, addr, addrlen);
|
return accept(sock, addr, addrlen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,16 +138,13 @@ bool skConnect(socket_t sock, const char *server, unsigned short server_port) {
|
||||||
address = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]);
|
address = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
sk_addrin_t addr;
|
skaddrin_t addr = skAddrinInit(address, server_port);
|
||||||
addr.sin_family = AF_INET;
|
|
||||||
addr.sin_addr.s_addr = inet_addr(address);
|
|
||||||
addr.sin_port = htons(server_port);
|
|
||||||
|
|
||||||
return skConnectPro(sock, (sk_addr_t *) &addr, sizeof(addr));
|
return skConnectPro(sock, (skaddr_t *)&addr, sizeof(addr));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool skConnectPro(socket_t sock, const sk_addr_t *name, sk_len_t namelen) {
|
bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen) {
|
||||||
return connect(sock, name, namelen) != SOCKET_ERROR;
|
return connect(sock, name, namelen) != -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int skSend(socket_t sock, const void *buf, int len) {
|
int skSend(socket_t sock, const void *buf, int len) {
|
||||||
|
|
@ -163,11 +155,11 @@ int skSendPro(socket_t sock, const void *buf, int len, int flags) {
|
||||||
return send(sock, buf, len, flags);
|
return send(sock, buf, len, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
int skSendTo(socket_t sock, const void *buf, int len, const sk_addrin_t *to) {
|
int skSendTo(socket_t sock, const void *buf, int len, const skaddrin_t *to) {
|
||||||
return skSendToPro(sock, buf, len, 0, (sk_addr_t*) to, sizeof(sk_addrin_t));
|
return skSendToPro(sock, buf, len, 0, (skaddr_t*)to, sizeof(skaddrin_t));
|
||||||
}
|
}
|
||||||
|
|
||||||
int skSendToPro(socket_t sock, const void *buf, int len, int flags, const sk_addr_t *to, int tolen) {
|
int skSendToPro(socket_t sock, const void *buf, int len, int flags, const skaddr_t *to, int tolen) {
|
||||||
return sendto(sock, buf, len, flags, to, tolen);
|
return sendto(sock, buf, len, flags, to, tolen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,52 +171,19 @@ int skReceivePro(socket_t sock, void *buf, int len, int flags) {
|
||||||
return recv(sock, buf, len, flags);
|
return recv(sock, buf, len, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
int skReceiveFrom(socket_t sock, void *buf, int len, sk_addrin_t *from) {
|
int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from) {
|
||||||
sk_len_t fromlen = sizeof(sk_addr_t);
|
int fromlen = sizeof(skaddr_t);
|
||||||
return skReceiveFromPro(sock, buf, len, 0, (sk_addr_t*)from, &fromlen);
|
return skReceiveFromPro(sock, buf, len, 0, (skaddr_t*)from, &fromlen);
|
||||||
}
|
}
|
||||||
|
|
||||||
int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, sk_addr_t *from, sk_len_t *fromlen) {
|
int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, skaddr_t *from, int *fromlen) {
|
||||||
return recvfrom(sock, buf, len, flags, from, fromlen);
|
return recvfrom(sock, buf, len, flags, from, fromlen);
|
||||||
}
|
}
|
||||||
|
|
||||||
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout) {
|
// put this at the end of file to not make everything else unreadable
|
||||||
#if SOCK_WINDOWS
|
#if COLLA_WIN
|
||||||
return WSAPoll(to_poll, num_to_poll, timeout);
|
const char *skGetErrorString(void) {
|
||||||
#elif SOCK_POSIX
|
switch(skGetError()) {
|
||||||
return poll(to_poll, num_to_poll, timeout);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool skIsValid(socket_t sock) {
|
|
||||||
return sock != INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
|
|
||||||
int skGetError() {
|
|
||||||
return SOCK_CALL(skGetError());
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *skGetErrorString() {
|
|
||||||
return SOCK_CALL(skGetErrorString());
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef SOCK_WINDOWS
|
|
||||||
static bool _win_skInit() {
|
|
||||||
WSADATA w;
|
|
||||||
int error = WSAStartup(0x0202, &w);
|
|
||||||
return error == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool _win_skCleanup() {
|
|
||||||
return WSACleanup() == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _win_skGetError() {
|
|
||||||
return WSAGetLastError();
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *_win_skGetErrorString() {
|
|
||||||
switch(_win_skGetError()) {
|
|
||||||
case WSA_INVALID_HANDLE: return "Specified event object handle is invalid.";
|
case WSA_INVALID_HANDLE: return "Specified event object handle is invalid.";
|
||||||
case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available.";
|
case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available.";
|
||||||
case WSA_INVALID_PARAMETER: return "One or more parameters are invalid.";
|
case WSA_INVALID_PARAMETER: return "One or more parameters are invalid.";
|
||||||
|
|
@ -324,22 +283,4 @@ static const char *_win_skGetErrorString() {
|
||||||
|
|
||||||
return "(nothing)";
|
return "(nothing)";
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
static bool _posix_skInit() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool _posix_skCleanup() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _posix_skGetError() {
|
|
||||||
return errno;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *_posix_skGetErrorString() {
|
|
||||||
return strerror(errno);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -1,45 +1,22 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#include "collatypes.h"
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
#if COLLA_WIN
|
||||||
#include <stdint.h>
|
#include <winsock2.h>
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#define SOCK_WINDOWS 1
|
|
||||||
#else
|
#else
|
||||||
#define SOCK_POSIX 1
|
#include <sys/socket.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if SOCK_WINDOWS
|
typedef uintptr_t socket_t;
|
||||||
#pragma warning(disable:4996) // _WINSOCK_DEPRECATED_NO_WARNINGS
|
typedef struct sockaddr skaddr_t;
|
||||||
#include "win32_slim.h"
|
typedef struct sockaddr_in skaddrin_t;
|
||||||
#include <winsock2.h>
|
|
||||||
#include <ws2tcpip.h>
|
|
||||||
typedef SOCKET socket_t;
|
|
||||||
typedef int sk_len_t;
|
|
||||||
#elif SOCK_POSIX
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
typedef int socket_t;
|
|
||||||
typedef uint32_t sk_len_t;
|
|
||||||
#define INVALID_SOCKET (-1)
|
|
||||||
#define SOCKET_ERROR (-1)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef struct sockaddr sk_addr_t;
|
|
||||||
typedef struct sockaddr_in sk_addrin_t;
|
|
||||||
typedef struct pollfd skpoll_t;
|
typedef struct pollfd skpoll_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
SOCK_TCP,
|
SOCK_TCP,
|
||||||
SOCK_UDP,
|
SOCK_UDP,
|
||||||
} sktype_t;
|
} sktype_e;
|
||||||
|
|
||||||
// == RAW SOCKETS ==========================================
|
|
||||||
|
|
||||||
// Initialize sockets, returns true on success
|
// Initialize sockets, returns true on success
|
||||||
bool skInit(void);
|
bool skInit(void);
|
||||||
|
|
@ -47,7 +24,7 @@ bool skInit(void);
|
||||||
bool skCleanup(void);
|
bool skCleanup(void);
|
||||||
|
|
||||||
// Opens a socket, check socket_t with skValid
|
// Opens a socket, check socket_t with skValid
|
||||||
socket_t skOpen(sktype_t type);
|
socket_t skOpen(sktype_e type);
|
||||||
// Opens a socket using 'protocol', options are
|
// Opens a socket using 'protocol', options are
|
||||||
// ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp
|
// ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp
|
||||||
// check socket_t with skValid
|
// check socket_t with skValid
|
||||||
|
|
@ -62,12 +39,12 @@ bool skIsValid(socket_t sock);
|
||||||
bool skClose(socket_t sock);
|
bool skClose(socket_t sock);
|
||||||
|
|
||||||
// Fill out a sk_addrin_t structure with "ip" and "port"
|
// Fill out a sk_addrin_t structure with "ip" and "port"
|
||||||
sk_addrin_t skAddrinInit(const char *ip, uint16_t port);
|
skaddrin_t skAddrinInit(const char *ip, uint16_t port);
|
||||||
|
|
||||||
// Associate a local address with a socket
|
// Associate a local address with a socket
|
||||||
bool skBind(socket_t sock, const char *ip, uint16_t port);
|
bool skBind(socket_t sock, const char *ip, uint16_t port);
|
||||||
// Associate a local address with a socket
|
// Associate a local address with a socket
|
||||||
bool skBindPro(socket_t sock, const sk_addr_t *name, sk_len_t namelen);
|
bool skBindPro(socket_t sock, const skaddr_t *name, int namelen);
|
||||||
|
|
||||||
// Place a socket in a state in which it is listening for an incoming connection
|
// Place a socket in a state in which it is listening for an incoming connection
|
||||||
bool skListen(socket_t sock);
|
bool skListen(socket_t sock);
|
||||||
|
|
@ -77,29 +54,29 @@ bool skListenPro(socket_t sock, int backlog);
|
||||||
// Permits an incoming connection attempt on a socket
|
// Permits an incoming connection attempt on a socket
|
||||||
socket_t skAccept(socket_t sock);
|
socket_t skAccept(socket_t sock);
|
||||||
// Permits an incoming connection attempt on a socket
|
// Permits an incoming connection attempt on a socket
|
||||||
socket_t skAcceptPro(socket_t sock, sk_addr_t *addr, sk_len_t *addrlen);
|
socket_t skAcceptPro(socket_t sock, skaddr_t *addr, int *addrlen);
|
||||||
|
|
||||||
// Connects to a server (e.g. "127.0.0.1" or "google.com") with a port(e.g. 1234), returns true on success
|
// Connects to a server (e.g. "127.0.0.1" or "google.com") with a port(e.g. 1234), returns true on success
|
||||||
bool skConnect(socket_t sock, const char *server, unsigned short server_port);
|
bool skConnect(socket_t sock, const char *server, unsigned short server_port);
|
||||||
// Connects to a server, returns true on success
|
// Connects to a server, returns true on success
|
||||||
bool skConnectPro(socket_t sock, const sk_addr_t *name, sk_len_t namelen);
|
bool skConnectPro(socket_t sock, const skaddr_t *name, int namelen);
|
||||||
|
|
||||||
// Sends data on a socket, returns true on success
|
// Sends data on a socket, returns true on success
|
||||||
int skSend(socket_t sock, const void *buf, int len);
|
int skSend(socket_t sock, const void *buf, int len);
|
||||||
// Sends data on a socket, returns true on success
|
// Sends data on a socket, returns true on success
|
||||||
int skSendPro(socket_t sock, const void *buf, int len, int flags);
|
int skSendPro(socket_t sock, const void *buf, int len, int flags);
|
||||||
// Sends data to a specific destination
|
// Sends data to a specific destination
|
||||||
int skSendTo(socket_t sock, const void *buf, int len, const sk_addrin_t *to);
|
int skSendTo(socket_t sock, const void *buf, int len, const skaddrin_t *to);
|
||||||
// Sends data to a specific destination
|
// Sends data to a specific destination
|
||||||
int skSendToPro(socket_t sock, const void *buf, int len, int flags, const sk_addr_t *to, int tolen);
|
int skSendToPro(socket_t sock, const void *buf, int len, int flags, const skaddr_t *to, int tolen);
|
||||||
// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error
|
// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error
|
||||||
int skReceive(socket_t sock, void *buf, int len);
|
int skReceive(socket_t sock, void *buf, int len);
|
||||||
// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error
|
// Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error
|
||||||
int skReceivePro(socket_t sock, void *buf, int len, int flags);
|
int skReceivePro(socket_t sock, void *buf, int len, int flags);
|
||||||
// Receives a datagram and stores the source address.
|
// Receives a datagram and stores the source address.
|
||||||
int skReceiveFrom(socket_t sock, void *buf, int len, sk_addrin_t *from);
|
int skReceiveFrom(socket_t sock, void *buf, int len, skaddrin_t *from);
|
||||||
// Receives a datagram and stores the source address.
|
// Receives a datagram and stores the source address.
|
||||||
int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, sk_addr_t *from, sk_len_t *fromlen);
|
int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, skaddr_t *from, int *fromlen);
|
||||||
|
|
||||||
// Wait for an event on some sockets
|
// Wait for an event on some sockets
|
||||||
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout);
|
int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout);
|
||||||
|
|
@ -108,14 +85,3 @@ int skPoll(skpoll_t *to_poll, int num_to_poll, int timeout);
|
||||||
int skGetError(void);
|
int skGetError(void);
|
||||||
// Returns a human-readable string from a skGetError
|
// Returns a human-readable string from a skGetError
|
||||||
const char *skGetErrorString(void);
|
const char *skGetErrorString(void);
|
||||||
|
|
||||||
// == UDP SOCKETS ==========================================
|
|
||||||
|
|
||||||
typedef socket_t udpsock_t;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
} // extern "C"
|
|
||||||
#endif
|
|
||||||
|
|
|
||||||
1930
colla/stb/stb_sprintf.h
Normal file
1930
colla/stb/stb_sprintf.h
Normal file
File diff suppressed because it is too large
Load diff
753
colla/str.c
753
colla/str.c
|
|
@ -1,291 +1,259 @@
|
||||||
#include "str.h"
|
#include "str.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include "warnings/colla_warn_beg.h"
|
||||||
#include <stdlib.h>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
|
#include "arena.h"
|
||||||
|
#include "format.h"
|
||||||
#include "tracelog.h"
|
#include "tracelog.h"
|
||||||
#include "strstream.h"
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#if COLLA_WIN
|
||||||
#include "win32_slim.h"
|
#include <stringapiset.h>
|
||||||
#else
|
|
||||||
#include <iconv.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef min
|
|
||||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// == STR_T ========================================================
|
// == STR_T ========================================================
|
||||||
|
|
||||||
str_t strInit(void) {
|
str_t strInit(arena_t *arena, const char *buf) {
|
||||||
return (str_t) {
|
return buf ? strInitLen(arena, buf, strlen(buf)) : (str_t){0};
|
||||||
.buf = NULL,
|
}
|
||||||
.len = 0
|
|
||||||
|
str_t strInitLen(arena_t *arena, const char *buf, usize len) {
|
||||||
|
if (!buf || !len) return (str_t){0};
|
||||||
|
|
||||||
|
str_t out = {
|
||||||
|
.buf = alloc(arena, char, len + 1),
|
||||||
|
.len = len
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
str_t strFromStr(const char *cstr) {
|
memcpy(out.buf, buf, len);
|
||||||
return cstr ? strFromBuf(cstr, strlen(cstr)) : strInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t strFromView(strview_t view) {
|
|
||||||
return strFromBuf(view.buf, view.len);
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t strFromBuf(const char *buf, usize len) {
|
|
||||||
if (!buf) return strInit();
|
|
||||||
str_t str;
|
|
||||||
str.len = len;
|
|
||||||
str.buf = (char *)malloc(len + 1);
|
|
||||||
memcpy(str.buf, buf, len);
|
|
||||||
str.buf[len] = '\0';
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t strFromFmt(const char *fmt, ...) {
|
|
||||||
str_ostream_t out = ostrInit();
|
|
||||||
va_list va;
|
|
||||||
va_start(va, fmt);
|
|
||||||
ostrPrintfV(&out, fmt, va);
|
|
||||||
va_end(va);
|
|
||||||
return ostrAsStr(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
void strFree(str_t ctx) {
|
|
||||||
free(ctx.buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t strFromWCHAR(const wchar_t *src, usize len) {
|
|
||||||
if(len == 0) len = wcslen(src);
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
// TODO CP_ACP should be CP_UTF8 but if i put CP_UTF8 it doesn't work??
|
|
||||||
int result_len = WideCharToMultiByte(
|
|
||||||
CP_ACP, 0,
|
|
||||||
src, (int)len,
|
|
||||||
NULL, 0,
|
|
||||||
NULL, NULL
|
|
||||||
);
|
|
||||||
char *buf = (char *)malloc(result_len + 1);
|
|
||||||
if(buf) {
|
|
||||||
WideCharToMultiByte(
|
|
||||||
CP_ACP, 0,
|
|
||||||
src, (int)len,
|
|
||||||
buf, result_len,
|
|
||||||
NULL, NULL
|
|
||||||
);
|
|
||||||
buf[result_len] = '\0';
|
|
||||||
}
|
|
||||||
return (str_t) {
|
|
||||||
.buf = buf,
|
|
||||||
.len = result_len
|
|
||||||
};
|
|
||||||
#else
|
|
||||||
usize actual_len = len * sizeof(wchar_t);
|
|
||||||
|
|
||||||
usize dest_len = len * 6;
|
|
||||||
char *dest = (char *)malloc(dest_len);
|
|
||||||
|
|
||||||
iconv_t cd = iconv_open("UTF-8", "WCHAR_T");
|
|
||||||
assert(cd);
|
|
||||||
|
|
||||||
usize dest_left = dest_len;
|
|
||||||
char *dest_temp = dest;
|
|
||||||
char *src_temp = (char*)src;
|
|
||||||
usize lost = iconv(cd, &src_temp, &actual_len, &dest_temp, &dest_left);
|
|
||||||
assert(lost != ((usize)-1));
|
|
||||||
(void)lost;
|
|
||||||
|
|
||||||
dest_len -= dest_left;
|
|
||||||
dest = (char *)realloc(dest, dest_len + 1);
|
|
||||||
dest[dest_len] = '\0';
|
|
||||||
|
|
||||||
iconv_close(cd);
|
|
||||||
|
|
||||||
return (str_t){
|
|
||||||
.buf = dest,
|
|
||||||
.len = dest_len
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
wchar_t *strToWCHAR(str_t ctx) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
UINT codepage = CP_ACP;
|
|
||||||
int len = MultiByteToWideChar(
|
|
||||||
codepage, 0,
|
|
||||||
ctx.buf, (int)ctx.len,
|
|
||||||
NULL, 0
|
|
||||||
);
|
|
||||||
wchar_t *str = (wchar_t *)malloc(sizeof(wchar_t) * (len + 1));
|
|
||||||
if(!str) return NULL;
|
|
||||||
len = MultiByteToWideChar(
|
|
||||||
codepage, 0,
|
|
||||||
ctx.buf, (int)ctx.len,
|
|
||||||
str, len
|
|
||||||
);
|
|
||||||
str[len] = '\0';
|
|
||||||
return str;
|
|
||||||
#else
|
|
||||||
usize dest_len = ctx.len * sizeof(wchar_t);
|
|
||||||
char *dest = (char *)malloc(dest_len);
|
|
||||||
|
|
||||||
iconv_t cd = iconv_open("WCHAR_T", "UTF-8");
|
|
||||||
assert(cd);
|
|
||||||
|
|
||||||
usize dest_left = dest_len;
|
|
||||||
char *dest_temp = dest;
|
|
||||||
char *src_temp = ctx.buf;
|
|
||||||
usize lost = iconv(cd, &src_temp, &ctx.len, &dest_temp, &dest_left);
|
|
||||||
assert(lost != ((usize)-1));
|
|
||||||
(void)lost;
|
|
||||||
|
|
||||||
dest_len -= dest_left;
|
|
||||||
dest = (char *)realloc(dest, dest_len + 1);
|
|
||||||
dest[dest_len] = '\0';
|
|
||||||
|
|
||||||
iconv_close(cd);
|
|
||||||
|
|
||||||
return (wchar_t *)dest;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t strDup(str_t ctx) {
|
|
||||||
return strFromBuf(ctx.buf, ctx.len);
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t strMove(str_t *ctx) {
|
|
||||||
str_t out = *ctx;
|
|
||||||
ctx->buf = NULL;
|
|
||||||
ctx->len = 0;
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
strview_t strGetView(str_t ctx) {
|
str_t strInitView(arena_t *arena, strview_t view) {
|
||||||
return (strview_t) {
|
return strInitLen(arena, view.buf, view.len);
|
||||||
.buf = ctx.buf,
|
|
||||||
.len = ctx.len
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
char *strBegin(str_t ctx) {
|
str_t strFmt(arena_t *arena, const char *fmt, ...) {
|
||||||
return ctx.buf;
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
str_t out = strFmtv(arena, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *strEnd(str_t ctx) {
|
str_t strFmtv(arena_t *arena, const char *fmt, va_list args) {
|
||||||
return ctx.buf ? ctx.buf + ctx.len : NULL;
|
va_list vcopy;
|
||||||
|
va_copy(vcopy, args);
|
||||||
|
int len = fmtBufferv(NULL, 0, fmt, vcopy);
|
||||||
|
va_end(vcopy);
|
||||||
|
|
||||||
|
char *buffer = alloc(arena, char, len + 1);
|
||||||
|
fmtBufferv(buffer, len + 1, fmt, args);
|
||||||
|
|
||||||
|
return (str_t){ .buf = buffer, .len = (usize)len };
|
||||||
}
|
}
|
||||||
|
|
||||||
char strBack(str_t ctx) {
|
str_t strFromWChar(arena_t *arena, const wchar_t *src, usize srclen) {
|
||||||
return ctx.buf ? ctx.buf[ctx.len - 1] : '\0';
|
if (!src) return (str_t){0};
|
||||||
|
if (!srclen) srclen = wcslen(src);
|
||||||
|
|
||||||
|
str_t out = {0};
|
||||||
|
|
||||||
|
#if COLLA_WIN
|
||||||
|
int outlen = WideCharToMultiByte(
|
||||||
|
CP_UTF8, 0,
|
||||||
|
src, (int)srclen,
|
||||||
|
NULL, 0,
|
||||||
|
NULL, NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (outlen == 0) {
|
||||||
|
unsigned long error = GetLastError();
|
||||||
|
if (error == ERROR_NO_UNICODE_TRANSLATION) {
|
||||||
|
err("couldn't translate wide string (%S) to utf8, no unicode translation", src);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
err("couldn't translate wide string (%S) to utf8, %u", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (str_t){0};
|
||||||
|
}
|
||||||
|
|
||||||
|
out.buf = alloc(arena, char, outlen + 1);
|
||||||
|
WideCharToMultiByte(
|
||||||
|
CP_UTF8, 0,
|
||||||
|
src, (int)srclen,
|
||||||
|
out.buf, outlen,
|
||||||
|
NULL, NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
#elif COLLA_LIN
|
||||||
|
fatal("strFromWChar not implemented yet!");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool strEquals(str_t a, str_t b) {
|
||||||
|
return strCompare(a, b) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int strCompare(str_t a, str_t b) {
|
||||||
|
return a.len == b.len ?
|
||||||
|
memcmp(a.buf, b.buf, a.len) :
|
||||||
|
a.len - (int)b.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
str_t strDup(arena_t *arena, str_t src) {
|
||||||
|
return strInitLen(arena, src.buf, src.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool strIsEmpty(str_t ctx) {
|
bool strIsEmpty(str_t ctx) {
|
||||||
return ctx.len == 0;
|
return ctx.len == 0 || ctx.buf == NULL;
|
||||||
}
|
|
||||||
|
|
||||||
void strSwap(str_t *ctx, str_t *other) {
|
|
||||||
char *buf = other->buf;
|
|
||||||
usize len = other->len;
|
|
||||||
other->buf = ctx->buf;
|
|
||||||
other->len = ctx->len;
|
|
||||||
ctx->buf = buf;
|
|
||||||
ctx->len = len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void strReplace(str_t *ctx, char from, char to) {
|
void strReplace(str_t *ctx, char from, char to) {
|
||||||
for(usize i = 0; i < ctx->len; ++i) {
|
if (!ctx) return;
|
||||||
if(ctx->buf[i] == from) {
|
char *buf = ctx->buf;
|
||||||
ctx->buf[i] = to;
|
for (usize i = 0; i < ctx->len; ++i) {
|
||||||
}
|
buf[i] = buf[i] == from ? to : buf[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
str_t strSubstr(str_t ctx, usize from, usize to) {
|
strview_t strSub(str_t ctx, usize from, usize to) {
|
||||||
if(strIsEmpty(ctx)) return strInit();
|
|
||||||
if (to > ctx.len) to = ctx.len;
|
if (to > ctx.len) to = ctx.len;
|
||||||
if (from > to) from = to;
|
if (from > to) from = to;
|
||||||
return strFromBuf(ctx.buf + from, to - from);
|
return (strview_t){ ctx.buf + from, to - from };
|
||||||
}
|
|
||||||
|
|
||||||
strview_t strSubview(str_t ctx, usize from, usize to) {
|
|
||||||
if(strIsEmpty(ctx)) return strvInit(NULL);
|
|
||||||
if (to > ctx.len) to = ctx.len;
|
|
||||||
if (from > to) from = to;
|
|
||||||
return strvInitLen(ctx.buf + from, to - from);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void strLower(str_t *ctx) {
|
void strLower(str_t *ctx) {
|
||||||
for(usize i = 0; i < ctx->len; ++i) {
|
char *buf = ctx->buf;
|
||||||
ctx->buf[i] = (char)tolower(ctx->buf[i]);
|
for (usize i = 0; i < ctx->len; ++i) {
|
||||||
|
buf[i] = (buf[i] >= 'A' && buf[i] <= 'Z') ?
|
||||||
|
buf[i] += 'a' - 'A' :
|
||||||
|
buf[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
str_t strToLower(str_t ctx) {
|
|
||||||
str_t str = strDup(ctx);
|
|
||||||
strLower(&str);
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
void strUpper(str_t *ctx) {
|
void strUpper(str_t *ctx) {
|
||||||
for(usize i = 0; i < ctx->len; ++i) {
|
char *buf = ctx->buf;
|
||||||
ctx->buf[i] = (char)toupper(ctx->buf[i]);
|
for (usize i = 0; i < ctx->len; ++i) {
|
||||||
|
buf[i] = (buf[i] >= 'a' && buf[i] <= 'z') ?
|
||||||
|
buf[i] -= 'a' - 'A' :
|
||||||
|
buf[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
str_t strToUpper(str_t ctx) {
|
str_t strToLower(arena_t *arena, str_t ctx) {
|
||||||
str_t str = strDup(ctx);
|
strLower(&ctx);
|
||||||
strUpper(&str);
|
return strDup(arena, ctx);
|
||||||
return str;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
str_t strToUpper(arena_t *arena, str_t ctx) {
|
||||||
|
strUpper(&ctx);
|
||||||
|
return strDup(arena, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// == STRVIEW_T ====================================================
|
// == STRVIEW_T ====================================================
|
||||||
|
|
||||||
strview_t strvInit(const char *cstr) {
|
strview_t strvInit(const char *cstr) {
|
||||||
return strvInitLen(cstr, cstr ? strlen(cstr) : 0);
|
return (strview_t){
|
||||||
}
|
.buf = cstr,
|
||||||
|
.len = cstr ? strlen(cstr) : 0,
|
||||||
strview_t strvInitStr(str_t str) {
|
|
||||||
return strvInitLen(str.buf, str.len);
|
|
||||||
}
|
|
||||||
|
|
||||||
strview_t strvInitLen(const char *buf, usize size) {
|
|
||||||
return (strview_t) {
|
|
||||||
.buf = buf,
|
|
||||||
.len = size
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
char strvFront(strview_t ctx) {
|
strview_t strvInitLen(const char *buf, usize size) {
|
||||||
return ctx.buf[0];
|
return (strview_t){
|
||||||
|
.buf = buf,
|
||||||
|
.len = size,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
char strvBack(strview_t ctx) {
|
strview_t strvInitStr(str_t str) {
|
||||||
return ctx.buf[ctx.len - 1];
|
return (strview_t){
|
||||||
|
.buf = str.buf,
|
||||||
|
.len = str.len
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *strvBegin(strview_t ctx) {
|
|
||||||
return ctx.buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *strvEnd(strview_t ctx) {
|
|
||||||
return ctx.buf + ctx.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool strvIsEmpty(strview_t ctx) {
|
bool strvIsEmpty(strview_t ctx) {
|
||||||
return ctx.len == 0;
|
return ctx.len == 0 || !ctx.buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool strvEquals(strview_t a, strview_t b) {
|
||||||
|
return strvCompare(a, b) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int strvCompare(strview_t a, strview_t b) {
|
||||||
|
return a.len == b.len ?
|
||||||
|
memcmp(a.buf, b.buf, a.len) :
|
||||||
|
a.len - (int)b.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen) {
|
||||||
|
wchar_t *out = NULL;
|
||||||
|
int len = 0;
|
||||||
|
|
||||||
|
if (strvIsEmpty(ctx)) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if COLLA_WIN
|
||||||
|
len = MultiByteToWideChar(
|
||||||
|
CP_UTF8, 0,
|
||||||
|
ctx.buf, (int)ctx.len,
|
||||||
|
NULL, 0
|
||||||
|
);
|
||||||
|
|
||||||
|
if (len == 0) {
|
||||||
|
unsigned long error = GetLastError();
|
||||||
|
if (error == ERROR_NO_UNICODE_TRANSLATION) {
|
||||||
|
err("couldn't translate string (%v) to a wide string, no unicode translation", ctx);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
err("couldn't translate string (%v) to a wide string, %u", ctx, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
out = alloc(arena, wchar_t, len + 1);
|
||||||
|
|
||||||
|
MultiByteToWideChar(
|
||||||
|
CP_UTF8, 0,
|
||||||
|
ctx.buf, (int)ctx.len,
|
||||||
|
out, len
|
||||||
|
);
|
||||||
|
|
||||||
|
#elif COLLA_LIN
|
||||||
|
fatal("strFromWChar not implemented yet!");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
error:
|
||||||
|
if (outlen) {
|
||||||
|
*outlen = (usize)len;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
TCHAR *strvToTChar(arena_t *arena, strview_t str) {
|
||||||
|
#if UNICODE
|
||||||
|
return strvToWChar(arena, str, NULL);
|
||||||
|
#else
|
||||||
|
char *cstr = alloc(arena, char, str.len + 1);
|
||||||
|
memcpy(cstr, str.buf, str.len);
|
||||||
|
return cstr;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
strview_t strvRemovePrefix(strview_t ctx, usize n) {
|
strview_t strvRemovePrefix(strview_t ctx, usize n) {
|
||||||
if (n > ctx.len) n = ctx.len;
|
if (n > ctx.len) n = ctx.len;
|
||||||
return (strview_t){
|
return (strview_t){
|
||||||
.buf = ctx.buf + n,
|
.buf = ctx.buf + n,
|
||||||
.len = ctx.len - n
|
.len = ctx.len - n,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -293,7 +261,7 @@ strview_t strvRemoveSuffix(strview_t ctx, usize n) {
|
||||||
if (n > ctx.len) n = ctx.len;
|
if (n > ctx.len) n = ctx.len;
|
||||||
return (strview_t){
|
return (strview_t){
|
||||||
.buf = ctx.buf,
|
.buf = ctx.buf,
|
||||||
.len = ctx.len - n
|
.len = ctx.len - n,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -303,334 +271,115 @@ strview_t strvTrim(strview_t ctx) {
|
||||||
|
|
||||||
strview_t strvTrimLeft(strview_t ctx) {
|
strview_t strvTrimLeft(strview_t ctx) {
|
||||||
strview_t out = ctx;
|
strview_t out = ctx;
|
||||||
for (usize i = 0; i < ctx.len && isspace(ctx.buf[i]); ++i) {
|
for (usize i = 0; i < ctx.len; ++i) {
|
||||||
++out.buf;
|
char c = ctx.buf[i];
|
||||||
--out.len;
|
if (c != ' ' || c < '\t' || c > '\r') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
out.buf++;
|
||||||
|
out.len--;
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
strview_t strvTrimRight(strview_t ctx) {
|
strview_t strvTrimRight(strview_t ctx) {
|
||||||
strview_t out = ctx;
|
strview_t out = ctx;
|
||||||
for (isize i = ctx.len - 1; i >= 0 && isspace(ctx.buf[i]); --i) {
|
for (isize i = ctx.len - 1; i >= 0; --i) {
|
||||||
--out.len;
|
char c = ctx.buf[i];
|
||||||
|
if (c != ' ' || c < '\t' || c > '\r') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
out.len--;
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
str_t strvCopy(strview_t ctx) {
|
|
||||||
return strFromView(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t strvCopyN(strview_t ctx, usize count, usize from) {
|
|
||||||
usize sz = ctx.len + 1 - from;
|
|
||||||
count = min(count, sz);
|
|
||||||
return strFromBuf(ctx.buf + from, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
usize strvCopyBuf(strview_t ctx, char *buf, usize len, usize from) {
|
|
||||||
usize sz = ctx.len + 1 - from;
|
|
||||||
len = min(len, sz);
|
|
||||||
memcpy(buf, ctx.buf + from, len);
|
|
||||||
buf[len - 1] = '\0';
|
|
||||||
return len - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
strview_t strvSub(strview_t ctx, usize from, usize to) {
|
strview_t strvSub(strview_t ctx, usize from, usize to) {
|
||||||
if (to > ctx.len) to = ctx.len;
|
if (to > ctx.len) to = ctx.len;
|
||||||
if (from > to) from = to;
|
if (from > to) from = to;
|
||||||
return strvInitLen(ctx.buf + from, to - from);
|
return (strview_t){ ctx.buf + from, to - from };
|
||||||
}
|
|
||||||
|
|
||||||
int strvCompare(strview_t ctx, strview_t other) {
|
|
||||||
if(ctx.len < other.len) return -1;
|
|
||||||
if(ctx.len > other.len) return 1;
|
|
||||||
return memcmp(ctx.buf, other.buf, ctx.len);
|
|
||||||
}
|
|
||||||
|
|
||||||
int strvICompare(strview_t ctx, strview_t other) {
|
|
||||||
if(ctx.len < other.len) return -1;
|
|
||||||
if(ctx.len > other.len) return 1;
|
|
||||||
for(usize i = 0; i < ctx.len; ++i) {
|
|
||||||
int a = tolower(ctx.buf[i]);
|
|
||||||
int b = tolower(other.buf[i]);
|
|
||||||
if(a != b) return a - b;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool strvStartsWith(strview_t ctx, char c) {
|
bool strvStartsWith(strview_t ctx, char c) {
|
||||||
return strvFront(ctx) == c;
|
return ctx.len > 0 && ctx.buf[0] == c;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool strvStartsWithView(strview_t ctx, strview_t view) {
|
bool strvStartsWithView(strview_t ctx, strview_t view) {
|
||||||
if(ctx.len < view.len) return false;
|
return ctx.len >= view.len && memcmp(ctx.buf, view.buf, view.len) == 0;
|
||||||
return memcmp(ctx.buf, view.buf, view.len) == 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool strvEndsWith(strview_t ctx, char c) {
|
bool strvEndsWith(strview_t ctx, char c) {
|
||||||
return strvBack(ctx) == c;
|
return ctx.len > 0 && ctx.buf[ctx.len - 1] == c;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool strvEndsWithView(strview_t ctx, strview_t view) {
|
bool strvEndsWithView(strview_t ctx, strview_t view) {
|
||||||
if(ctx.len < view.len) return false;
|
return ctx.len >= view.len && memcmp(ctx.buf + ctx.len, view.buf, view.len) == 0;
|
||||||
return memcmp(ctx.buf + ctx.len - view.len, view.buf, view.len) == 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool strvContains(strview_t ctx, char c) {
|
bool strvContains(strview_t ctx, char c) {
|
||||||
for(usize i = 0; i < ctx.len; ++i) {
|
for(usize i = 0; i < ctx.len; ++i) {
|
||||||
if(ctx.buf[i] == c) return true;
|
if(ctx.buf[i] == c) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool strvContainsView(strview_t ctx, strview_t view) {
|
bool strvContainsView(strview_t ctx, strview_t view) {
|
||||||
if(ctx.len < view.len) return false;
|
if (ctx.len < view.len) return false;
|
||||||
usize end = ctx.len - view.len;
|
usize end = ctx.len - view.len;
|
||||||
for(usize i = 0; i < end; ++i) {
|
for (usize i = 0; i < end; ++i) {
|
||||||
if(memcmp(ctx.buf + i, view.buf, view.len) == 0) return true;
|
if (memcmp(ctx.buf + i, view.buf, view.len) == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
usize strvFind(strview_t ctx, char c, usize from) {
|
usize strvFind(strview_t ctx, char c, usize from) {
|
||||||
for(usize i = from; i < ctx.len; ++i) {
|
for (usize i = from; i < ctx.len; ++i) {
|
||||||
if(ctx.buf[i] == c) return i;
|
if (ctx.buf[i] == c) {
|
||||||
}
|
|
||||||
return SIZE_MAX;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize strvFindView(strview_t ctx, strview_t view, usize from) {
|
|
||||||
if(ctx.len < view.len) return SIZE_MAX;
|
|
||||||
usize end = ctx.len - view.len;
|
|
||||||
for(usize i = from; i < end; ++i) {
|
|
||||||
if(memcmp(ctx.buf + i, view.buf, view.len) == 0) return i;
|
|
||||||
}
|
|
||||||
return SIZE_MAX;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize strvRFind(strview_t ctx, char c, usize from) {
|
|
||||||
if(from >= ctx.len) {
|
|
||||||
from = ctx.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
from = ctx.len - from;
|
|
||||||
|
|
||||||
const char *buf = ctx.buf + from;
|
|
||||||
for(; buf >= ctx.buf; --buf) {
|
|
||||||
if(*buf == c) return (buf - ctx.buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
return SIZE_MAX;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize strvRFindView(strview_t ctx, strview_t view, usize from) {
|
|
||||||
if(view.len > ctx.len) {
|
|
||||||
return SIZE_MAX;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(from > ctx.len) {
|
|
||||||
from = ctx.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
from = ctx.len - from;
|
|
||||||
from -= view.len;
|
|
||||||
|
|
||||||
const char *buf = ctx.buf + from;
|
|
||||||
for(; buf >= ctx.buf; --buf) {
|
|
||||||
if(memcmp(buf, view.buf, view.len) == 0) return (buf - ctx.buf);
|
|
||||||
}
|
|
||||||
return SIZE_MAX;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize strvFindFirstOf(strview_t ctx, strview_t view, usize from) {
|
|
||||||
if(ctx.len < view.len) return SIZE_MAX;
|
|
||||||
for(usize i = from; i < ctx.len; ++i) {
|
|
||||||
for(usize j = 0; j < view.len; ++j) {
|
|
||||||
if(ctx.buf[i] == view.buf[j]) return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return SIZE_MAX;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize strvFindLastOf(strview_t ctx, strview_t view, usize from) {
|
|
||||||
if(from >= ctx.len) {
|
|
||||||
from = ctx.len - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *buf = ctx.buf + from;
|
|
||||||
for(; buf >= ctx.buf; --buf) {
|
|
||||||
for(usize j = 0; j < view.len; ++j) {
|
|
||||||
if(*buf == view.buf[j]) return (buf - ctx.buf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return SIZE_MAX;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize strvFindFirstNot(strview_t ctx, char c, usize from) {
|
|
||||||
usize end = ctx.len - 1;
|
|
||||||
for(usize i = from; i < end; ++i) {
|
|
||||||
if(ctx.buf[i] != c) return i;
|
|
||||||
}
|
|
||||||
return SIZE_MAX;
|
|
||||||
}
|
|
||||||
|
|
||||||
usize strvFindFirstNotOf(strview_t ctx, strview_t view, usize from) {
|
|
||||||
for(usize i = from; i < ctx.len; ++i) {
|
|
||||||
if(!strvContains(view, ctx.buf[i])) {
|
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return SIZE_MAX;
|
return STR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
usize strvFindLastNot(strview_t ctx, char c, usize from) {
|
usize strvFindView(strview_t ctx, strview_t view, usize from) {
|
||||||
if(from >= ctx.len) {
|
if (ctx.len < view.len) return STR_NONE;
|
||||||
from = ctx.len - 1;
|
usize end = ctx.len - view.len;
|
||||||
}
|
for (usize i = 0; i < end; ++i) {
|
||||||
|
if (memcmp(ctx.buf + i, view.buf, view.len) == 0) {
|
||||||
const char *buf = ctx.buf + from;
|
return i;
|
||||||
for(; buf >= ctx.buf; --buf) {
|
|
||||||
if(*buf != c) {
|
|
||||||
return buf - ctx.buf;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return STR_NONE;
|
||||||
return SIZE_MAX;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
usize strvFindLastNotOf(strview_t ctx, strview_t view, usize from) {
|
usize strvRFind(strview_t ctx, char c, usize from_right) {
|
||||||
if(from >= ctx.len) {
|
if (from_right > ctx.len) from_right = ctx.len;
|
||||||
from = ctx.len - 1;
|
isize end = (isize)(ctx.len - from_right);
|
||||||
}
|
for (isize i = end; i >= 0; --i) {
|
||||||
|
if (ctx.buf[i] == c) {
|
||||||
const char *buf = ctx.buf + from;
|
return (usize)i;
|
||||||
for(; buf >= ctx.buf; --buf) {
|
|
||||||
if(!strvContains(view, *buf)) {
|
|
||||||
return buf - ctx.buf;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return STR_NONE;
|
||||||
return SIZE_MAX;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef STR_TESTING
|
usize strvRFindView(strview_t ctx, strview_t view, usize from_right) {
|
||||||
#include <stdio.h>
|
if (from_right > ctx.len) from_right = ctx.len;
|
||||||
#include "tracelog.h"
|
isize end = (isize)(ctx.len - from_right);
|
||||||
|
if (end < view.len) return STR_NONE;
|
||||||
void strTest(void) {
|
for (isize i = end - view.len; i >= 0; --i) {
|
||||||
str_t s;
|
if (memcmp(ctx.buf + i, view.buf, view.len) == 0) {
|
||||||
debug("== testing init =================");
|
return (usize)i;
|
||||||
{
|
|
||||||
s = strInit();
|
|
||||||
printf("%s %zu\n", s.buf, s.len);
|
|
||||||
strFree(&s);
|
|
||||||
s = strInitStr("hello world");
|
|
||||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
|
||||||
strFree(&s);
|
|
||||||
uint8 buf[] = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' };
|
|
||||||
s = strFromBuf((char *)buf, sizeof(buf));
|
|
||||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
|
||||||
strFree(&s);
|
|
||||||
}
|
}
|
||||||
debug("== testing view =================");
|
|
||||||
{
|
|
||||||
s = strInitStr("hello world");
|
|
||||||
strview_t view = strGetView(&s);
|
|
||||||
printf("\"%.*s\" %zu\n", (int)view.len, view.buf, view.len);
|
|
||||||
strFree(&s);
|
|
||||||
}
|
}
|
||||||
debug("== testing begin/end ============");
|
return STR_NONE;
|
||||||
{
|
|
||||||
s = strInitStr("hello world");
|
|
||||||
char *beg = strBegin(&s);
|
|
||||||
char *end = strEnd(&s);
|
|
||||||
printf("[ ");
|
|
||||||
for(; beg < end; ++beg) {
|
|
||||||
printf("%c ", *beg);
|
|
||||||
}
|
|
||||||
printf("]\n");
|
|
||||||
strFree(&s);
|
|
||||||
}
|
|
||||||
debug("== testing back/isempty =========");
|
|
||||||
{
|
|
||||||
s = strInitStr("hello world");
|
|
||||||
printf("[ ");
|
|
||||||
while(!strIsEmpty(&s)) {
|
|
||||||
printf("%c ", strBack(&s));
|
|
||||||
strPop(&s);
|
|
||||||
}
|
|
||||||
printf("]\n");
|
|
||||||
strFree(&s);
|
|
||||||
}
|
|
||||||
debug("== testing append ===============");
|
|
||||||
{
|
|
||||||
s = strInitStr("hello ");
|
|
||||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
|
||||||
strAppend(&s, "world");
|
|
||||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
|
||||||
strAppendView(&s, strvInit(", how is it "));
|
|
||||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
|
||||||
uint8 buf[] = { 'g', 'o', 'i', 'n', 'g' };
|
|
||||||
strAppendBuf(&s, (char*)buf, sizeof(buf));
|
|
||||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
|
||||||
strAppendChars(&s, '?', 2);
|
|
||||||
printf("\"%s\" %zu\n", s.buf, s.len);
|
|
||||||
strFree(&s);
|
|
||||||
}
|
|
||||||
debug("== testing push/pop =============");
|
|
||||||
{
|
|
||||||
s = strInit();
|
|
||||||
str_t s2 = strInitStr("hello world");
|
|
||||||
|
|
||||||
printf("%-14s %-14s\n", "s", "s2");
|
|
||||||
printf("----------------------------\n");
|
|
||||||
while(!strIsEmpty(&s2)) {
|
|
||||||
printf("%-14s %-14s\n", s.buf, s2.buf);
|
|
||||||
strPush(&s, strPop(&s2));
|
|
||||||
}
|
|
||||||
printf("%-14s %-14s\n", s.buf, s2.buf);
|
|
||||||
|
|
||||||
strFree(&s);
|
|
||||||
strFree(&s2);
|
|
||||||
}
|
|
||||||
debug("== testing swap =================");
|
|
||||||
{
|
|
||||||
s = strInitStr("hello");
|
|
||||||
str_t s2 = strInitStr("world");
|
|
||||||
printf("%-8s %-8s\n", "s", "s2");
|
|
||||||
printf("----------------\n");
|
|
||||||
printf("%-8s %-8s\n", s.buf, s2.buf);
|
|
||||||
strSwap(&s, &s2);
|
|
||||||
printf("%-8s %-8s\n", s.buf, s2.buf);
|
|
||||||
|
|
||||||
strFree(&s);
|
|
||||||
strFree(&s2);
|
|
||||||
}
|
|
||||||
debug("== testing substr ===============");
|
|
||||||
{
|
|
||||||
s = strInitStr("hello world");
|
|
||||||
printf("s: %s\n", s.buf);
|
|
||||||
|
|
||||||
printf("-- string\n");
|
|
||||||
str_t s2 = strSubstr(&s, 0, 5);
|
|
||||||
printf("0..5: \"%s\"\n", s2.buf);
|
|
||||||
strFree(&s2);
|
|
||||||
|
|
||||||
s2 = strSubstr(&s, 5, SIZE_MAX);
|
|
||||||
printf("6..SIZE_MAX: \"%s\"\n", s2.buf);
|
|
||||||
strFree(&s2);
|
|
||||||
|
|
||||||
printf("-- view\n");
|
|
||||||
strview_t v = strSubview(&s, 0, 5);
|
|
||||||
printf("0..5: \"%.*s\"\n", (int)v.len, v.buf);
|
|
||||||
v = strSubview(&s, 5, SIZE_MAX);
|
|
||||||
printf("6..SIZE_MAX: \"%.*s\"\n", (int)v.len, v.buf);
|
|
||||||
|
|
||||||
strFree(&s);
|
|
||||||
}
|
|
||||||
|
|
||||||
strFree(&s);
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
#include "warnings/colla_warn_beg.h"
|
||||||
|
|
||||||
|
#undef CP_UTF8
|
||||||
|
#undef ERROR_NO_UNICODE_TRANSLATION
|
||||||
129
colla/str.h
129
colla/str.h
|
|
@ -1,19 +1,15 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#include <stdarg.h> // va_list
|
||||||
extern "C" {
|
#include <string.h> // strlen
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <wchar.h>
|
|
||||||
|
|
||||||
#include "collatypes.h"
|
#include "collatypes.h"
|
||||||
|
|
||||||
#define STRV_NOT_FOUND SIZE_MAX
|
typedef struct arena_t arena_t;
|
||||||
|
|
||||||
typedef struct str_t {
|
#define STR_NONE SIZE_MAX
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
char *buf;
|
char *buf;
|
||||||
usize len;
|
usize len;
|
||||||
} str_t;
|
} str_t;
|
||||||
|
|
@ -25,73 +21,77 @@ typedef struct {
|
||||||
|
|
||||||
// == STR_T ========================================================
|
// == STR_T ========================================================
|
||||||
|
|
||||||
str_t strInit(void);
|
#define str__1(arena, x) \
|
||||||
str_t strFromStr(const char *cstr);
|
_Generic((x), \
|
||||||
str_t strFromView(strview_t view);
|
const char *: strInit, \
|
||||||
str_t strFromBuf(const char *buf, usize len);
|
char *: strInit, \
|
||||||
str_t strFromFmt(const char *fmt, ...);
|
strview_t: strInitView \
|
||||||
|
)(arena, x)
|
||||||
|
|
||||||
str_t strFromWCHAR(const wchar_t *src, usize len);
|
#define str__2(arena, cstr, clen) strInitLen(arena, cstr, clen)
|
||||||
wchar_t *strToWCHAR(str_t ctx);
|
#define str__impl(_1, _2, n, ...) str__##n
|
||||||
|
|
||||||
void strFree(str_t ctx);
|
// either:
|
||||||
str_t strDup(str_t ctx);
|
// arena_t arena, [const] char *cstr, [usize len]
|
||||||
str_t strMove(str_t *ctx);
|
// arena_t arena, strview_t view
|
||||||
|
#define str(arena, ...) str__impl(__VA_ARGS__, 2, 1, 0)(arena, __VA_ARGS__)
|
||||||
|
|
||||||
strview_t strGetView(str_t ctx);
|
str_t strInit(arena_t *arena, const char *buf);
|
||||||
|
str_t strInitLen(arena_t *arena, const char *buf, usize len);
|
||||||
|
str_t strInitView(arena_t *arena, strview_t view);
|
||||||
|
str_t strFmt(arena_t *arena, const char *fmt, ...);
|
||||||
|
str_t strFmtv(arena_t *arena, const char *fmt, va_list args);
|
||||||
|
|
||||||
char *strBegin(str_t ctx);
|
str_t strFromWChar(arena_t *arena, const wchar_t *src, usize srclen);
|
||||||
char *strEnd(str_t ctx);
|
|
||||||
char strBack(str_t ctx);
|
|
||||||
|
|
||||||
|
bool strEquals(str_t a, str_t b);
|
||||||
|
int strCompare(str_t a, str_t b);
|
||||||
|
|
||||||
|
str_t strDup(arena_t *arena, str_t src);
|
||||||
bool strIsEmpty(str_t ctx);
|
bool strIsEmpty(str_t ctx);
|
||||||
void strReplace(str_t *ctx, char from, char to);
|
|
||||||
|
|
||||||
|
void strReplace(str_t *ctx, char from, char to);
|
||||||
// if len == SIZE_MAX, copies until end
|
// if len == SIZE_MAX, copies until end
|
||||||
str_t strSubstr(str_t ctx, usize from, usize to);
|
strview_t strSub(str_t ctx, usize from, usize to);
|
||||||
// if len == SIZE_MAX, returns until end
|
|
||||||
strview_t strSubview(str_t ctx, usize from, usize to);
|
|
||||||
|
|
||||||
void strLower(str_t *ctx);
|
void strLower(str_t *ctx);
|
||||||
str_t strToLower(str_t ctx);
|
|
||||||
|
|
||||||
void strUpper(str_t *ctx);
|
void strUpper(str_t *ctx);
|
||||||
str_t strToUpper(str_t ctx);
|
|
||||||
|
|
||||||
#ifdef STR_TESTING
|
str_t strToLower(arena_t *arena, str_t ctx);
|
||||||
void strTest(void);
|
str_t strToUpper(arena_t *arena, str_t ctx);
|
||||||
#endif
|
|
||||||
|
|
||||||
// == STRVIEW_T ====================================================
|
// == STRVIEW_T ====================================================
|
||||||
|
|
||||||
strview_t strvInit(const char *cstr);
|
#define strv__1(x) \
|
||||||
strview_t strvInitStr(str_t str);
|
_Generic((x), \
|
||||||
strview_t strvInitLen(const char *buf, usize size);
|
char *: strvInit, \
|
||||||
|
const char *: strvInit, \
|
||||||
|
str_t: strvInitStr \
|
||||||
|
)(x)
|
||||||
|
|
||||||
char strvFront(strview_t ctx);
|
#define strv__2(cstr, clen) strvInitLen(cstr, clen)
|
||||||
char strvBack(strview_t ctx);
|
#define strv__impl(_1, _2, n, ...) strv__##n
|
||||||
const char *strvBegin(strview_t ctx);
|
|
||||||
const char *strvEnd(strview_t ctx);
|
#define strv(...) strv__impl(__VA_ARGS__, 2, 1, 0)(__VA_ARGS__)
|
||||||
// move view forward by n characters
|
|
||||||
strview_t strvRemovePrefix(strview_t ctx, usize n);
|
strview_t strvInit(const char *cstr);
|
||||||
// move view backwards by n characters
|
strview_t strvInitLen(const char *buf, usize size);
|
||||||
strview_t strvRemoveSuffix(strview_t ctx, usize n);
|
strview_t strvInitStr(str_t str);
|
||||||
// removes whitespace from the beginning and the end
|
|
||||||
strview_t strvTrim(strview_t ctx);
|
|
||||||
// removes whitespace from the beginning
|
|
||||||
strview_t strvTrimLeft(strview_t ctx);
|
|
||||||
// removes whitespace from the end
|
|
||||||
strview_t strvTrimRight(strview_t ctx);
|
|
||||||
|
|
||||||
bool strvIsEmpty(strview_t ctx);
|
bool strvIsEmpty(strview_t ctx);
|
||||||
|
bool strvEquals(strview_t a, strview_t b);
|
||||||
|
int strvCompare(strview_t a, strview_t b);
|
||||||
|
|
||||||
str_t strvCopy(strview_t ctx);
|
wchar_t *strvToWChar(arena_t *arena, strview_t ctx, usize *outlen);
|
||||||
str_t strvCopyN(strview_t ctx, usize count, usize from);
|
TCHAR *strvToTChar(arena_t *arena, strview_t str);
|
||||||
usize strvCopyBuf(strview_t ctx, char *buf, usize len, usize from);
|
|
||||||
|
strview_t strvRemovePrefix(strview_t ctx, usize n);
|
||||||
|
strview_t strvRemoveSuffix(strview_t ctx, usize n);
|
||||||
|
strview_t strvTrim(strview_t ctx);
|
||||||
|
strview_t strvTrimLeft(strview_t ctx);
|
||||||
|
strview_t strvTrimRight(strview_t ctx);
|
||||||
|
|
||||||
strview_t strvSub(strview_t ctx, usize from, usize to);
|
strview_t strvSub(strview_t ctx, usize from, usize to);
|
||||||
int strvCompare(strview_t ctx, strview_t other);
|
|
||||||
int strvICompare(strview_t ctx, strview_t other);
|
|
||||||
|
|
||||||
bool strvStartsWith(strview_t ctx, char c);
|
bool strvStartsWith(strview_t ctx, char c);
|
||||||
bool strvStartsWithView(strview_t ctx, strview_t view);
|
bool strvStartsWithView(strview_t ctx, strview_t view);
|
||||||
|
|
@ -105,18 +105,5 @@ bool strvContainsView(strview_t ctx, strview_t view);
|
||||||
usize strvFind(strview_t ctx, char c, usize from);
|
usize strvFind(strview_t ctx, char c, usize from);
|
||||||
usize strvFindView(strview_t ctx, strview_t view, usize from);
|
usize strvFindView(strview_t ctx, strview_t view, usize from);
|
||||||
|
|
||||||
usize strvRFind(strview_t ctx, char c, usize from);
|
usize strvRFind(strview_t ctx, char c, usize from_right);
|
||||||
usize strvRFindView(strview_t ctx, strview_t view, usize from);
|
usize strvRFindView(strview_t ctx, strview_t view, usize from_right);
|
||||||
|
|
||||||
// Finds the first occurrence of any of the characters of 'view' in this view
|
|
||||||
usize strvFindFirstOf(strview_t ctx, strview_t view, usize from);
|
|
||||||
usize strvFindLastOf(strview_t ctx, strview_t view, usize from);
|
|
||||||
|
|
||||||
usize strvFindFirstNot(strview_t ctx, char c, usize from);
|
|
||||||
usize strvFindFirstNotOf(strview_t ctx, strview_t view, usize from);
|
|
||||||
usize strvFindLastNot(strview_t ctx, char c, usize from);
|
|
||||||
usize strvFindLastNotOf(strview_t ctx, strview_t view, usize from);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
} // extern "C"
|
|
||||||
#endif
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
#include "strstream.h"
|
#include "strstream.h"
|
||||||
|
|
||||||
|
#include "warnings/colla_warn_beg.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
|
@ -7,9 +9,11 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <math.h> // HUGE_VALF
|
#include <math.h> // HUGE_VALF
|
||||||
#include "tracelog.h"
|
|
||||||
|
|
||||||
#if defined(_WIN32) && defined(__TINYC__)
|
#include "tracelog.h"
|
||||||
|
#include "arena.h"
|
||||||
|
|
||||||
|
#if COLLA_WIN && COLLA_TCC
|
||||||
#define strtoull _strtoui64
|
#define strtoull _strtoui64
|
||||||
#define strtoll _strtoi64
|
#define strtoll _strtoi64
|
||||||
#define strtof strtod
|
#define strtof strtod
|
||||||
|
|
@ -17,39 +21,48 @@
|
||||||
|
|
||||||
/* == INPUT STREAM ============================================ */
|
/* == INPUT STREAM ============================================ */
|
||||||
|
|
||||||
str_istream_t istrInit(const char *str) {
|
instream_t istrInit(const char *str) {
|
||||||
return istrInitLen(str, strlen(str));
|
return istrInitLen(str, strlen(str));
|
||||||
}
|
}
|
||||||
|
|
||||||
str_istream_t istrInitLen(const char *str, usize len) {
|
instream_t istrInitLen(const char *str, usize len) {
|
||||||
str_istream_t res;
|
instream_t res;
|
||||||
res.start = res.cur = str;
|
res.start = res.cur = str;
|
||||||
res.size = len;
|
res.size = len;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
char istrGet(str_istream_t *ctx) {
|
char istrGet(instream_t *ctx) {
|
||||||
return *ctx->cur++;
|
return *ctx->cur++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void istrIgnore(str_istream_t *ctx, char delim) {
|
void istrIgnore(instream_t *ctx, char delim) {
|
||||||
usize position = ctx->cur - ctx->start;
|
for (; !istrIsFinished(*ctx) && *ctx->cur != delim; ++ctx->cur) {
|
||||||
usize i;
|
|
||||||
for(i = position;
|
}
|
||||||
i < ctx->size && *ctx->cur != delim;
|
|
||||||
++i, ++ctx->cur);
|
//usize position = ctx->cur - ctx->start;
|
||||||
|
//usize i;
|
||||||
|
//for(i = position;
|
||||||
|
// i < ctx->size && *ctx->cur != delim;
|
||||||
|
// ++i, ++ctx->cur);
|
||||||
}
|
}
|
||||||
|
|
||||||
void istrIgnoreAndSkip(str_istream_t *ctx, char delim) {
|
void istrIgnoreAndSkip(instream_t *ctx, char delim) {
|
||||||
istrIgnore(ctx, delim);
|
istrIgnore(ctx, delim);
|
||||||
istrSkip(ctx, 1);
|
istrSkip(ctx, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
char istrPeek(str_istream_t *ctx) {
|
char istrPeek(instream_t *ctx) {
|
||||||
return *ctx->cur;
|
return *ctx->cur;
|
||||||
}
|
}
|
||||||
|
|
||||||
void istrSkip(str_istream_t *ctx, usize n) {
|
char istrPeekNext(instream_t *ctx) {
|
||||||
|
usize offset = (ctx->cur - ctx->start) + 1;
|
||||||
|
return offset > ctx->size ? '\0' : *(ctx->cur + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void istrSkip(instream_t *ctx, usize n) {
|
||||||
usize remaining = ctx->size - (ctx->cur - ctx->start);
|
usize remaining = ctx->size - (ctx->cur - ctx->start);
|
||||||
if(n > remaining) {
|
if(n > remaining) {
|
||||||
warn("skipping more then remaining: %zu -> %zu", n, remaining);
|
warn("skipping more then remaining: %zu -> %zu", n, remaining);
|
||||||
|
|
@ -58,13 +71,13 @@ void istrSkip(str_istream_t *ctx, usize n) {
|
||||||
ctx->cur += n;
|
ctx->cur += n;
|
||||||
}
|
}
|
||||||
|
|
||||||
void istrSkipWhitespace(str_istream_t *ctx) {
|
void istrSkipWhitespace(instream_t *ctx) {
|
||||||
while (*ctx->cur && isspace(*ctx->cur)) {
|
while (*ctx->cur && isspace(*ctx->cur)) {
|
||||||
++ctx->cur;
|
++ctx->cur;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void istrRead(str_istream_t *ctx, char *buf, usize len) {
|
void istrRead(instream_t *ctx, char *buf, usize len) {
|
||||||
usize remaining = ctx->size - (ctx->cur - ctx->start);
|
usize remaining = ctx->size - (ctx->cur - ctx->start);
|
||||||
if(len > remaining) {
|
if(len > remaining) {
|
||||||
warn("istrRead: trying to read len %zu from remaining %zu", len, remaining);
|
warn("istrRead: trying to read len %zu from remaining %zu", len, remaining);
|
||||||
|
|
@ -74,7 +87,7 @@ void istrRead(str_istream_t *ctx, char *buf, usize len) {
|
||||||
ctx->cur += len;
|
ctx->cur += len;
|
||||||
}
|
}
|
||||||
|
|
||||||
usize istrReadMax(str_istream_t *ctx, char *buf, usize len) {
|
usize istrReadMax(instream_t *ctx, char *buf, usize len) {
|
||||||
usize remaining = ctx->size - (ctx->cur - ctx->start);
|
usize remaining = ctx->size - (ctx->cur - ctx->start);
|
||||||
len = remaining < len ? remaining : len;
|
len = remaining < len ? remaining : len;
|
||||||
memcpy(buf, ctx->cur, len);
|
memcpy(buf, ctx->cur, len);
|
||||||
|
|
@ -82,29 +95,29 @@ usize istrReadMax(str_istream_t *ctx, char *buf, usize len) {
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
void istrRewind(str_istream_t *ctx) {
|
void istrRewind(instream_t *ctx) {
|
||||||
ctx->cur = ctx->start;
|
ctx->cur = ctx->start;
|
||||||
}
|
}
|
||||||
|
|
||||||
void istrRewindN(str_istream_t *ctx, usize amount) {
|
void istrRewindN(instream_t *ctx, usize amount) {
|
||||||
usize remaining = ctx->size - (ctx->cur - ctx->start);
|
usize remaining = ctx->size - (ctx->cur - ctx->start);
|
||||||
if (amount > remaining) amount = remaining;
|
if (amount > remaining) amount = remaining;
|
||||||
ctx->cur -= amount;
|
ctx->cur -= amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
usize istrTell(str_istream_t ctx) {
|
usize istrTell(instream_t ctx) {
|
||||||
return ctx.cur - ctx.start;
|
return ctx.cur - ctx.start;
|
||||||
}
|
}
|
||||||
|
|
||||||
usize istrRemaining(str_istream_t ctx) {
|
usize istrRemaining(instream_t ctx) {
|
||||||
return ctx.size - (ctx.cur - ctx.start);
|
return ctx.size - (ctx.cur - ctx.start);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool istrIsFinished(str_istream_t ctx) {
|
bool istrIsFinished(instream_t ctx) {
|
||||||
return (usize)(ctx.cur - ctx.start) >= ctx.size;
|
return (usize)(ctx.cur - ctx.start) >= ctx.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool istrGetbool(str_istream_t *ctx, bool *val) {
|
bool istrGetBool(instream_t *ctx, bool *val) {
|
||||||
usize remaining = ctx->size - (ctx->cur - ctx->start);
|
usize remaining = ctx->size - (ctx->cur - ctx->start);
|
||||||
if(strncmp(ctx->cur, "true", remaining) == 0) {
|
if(strncmp(ctx->cur, "true", remaining) == 0) {
|
||||||
*val = true;
|
*val = true;
|
||||||
|
|
@ -117,16 +130,16 @@ bool istrGetbool(str_istream_t *ctx, bool *val) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool istrGetu8(str_istream_t *ctx, uint8 *val) {
|
bool istrGetU8(instream_t *ctx, uint8 *val) {
|
||||||
char *end = NULL;
|
char *end = NULL;
|
||||||
*val = (uint8) strtoul(ctx->cur, &end, 0);
|
*val = (uint8) strtoul(ctx->cur, &end, 0);
|
||||||
|
|
||||||
if(ctx->cur == end) {
|
if(ctx->cur == end) {
|
||||||
warn("istrGetu8: no valid conversion could be performed");
|
warn("istrGetU8: no valid conversion could be performed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if(*val == UINT8_MAX) {
|
else if(*val == UINT8_MAX) {
|
||||||
warn("istrGetu8: value read is out of the range of representable values");
|
warn("istrGetU8: value read is out of the range of representable values");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,16 +147,16 @@ bool istrGetu8(str_istream_t *ctx, uint8 *val) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool istrGetu16(str_istream_t *ctx, uint16 *val) {
|
bool istrGetU16(instream_t *ctx, uint16 *val) {
|
||||||
char *end = NULL;
|
char *end = NULL;
|
||||||
*val = (uint16) strtoul(ctx->cur, &end, 0);
|
*val = (uint16) strtoul(ctx->cur, &end, 0);
|
||||||
|
|
||||||
if(ctx->cur == end) {
|
if(ctx->cur == end) {
|
||||||
warn("istrGetu16: no valid conversion could be performed");
|
warn("istrGetU16: no valid conversion could be performed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if(*val == UINT16_MAX) {
|
else if(*val == UINT16_MAX) {
|
||||||
warn("istrGetu16: value read is out of the range of representable values");
|
warn("istrGetU16: value read is out of the range of representable values");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,16 +164,16 @@ bool istrGetu16(str_istream_t *ctx, uint16 *val) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool istrGetu32(str_istream_t *ctx, uint32 *val) {
|
bool istrGetU32(instream_t *ctx, uint32 *val) {
|
||||||
char *end = NULL;
|
char *end = NULL;
|
||||||
*val = (uint32) strtoul(ctx->cur, &end, 0);
|
*val = (uint32) strtoul(ctx->cur, &end, 0);
|
||||||
|
|
||||||
if(ctx->cur == end) {
|
if(ctx->cur == end) {
|
||||||
warn("istrGetu32: no valid conversion could be performed");
|
warn("istrGetU32: no valid conversion could be performed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if(*val == UINT32_MAX) {
|
else if(*val == UINT32_MAX) {
|
||||||
warn("istrGetu32: value read is out of the range of representable values");
|
warn("istrGetU32: value read is out of the range of representable values");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,16 +181,16 @@ bool istrGetu32(str_istream_t *ctx, uint32 *val) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool istrGetu64(str_istream_t *ctx, uint64 *val) {
|
bool istrGetU64(instream_t *ctx, uint64 *val) {
|
||||||
char *end = NULL;
|
char *end = NULL;
|
||||||
*val = strtoull(ctx->cur, &end, 0);
|
*val = strtoull(ctx->cur, &end, 0);
|
||||||
|
|
||||||
if(ctx->cur == end) {
|
if(ctx->cur == end) {
|
||||||
warn("istrGetu64: no valid conversion could be performed");
|
warn("istrGetU64: no valid conversion could be performed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if(*val == ULLONG_MAX) {
|
else if(*val == ULLONG_MAX) {
|
||||||
warn("istrGetu64: value read is out of the range of representable values");
|
warn("istrGetU64: value read is out of the range of representable values");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -185,16 +198,16 @@ bool istrGetu64(str_istream_t *ctx, uint64 *val) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool istrGeti8(str_istream_t *ctx, int8 *val) {
|
bool istrGetI8(instream_t *ctx, int8 *val) {
|
||||||
char *end = NULL;
|
char *end = NULL;
|
||||||
*val = (int8) strtol(ctx->cur, &end, 0);
|
*val = (int8) strtol(ctx->cur, &end, 0);
|
||||||
|
|
||||||
if(ctx->cur == end) {
|
if(ctx->cur == end) {
|
||||||
warn("istrGeti8: no valid conversion could be performed");
|
warn("istrGetI8: no valid conversion could be performed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if(*val == INT8_MAX || *val == INT8_MIN) {
|
else if(*val == INT8_MAX || *val == INT8_MIN) {
|
||||||
warn("istrGeti8: value read is out of the range of representable values");
|
warn("istrGetI8: value read is out of the range of representable values");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,16 +215,16 @@ bool istrGeti8(str_istream_t *ctx, int8 *val) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool istrGeti16(str_istream_t *ctx, int16 *val) {
|
bool istrGetI16(instream_t *ctx, int16 *val) {
|
||||||
char *end = NULL;
|
char *end = NULL;
|
||||||
*val = (int16) strtol(ctx->cur, &end, 0);
|
*val = (int16) strtol(ctx->cur, &end, 0);
|
||||||
|
|
||||||
if(ctx->cur == end) {
|
if(ctx->cur == end) {
|
||||||
warn("istrGeti16: no valid conversion could be performed");
|
warn("istrGetI16: no valid conversion could be performed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if(*val == INT16_MAX || *val == INT16_MIN) {
|
else if(*val == INT16_MAX || *val == INT16_MIN) {
|
||||||
warn("istrGeti16: value read is out of the range of representable values");
|
warn("istrGetI16: value read is out of the range of representable values");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -219,16 +232,16 @@ bool istrGeti16(str_istream_t *ctx, int16 *val) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool istrGeti32(str_istream_t *ctx, int32 *val) {
|
bool istrGetI32(instream_t *ctx, int32 *val) {
|
||||||
char *end = NULL;
|
char *end = NULL;
|
||||||
*val = (int32) strtol(ctx->cur, &end, 0);
|
*val = (int32) strtol(ctx->cur, &end, 0);
|
||||||
|
|
||||||
if(ctx->cur == end) {
|
if(ctx->cur == end) {
|
||||||
warn("istrGeti32: no valid conversion could be performed");
|
warn("istrGetI32: no valid conversion could be performed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if(*val == INT32_MAX || *val == INT32_MIN) {
|
else if(*val == INT32_MAX || *val == INT32_MIN) {
|
||||||
warn("istrGeti32: value read is out of the range of representable values");
|
warn("istrGetI32: value read is out of the range of representable values");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -236,16 +249,16 @@ bool istrGeti32(str_istream_t *ctx, int32 *val) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool istrGeti64(str_istream_t *ctx, int64 *val) {
|
bool istrGetI64(instream_t *ctx, int64 *val) {
|
||||||
char *end = NULL;
|
char *end = NULL;
|
||||||
*val = strtoll(ctx->cur, &end, 0);
|
*val = strtoll(ctx->cur, &end, 0);
|
||||||
|
|
||||||
if(ctx->cur == end) {
|
if(ctx->cur == end) {
|
||||||
warn("istrGeti64: no valid conversion could be performed");
|
warn("istrGetI64: no valid conversion could be performed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if(*val == INT64_MAX || *val == INT64_MIN) {
|
else if(*val == INT64_MAX || *val == INT64_MIN) {
|
||||||
warn("istrGeti64: value read is out of the range of representable values");
|
warn("istrGetI64: value read is out of the range of representable values");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -253,16 +266,16 @@ bool istrGeti64(str_istream_t *ctx, int64 *val) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool istrGetfloat(str_istream_t *ctx, float *val) {
|
bool istrGetFloat(instream_t *ctx, float *val) {
|
||||||
char *end = NULL;
|
char *end = NULL;
|
||||||
*val = strtof(ctx->cur, &end);
|
*val = strtof(ctx->cur, &end);
|
||||||
|
|
||||||
if(ctx->cur == end) {
|
if(ctx->cur == end) {
|
||||||
warn("istrGetfloat: no valid conversion could be performed");
|
warn("istrGetFloat: no valid conversion could be performed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if(*val == HUGE_VALF || *val == -HUGE_VALF) {
|
else if(*val == HUGE_VALF || *val == -HUGE_VALF) {
|
||||||
warn("istrGetfloat: value read is out of the range of representable values");
|
warn("istrGetFloat: value read is out of the range of representable values");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -270,16 +283,16 @@ bool istrGetfloat(str_istream_t *ctx, float *val) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool istrGetdouble(str_istream_t *ctx, double *val) {
|
bool istrGetDouble(instream_t *ctx, double *val) {
|
||||||
char *end = NULL;
|
char *end = NULL;
|
||||||
*val = strtod(ctx->cur, &end);
|
*val = strtod(ctx->cur, &end);
|
||||||
|
|
||||||
if(ctx->cur == end) {
|
if(ctx->cur == end) {
|
||||||
warn("istrGetdouble: no valid conversion could be performed");
|
warn("istrGetDouble: no valid conversion could be performed (%.5s)", ctx->cur);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if(*val == HUGE_VAL || *val == -HUGE_VAL) {
|
else if(*val == HUGE_VAL || *val == -HUGE_VAL) {
|
||||||
warn("istrGetdouble: value read is out of the range of representable values");
|
warn("istrGetDouble: value read is out of the range of representable values");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -287,286 +300,127 @@ bool istrGetdouble(str_istream_t *ctx, double *val) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
usize istrGetstring(str_istream_t *ctx, char **val, char delim) {
|
str_t istrGetStr(arena_t *arena, instream_t *ctx, char delim) {
|
||||||
const char *from = ctx->cur;
|
const char *from = ctx->cur;
|
||||||
istrIgnore(ctx, delim);
|
istrIgnore(ctx, delim);
|
||||||
// if it didn't actually find it, it just reached the end of the string
|
// if it didn't actually find it, it just reached the end of the string
|
||||||
if(*ctx->cur != delim) {
|
if(*ctx->cur != delim) {
|
||||||
*val = NULL;
|
return (str_t){0};
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
usize len = ctx->cur - from;
|
usize len = ctx->cur - from;
|
||||||
*val = (char *)malloc(len + 1);
|
str_t out = {
|
||||||
memcpy(*val, from, len);
|
.buf = alloc(arena, char, len + 1),
|
||||||
(*val)[len] = '\0';
|
.len = len
|
||||||
return len;
|
};
|
||||||
|
memcpy(out.buf, from, len);
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
usize istrGetstringBuf(str_istream_t *ctx, char *val, usize len) {
|
usize istrGetBuf(instream_t *ctx, char *buf, usize buflen) {
|
||||||
usize remaining = ctx->size - (ctx->cur - ctx->start);
|
usize remaining = ctx->size - (ctx->cur - ctx->start);
|
||||||
len -= 1;
|
buflen -= 1;
|
||||||
len = remaining < len ? remaining : len;
|
buflen = remaining < buflen ? remaining : buflen;
|
||||||
memcpy(val, ctx->cur, len);
|
memcpy(buf, ctx->cur, buflen);
|
||||||
val[len] = '\0';
|
buf[buflen] = '\0';
|
||||||
ctx->cur += len;
|
ctx->cur += buflen;
|
||||||
return len;
|
return buflen;
|
||||||
}
|
}
|
||||||
|
|
||||||
strview_t istrGetview(str_istream_t *ctx, char delim) {
|
strview_t istrGetView(instream_t *ctx, char delim) {
|
||||||
const char *from = ctx->cur;
|
const char *from = ctx->cur;
|
||||||
istrIgnore(ctx, delim);
|
istrIgnore(ctx, delim);
|
||||||
usize len = ctx->cur - from;
|
usize len = ctx->cur - from;
|
||||||
return strvInitLen(from, len);
|
return strvInitLen(from, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
strview_t istrGetviewLen(str_istream_t *ctx, usize from, usize to) {
|
strview_t istrGetViewLen(instream_t *ctx, usize len) {
|
||||||
usize len = ctx->size - (ctx->cur - ctx->start) - from;
|
const char *from = ctx->cur;
|
||||||
if (to > len) to = len;
|
istrSkip(ctx, len);
|
||||||
if (from > to) from = to;
|
usize buflen = ctx->cur - from;
|
||||||
return strvInitLen(ctx->cur + from, to - from);
|
return (strview_t){ from, buflen };
|
||||||
}
|
}
|
||||||
|
|
||||||
/* == OUTPUT STREAM =========================================== */
|
/* == OUTPUT STREAM =========================================== */
|
||||||
|
|
||||||
static void _ostrRealloc(str_ostream_t *ctx, usize needed) {
|
void ostr__remove_null(outstream_t *o) {
|
||||||
ctx->cap = (ctx->cap * 2) + needed;
|
if (ostrTell(o)) {
|
||||||
ctx->buf = (char *)realloc(ctx->buf, ctx->cap);
|
arenaPop(o->arena, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
str_ostream_t ostrInit() {
|
outstream_t ostrInit(arena_t *arena) {
|
||||||
return ostrInitLen(1);
|
return (outstream_t){
|
||||||
}
|
.beg = (char *)(arena ? arena->current : NULL),
|
||||||
|
.arena = arena,
|
||||||
str_ostream_t ostrInitLen(usize initial_alloc) {
|
|
||||||
str_ostream_t stream;
|
|
||||||
stream.buf = (char *)calloc(initial_alloc, 1);
|
|
||||||
stream.len = 0;
|
|
||||||
stream.cap = initial_alloc;
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
str_ostream_t ostrInitStr(const char *cstr, usize len) {
|
|
||||||
str_ostream_t stream;
|
|
||||||
stream.buf = (char *)malloc(len + 1);
|
|
||||||
memcpy(stream.buf, cstr, len);
|
|
||||||
stream.len = len;
|
|
||||||
stream.cap = len + 1;
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ostrFree(str_ostream_t ctx) {
|
|
||||||
free(ctx.buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ostrClear(str_ostream_t *ctx) {
|
|
||||||
ctx->len = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
char ostrBack(str_ostream_t ctx) {
|
|
||||||
if(ctx.len == 0) return '\0';
|
|
||||||
return ctx.buf[ctx.len - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
str_t ostrAsStr(str_ostream_t ctx) {
|
|
||||||
return (str_t) {
|
|
||||||
.buf = ctx.buf,
|
|
||||||
.len = ctx.len
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
strview_t ostrAsView(str_ostream_t ctx) {
|
void ostrClear(outstream_t *ctx) {
|
||||||
return (strview_t) {
|
arenaPop(ctx->arena, ostrTell(ctx));
|
||||||
.buf = ctx.buf,
|
}
|
||||||
.len = ctx.len
|
|
||||||
|
usize ostrTell(outstream_t *ctx) {
|
||||||
|
return ctx->arena ? (char *)ctx->arena->current - ctx->beg : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char ostrBack(outstream_t *ctx) {
|
||||||
|
return arenaTell(ctx->arena) ? *ctx->arena->current : '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
str_t ostrAsStr(outstream_t *ctx) {
|
||||||
|
return (str_t){
|
||||||
|
.buf = ctx->beg,
|
||||||
|
.len = ostrTell(ctx)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void ostrReplace(str_ostream_t *ctx, char from, char to) {
|
strview_t ostrAsView(outstream_t *ctx) {
|
||||||
for(usize i = 0; i < ctx->len; ++i) {
|
return (strview_t){
|
||||||
if(ctx->buf[i] == from) {
|
.buf = ctx->beg,
|
||||||
ctx->buf[i] = to;
|
.len = ostrTell(ctx)
|
||||||
}
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ostrPrintf(str_ostream_t *ctx, const char *fmt, ...) {
|
void ostrPrintf(outstream_t *ctx, const char *fmt, ...) {
|
||||||
va_list va;
|
va_list args;
|
||||||
va_start(va, fmt);
|
va_start(args, fmt);
|
||||||
ostrPrintfV(ctx, fmt, va);
|
ostrPrintfV(ctx, fmt, args);
|
||||||
va_end(va);
|
va_end(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ostrPrintfV(str_ostream_t *ctx, const char *fmt, va_list args) {
|
void ostrPrintfV(outstream_t *ctx, const char *fmt, va_list args) {
|
||||||
va_list vtemp;
|
if (!ctx->arena) return;
|
||||||
int len;
|
ostr__remove_null(ctx);
|
||||||
usize remaining;
|
strFmtv(ctx->arena, fmt, args);
|
||||||
|
|
||||||
// vsnprintf returns the length of the formatted string, even if truncated
|
|
||||||
// we use this to get the actual length of the formatted string
|
|
||||||
va_copy(vtemp, args);
|
|
||||||
len = vsnprintf(NULL, 0, fmt, vtemp);
|
|
||||||
va_end(vtemp);
|
|
||||||
if(len < 0) {
|
|
||||||
err("couldn't format string \"%s\"", fmt);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
remaining = ctx->cap - ctx->len;
|
|
||||||
if(remaining < (usize)len) {
|
|
||||||
_ostrRealloc(ctx, len + 1);
|
|
||||||
remaining = ctx->cap - ctx->len;
|
|
||||||
}
|
|
||||||
|
|
||||||
// actual formatting here
|
|
||||||
va_copy(vtemp, args);
|
|
||||||
len = vsnprintf(ctx->buf + ctx->len, remaining, fmt, vtemp);
|
|
||||||
va_end(vtemp);
|
|
||||||
if(len < 0) {
|
|
||||||
err("couldn't format stringh \"%s\"", fmt);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
ctx->len += len;
|
|
||||||
|
|
||||||
error:
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ostrPutc(outstream_t *ctx, char c) {
|
||||||
#define APPEND_BUF_LEN 20
|
if (!ctx->arena) return;
|
||||||
|
ostr__remove_null(ctx);
|
||||||
void ostrPutc(str_ostream_t *ctx, char c) {
|
char *newc = alloc(ctx->arena, char);
|
||||||
ostrAppendchar(ctx, c);
|
*newc = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ostrPuts(str_ostream_t *ctx, const char *str) {
|
void ostrPuts(outstream_t *ctx, strview_t v) {
|
||||||
ostrAppendview(ctx, strvInit(str));
|
if (strvIsEmpty(v)) return;
|
||||||
|
ostr__remove_null(ctx);
|
||||||
|
str(ctx->arena, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ostrAppendbool(str_ostream_t *ctx, bool val) {
|
void ostrAppendBool(outstream_t *ctx, bool val) {
|
||||||
ostrAppendview(ctx, strvInit(val ? "true" : "false"));
|
ostrPuts(ctx, val ? strv("true") : strv("false"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ostrAppendchar(str_ostream_t *ctx, char val) {
|
void ostrAppendUInt(outstream_t *ctx, uint64 val) {
|
||||||
if(ctx->len >= ctx->cap) {
|
ostrPrintf(ctx, "%I64u", val);
|
||||||
_ostrRealloc(ctx, 1);
|
|
||||||
}
|
|
||||||
ctx->buf[ctx->len++] = val;
|
|
||||||
ctx->buf[ctx->len] = '\0';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ostrAppendu8(str_ostream_t *ctx, uint8 val) {
|
void ostrAppendInt(outstream_t *ctx, int64 val) {
|
||||||
char buf[APPEND_BUF_LEN];
|
ostrPrintf(ctx, "%I64d", val);
|
||||||
int len = snprintf(buf, sizeof(buf), "%hhu", val);
|
|
||||||
if(len <= 0) {
|
|
||||||
err("ostrAppendu8: couldn't write %hhu", val);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ostrAppendu16(str_ostream_t *ctx, uint16 val) {
|
void ostrAppendNum(outstream_t *ctx, double val) {
|
||||||
char buf[APPEND_BUF_LEN];
|
ostrPrintf(ctx, "%g", val);
|
||||||
int len = snprintf(buf, sizeof(buf), "%hu", val);
|
|
||||||
if(len <= 0) {
|
|
||||||
err("ostrAppendu16: couldn't write %hu", val);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ostrAppendu32(str_ostream_t *ctx, uint32 val) {
|
#include "warnings/colla_warn_end.h"
|
||||||
char buf[APPEND_BUF_LEN];
|
|
||||||
int len = snprintf(buf, sizeof(buf), "%u", val);
|
|
||||||
if(len <= 0) {
|
|
||||||
err("ostrAppendu32: couldn't write %u", val);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ostrAppendu64(str_ostream_t *ctx, uint64 val) {
|
|
||||||
char buf[APPEND_BUF_LEN];
|
|
||||||
#if _WIN32
|
|
||||||
int len = snprintf(buf, sizeof(buf), "%llu", val);
|
|
||||||
#else
|
|
||||||
int len = snprintf(buf, sizeof(buf), "%lu", val);
|
|
||||||
#endif
|
|
||||||
if(len <= 0) {
|
|
||||||
err("ostrAppendu64: couldn't write %lu", val);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ostrAppendi8(str_ostream_t *ctx, int8 val) {
|
|
||||||
char buf[APPEND_BUF_LEN];
|
|
||||||
int len = snprintf(buf, sizeof(buf), "%hhi", val);
|
|
||||||
if(len <= 0) {
|
|
||||||
err("ostrAppendi8: couldn't write %hhi", val);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ostrAppendi16(str_ostream_t *ctx, int16 val) {
|
|
||||||
char buf[APPEND_BUF_LEN];
|
|
||||||
int len = snprintf(buf, sizeof(buf), "%hi", val);
|
|
||||||
if(len <= 0) {
|
|
||||||
err("ostrAppendi16: couldn't write %hi", val);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ostrAppendi32(str_ostream_t *ctx, int32 val) {
|
|
||||||
char buf[APPEND_BUF_LEN];
|
|
||||||
int len = snprintf(buf, sizeof(buf), "%i", val);
|
|
||||||
if(len <= 0) {
|
|
||||||
err("ostrAppendi32: couldn't write %i", val);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ostrAppendi64(str_ostream_t *ctx, int64 val) {
|
|
||||||
char buf[APPEND_BUF_LEN];
|
|
||||||
#if _WIN32
|
|
||||||
int len = snprintf(buf, sizeof(buf), "%lli", val);
|
|
||||||
#else
|
|
||||||
int len = snprintf(buf, sizeof(buf), "%li", val);
|
|
||||||
#endif
|
|
||||||
if(len <= 0) {
|
|
||||||
err("ostrAppendi64: couldn't write %li", val);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ostrAppendfloat(str_ostream_t *ctx, float val) {
|
|
||||||
char buf[APPEND_BUF_LEN * 3];
|
|
||||||
int len = snprintf(buf, sizeof(buf), "%g", (double)val);
|
|
||||||
if(len <= 0) {
|
|
||||||
err("ostrAppendfloat: couldn't write %g", (double)val);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ostrAppenddouble(str_ostream_t *ctx, double val) {
|
|
||||||
char buf[APPEND_BUF_LEN * 3];
|
|
||||||
int len = snprintf(buf, sizeof(buf), "%g", val);
|
|
||||||
if(len <= 0) {
|
|
||||||
err("ostrAppenddouble: couldn't write %g", val);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ostrAppendview(ctx, strvInitLen(buf, len));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ostrAppendview(str_ostream_t *ctx, strview_t view) {
|
|
||||||
if((ctx->cap - ctx->len) <= view.len) {
|
|
||||||
_ostrRealloc(ctx, view.len + 1);
|
|
||||||
}
|
|
||||||
memcpy(ctx->buf + ctx->len, view.buf, view.len);
|
|
||||||
ctx->len += view.len;
|
|
||||||
ctx->buf[ctx->len] = '\0';
|
|
||||||
}
|
|
||||||
|
|
@ -4,110 +4,97 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
|
||||||
#include "collatypes.h"
|
#include "collatypes.h"
|
||||||
#include "str.h"
|
#include "str.h"
|
||||||
|
|
||||||
|
typedef struct arena_t arena_t;
|
||||||
|
|
||||||
/* == INPUT STREAM ============================================ */
|
/* == INPUT STREAM ============================================ */
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char *start;
|
const char *start;
|
||||||
const char *cur;
|
const char *cur;
|
||||||
usize size;
|
usize size;
|
||||||
} str_istream_t;
|
} instream_t;
|
||||||
|
|
||||||
// initialize with null-terminated string
|
// initialize with null-terminated string
|
||||||
str_istream_t istrInit(const char *str);
|
instream_t istrInit(const char *str);
|
||||||
str_istream_t istrInitLen(const char *str, usize len);
|
instream_t istrInitLen(const char *str, usize len);
|
||||||
|
|
||||||
// get the current character and advance
|
// get the current character and advance
|
||||||
char istrGet(str_istream_t *ctx);
|
char istrGet(instream_t *ctx);
|
||||||
// get the current character but don't advance
|
// get the current character but don't advance
|
||||||
char istrPeek(str_istream_t *ctx);
|
char istrPeek(instream_t *ctx);
|
||||||
|
// get the next character but don't advance
|
||||||
|
char istrPeekNext(instream_t *ctx);
|
||||||
// ignore characters until the delimiter
|
// ignore characters until the delimiter
|
||||||
void istrIgnore(str_istream_t *ctx, char delim);
|
void istrIgnore(instream_t *ctx, char delim);
|
||||||
// ignore characters until the delimiter and skip it
|
// ignore characters until the delimiter and skip it
|
||||||
void istrIgnoreAndSkip(str_istream_t *ctx, char delim);
|
void istrIgnoreAndSkip(instream_t *ctx, char delim);
|
||||||
// skip n characters
|
// skip n characters
|
||||||
void istrSkip(str_istream_t *ctx, usize n);
|
void istrSkip(instream_t *ctx, usize n);
|
||||||
// skips whitespace (' ', '\n', '\t', '\r')
|
// skips whitespace (' ', '\n', '\t', '\r')
|
||||||
void istrSkipWhitespace(str_istream_t *ctx);
|
void istrSkipWhitespace(instream_t *ctx);
|
||||||
// read len bytes into buffer, the buffer will not be null terminated
|
// read len bytes into buffer, the buffer will not be null terminated
|
||||||
void istrRead(str_istream_t *ctx, char *buf, usize len);
|
void istrRead(instream_t *ctx, char *buf, usize len);
|
||||||
// read a maximum of len bytes into buffer, the buffer will not be null terminated
|
// read a maximum of len bytes into buffer, the buffer will not be null terminated
|
||||||
// returns the number of bytes read
|
// returns the number of bytes read
|
||||||
usize istrReadMax(str_istream_t *ctx, char *buf, usize len);
|
usize istrReadMax(instream_t *ctx, char *buf, usize len);
|
||||||
// returns to the beginning of the stream
|
// returns to the beginning of the stream
|
||||||
void istrRewind(str_istream_t *ctx);
|
void istrRewind(instream_t *ctx);
|
||||||
// returns back <amount> characters
|
// returns back <amount> characters
|
||||||
void istrRewindN(str_istream_t *ctx, usize amount);
|
void istrRewindN(instream_t *ctx, usize amount);
|
||||||
// returns the number of bytes read from beginning of stream
|
// returns the number of bytes read from beginning of stream
|
||||||
usize istrTell(str_istream_t ctx);
|
usize istrTell(instream_t ctx);
|
||||||
// returns the number of bytes left to read in the stream
|
// returns the number of bytes left to read in the stream
|
||||||
usize istrRemaining(str_istream_t ctx);
|
usize istrRemaining(instream_t ctx);
|
||||||
// return true if the stream doesn't have any new bytes to read
|
// return true if the stream doesn't have any new bytes to read
|
||||||
bool istrIsFinished(str_istream_t ctx);
|
bool istrIsFinished(instream_t ctx);
|
||||||
|
|
||||||
bool istrGetbool(str_istream_t *ctx, bool *val);
|
bool istrGetBool(instream_t *ctx, bool *val);
|
||||||
bool istrGetu8(str_istream_t *ctx, uint8 *val);
|
bool istrGetU8(instream_t *ctx, uint8 *val);
|
||||||
bool istrGetu16(str_istream_t *ctx, uint16 *val);
|
bool istrGetU16(instream_t *ctx, uint16 *val);
|
||||||
bool istrGetu32(str_istream_t *ctx, uint32 *val);
|
bool istrGetU32(instream_t *ctx, uint32 *val);
|
||||||
bool istrGetu64(str_istream_t *ctx, uint64 *val);
|
bool istrGetU64(instream_t *ctx, uint64 *val);
|
||||||
bool istrGeti8(str_istream_t *ctx, int8 *val);
|
bool istrGetI8(instream_t *ctx, int8 *val);
|
||||||
bool istrGeti16(str_istream_t *ctx, int16 *val);
|
bool istrGetI16(instream_t *ctx, int16 *val);
|
||||||
bool istrGeti32(str_istream_t *ctx, int32 *val);
|
bool istrGetI32(instream_t *ctx, int32 *val);
|
||||||
bool istrGeti64(str_istream_t *ctx, int64 *val);
|
bool istrGetI64(instream_t *ctx, int64 *val);
|
||||||
bool istrGetfloat(str_istream_t *ctx, float *val);
|
bool istrGetFloat(instream_t *ctx, float *val);
|
||||||
bool istrGetdouble(str_istream_t *ctx, double *val);
|
bool istrGetDouble(instream_t *ctx, double *val);
|
||||||
// get a string until a delimiter, the string is allocated by the function and should be freed
|
str_t istrGetStr(arena_t *arena, instream_t *ctx, char delim);
|
||||||
usize istrGetstring(str_istream_t *ctx, char **val, char delim);
|
|
||||||
// get a string of maximum size len, the string is not allocated by the function and will be null terminated
|
// get a string of maximum size len, the string is not allocated by the function and will be null terminated
|
||||||
usize istrGetstringBuf(str_istream_t *ctx, char *val, usize len);
|
usize istrGetBuf(instream_t *ctx, char *buf, usize buflen);
|
||||||
strview_t istrGetview(str_istream_t *ctx, char delim);
|
strview_t istrGetView(instream_t *ctx, char delim);
|
||||||
strview_t istrGetviewLen(str_istream_t *ctx, usize from, usize to);
|
strview_t istrGetViewLen(instream_t *ctx, usize len);
|
||||||
|
|
||||||
/* == OUTPUT STREAM =========================================== */
|
/* == OUTPUT STREAM =========================================== */
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char *buf;
|
char *beg;
|
||||||
usize len;
|
arena_t *arena;
|
||||||
usize cap;
|
} outstream_t;
|
||||||
} str_ostream_t;
|
|
||||||
|
|
||||||
str_ostream_t ostrInit(void);
|
outstream_t ostrInit(arena_t *exclusive_arena);
|
||||||
str_ostream_t ostrInitLen(usize initial_alloc);
|
void ostrClear(outstream_t *ctx);
|
||||||
str_ostream_t ostrInitStr(const char *buf, usize len);
|
|
||||||
|
|
||||||
void ostrFree(str_ostream_t ctx);
|
usize ostrTell(outstream_t *ctx);
|
||||||
void ostrClear(str_ostream_t *ctx);
|
|
||||||
|
|
||||||
char ostrBack(str_ostream_t ctx);
|
char ostrBack(outstream_t *ctx);
|
||||||
str_t ostrAsStr(str_ostream_t ctx);
|
str_t ostrAsStr(outstream_t *ctx);
|
||||||
strview_t ostrAsView(str_ostream_t ctx);
|
strview_t ostrAsView(outstream_t *ctx);
|
||||||
|
|
||||||
void ostrReplace(str_ostream_t *ctx, char from, char to);
|
void ostrPrintf(outstream_t *ctx, const char *fmt, ...);
|
||||||
|
void ostrPrintfV(outstream_t *ctx, const char *fmt, va_list args);
|
||||||
|
void ostrPutc(outstream_t *ctx, char c);
|
||||||
|
void ostrPuts(outstream_t *ctx, strview_t v);
|
||||||
|
|
||||||
void ostrPrintf(str_ostream_t *ctx, const char *fmt, ...);
|
void ostrAppendBool(outstream_t *ctx, bool val);
|
||||||
void ostrPrintfV(str_ostream_t *ctx, const char *fmt, va_list args);
|
void ostrAppendUInt(outstream_t *ctx, uint64 val);
|
||||||
void ostrPutc(str_ostream_t *ctx, char c);
|
void ostrAppendInt(outstream_t *ctx, int64 val);
|
||||||
void ostrPuts(str_ostream_t *ctx, const char *str);
|
void ostrAppendNum(outstream_t *ctx, double val);
|
||||||
|
|
||||||
void ostrAppendbool(str_ostream_t *ctx, bool val);
|
|
||||||
void ostrAppendchar(str_ostream_t *ctx, char val);
|
|
||||||
void ostrAppendu8(str_ostream_t *ctx, uint8 val);
|
|
||||||
void ostrAppendu16(str_ostream_t *ctx, uint16 val);
|
|
||||||
void ostrAppendu32(str_ostream_t *ctx, uint32 val);
|
|
||||||
void ostrAppendu64(str_ostream_t *ctx, uint64 val);
|
|
||||||
void ostrAppendi8(str_ostream_t *ctx, int8 val);
|
|
||||||
void ostrAppendi16(str_ostream_t *ctx, int16 val);
|
|
||||||
void ostrAppendi32(str_ostream_t *ctx, int32 val);
|
|
||||||
void ostrAppendi64(str_ostream_t *ctx, int64 val);
|
|
||||||
void ostrAppendfloat(str_ostream_t *ctx, float val);
|
|
||||||
void ostrAppenddouble(str_ostream_t *ctx, double val);
|
|
||||||
void ostrAppendview(str_ostream_t *ctx, strview_t view);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,32 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#include "format.h"
|
||||||
|
|
||||||
|
#if COLLA_WIN
|
||||||
|
#if COLLA_MSVC
|
||||||
#pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS.
|
#pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS.
|
||||||
#include "win32_slim.h"
|
#endif
|
||||||
|
|
||||||
|
#include <handleapi.h>
|
||||||
|
|
||||||
|
// avoid including windows.h
|
||||||
|
|
||||||
|
#ifndef STD_OUTPUT_HANDLE
|
||||||
|
#define STD_OUTPUT_HANDLE ((DWORD)-11)
|
||||||
|
#endif
|
||||||
|
#ifndef CP_UTF8
|
||||||
|
#define CP_UTF8 65001
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef unsigned short WORD;
|
||||||
|
typedef unsigned long DWORD;
|
||||||
|
typedef unsigned int UINT;
|
||||||
|
typedef int BOOL;
|
||||||
|
WINBASEAPI HANDLE WINAPI GetStdHandle(DWORD nStdHandle);
|
||||||
|
WINBASEAPI BOOL WINAPI SetConsoleTextAttribute(HANDLE hConsoleOutput, WORD wAttributes);
|
||||||
|
WINBASEAPI BOOL WINAPI SetConsoleOutputCP(UINT wCodePageID);
|
||||||
|
|
||||||
#ifndef TLOG_VS
|
#ifndef TLOG_VS
|
||||||
#define TLOG_WIN32_NO_VS
|
#define TLOG_WIN32_NO_VS
|
||||||
#ifndef TLOG_NO_COLOURS
|
#ifndef TLOG_NO_COLOURS
|
||||||
|
|
@ -16,33 +39,33 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef TLOG_VS
|
#ifdef TLOG_VS
|
||||||
#ifndef _WIN32
|
#if COLLA_WIN
|
||||||
#error "can't use TLOG_VS if not on windows"
|
#error "can't use TLOG_VS if not on windows"
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef TLOG_NO_COLOURS
|
#ifdef TLOG_NO_COLOURS
|
||||||
#define BLACK ""
|
#define COLOUR_BLACK ""
|
||||||
#define RED ""
|
#define COLOUR_RED ""
|
||||||
#define GREEN ""
|
#define COLOUR_GREEN ""
|
||||||
#define YELLOW ""
|
#define COLOUR_YELLOW ""
|
||||||
#define BLUE ""
|
#define COLOUR_BLUE ""
|
||||||
#define MAGENTA ""
|
#define COLOUR_MAGENTA ""
|
||||||
#define CYAN ""
|
#define COLOUR_CYAN ""
|
||||||
#define WHITE ""
|
#define COLOUR_WHITE ""
|
||||||
#define RESET ""
|
#define COLOUR_RESET ""
|
||||||
#define BOLD ""
|
#define COLOUR_BOLD ""
|
||||||
#else
|
#else
|
||||||
#define BLACK "\033[30m"
|
#define COLOUR_BLACK "\033[30m"
|
||||||
#define RED "\033[31m"
|
#define COLOUR_RED "\033[31m"
|
||||||
#define GREEN "\033[32m"
|
#define COLOUR_GREEN "\033[32m"
|
||||||
#define YELLOW "\033[33m"
|
#define COLOUR_YELLOW "\033[33m"
|
||||||
#define BLUE "\033[22;34m"
|
#define COLOUR_BLUE "\033[22;34m"
|
||||||
#define MAGENTA "\033[35m"
|
#define COLOUR_MAGENTA "\033[35m"
|
||||||
#define CYAN "\033[36m"
|
#define COLOUR_CYAN "\033[36m"
|
||||||
#define WHITE "\033[37m"
|
#define COLOUR_WHITE "\033[37m"
|
||||||
#define RESET "\033[0m"
|
#define COLOUR_RESET "\033[0m"
|
||||||
#define BOLD "\033[1m"
|
#define COLOUR_BOLD "\033[1m"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define MAX_TRACELOG_MSG_LENGTH 1024
|
#define MAX_TRACELOG_MSG_LENGTH 1024
|
||||||
|
|
@ -67,8 +90,7 @@ static void setLevelColour(int level) {
|
||||||
|
|
||||||
void traceLog(int level, const char *fmt, ...) {
|
void traceLog(int level, const char *fmt, ...) {
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt); traceLogVaList(level, fmt, args);
|
||||||
traceLogVaList(level, fmt, args);
|
|
||||||
va_end(args);
|
va_end(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,12 +110,12 @@ void traceLogVaList(int level, const char *fmt, va_list args) {
|
||||||
|
|
||||||
const char *beg;
|
const char *beg;
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case LogTrace: beg = BOLD WHITE "[TRACE]: " RESET; break;
|
case LogTrace: beg = COLOUR_BOLD COLOUR_WHITE "[TRACE]: " COLOUR_RESET; break;
|
||||||
case LogDebug: beg = BOLD BLUE "[DEBUG]: " RESET; break;
|
case LogDebug: beg = COLOUR_BOLD COLOUR_BLUE "[DEBUG]: " COLOUR_RESET; break;
|
||||||
case LogInfo: beg = BOLD GREEN "[INFO]: " RESET; break;
|
case LogInfo: beg = COLOUR_BOLD COLOUR_GREEN "[INFO]: " COLOUR_RESET; break;
|
||||||
case LogWarning: beg = BOLD YELLOW "[WARNING]: " RESET; break;
|
case LogWarning: beg = COLOUR_BOLD COLOUR_YELLOW "[WARNING]: " COLOUR_RESET; break;
|
||||||
case LogError: beg = BOLD RED "[ERROR]: " RESET; break;
|
case LogError: beg = COLOUR_BOLD COLOUR_RED "[ERROR]: " COLOUR_RESET; break;
|
||||||
case LogFatal: beg = BOLD RED "[FATAL]: " RESET; break;
|
case LogFatal: beg = COLOUR_BOLD COLOUR_RED "[FATAL]: " COLOUR_RESET; break;
|
||||||
default: beg = ""; break;
|
default: beg = ""; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,7 +126,7 @@ void traceLogVaList(int level, const char *fmt, va_list args) {
|
||||||
strncpy(buffer, beg, sizeof(buffer));
|
strncpy(buffer, beg, sizeof(buffer));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
vsnprintf(buffer + offset, sizeof(buffer) - offset, fmt, args);
|
fmtBufferv(buffer + offset, sizeof(buffer) - offset, fmt, args);
|
||||||
|
|
||||||
#if defined(TLOG_VS)
|
#if defined(TLOG_VS)
|
||||||
OutputDebugStringA(buffer);
|
OutputDebugStringA(buffer);
|
||||||
|
|
@ -123,7 +145,10 @@ void traceLogVaList(int level, const char *fmt, va_list args) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef TLOG_DONT_EXIT_ON_FATAL
|
#ifndef TLOG_DONT_EXIT_ON_FATAL
|
||||||
if (level == LogFatal) exit(1);
|
if (level == LogFatal) {
|
||||||
|
abort();
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef TLOG_MUTEX
|
#ifdef TLOG_MUTEX
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ extern "C" {
|
||||||
* -> TLOG_MUTEX: use a mutex on every traceLog call
|
* -> TLOG_MUTEX: use a mutex on every traceLog call
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include "collatypes.h"
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
|
|
||||||
133
colla/vmem.c
Normal file
133
colla/vmem.c
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
#include "vmem.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "tracelog.h"
|
||||||
|
|
||||||
|
static usize vmem__page_size = 0;
|
||||||
|
static void vmem__update_page_size(void);
|
||||||
|
|
||||||
|
// platform generic functions
|
||||||
|
|
||||||
|
usize vmemGetPageSize(void) {
|
||||||
|
if (!vmem__page_size) {
|
||||||
|
vmem__update_page_size();
|
||||||
|
}
|
||||||
|
return vmem__page_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
usize vmemPadToPage(usize byte_count) {
|
||||||
|
if (!vmem__page_size) {
|
||||||
|
vmem__update_page_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byte_count == 0) {
|
||||||
|
return vmem__page_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bit twiddiling, vmem__page_size MUST be a power of 2
|
||||||
|
usize padding = vmem__page_size - (byte_count & (vmem__page_size - 1));
|
||||||
|
if (padding == vmem__page_size) {
|
||||||
|
padding = 0;
|
||||||
|
}
|
||||||
|
return byte_count + padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if COLLA_WIN
|
||||||
|
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
void *vmemInit(usize size, usize *out_padded_size) {
|
||||||
|
usize alloc_size = vmemPadToPage(size);
|
||||||
|
|
||||||
|
void *ptr = VirtualAlloc(NULL, alloc_size, MEM_RESERVE, PAGE_NOACCESS);
|
||||||
|
|
||||||
|
if (out_padded_size) {
|
||||||
|
*out_padded_size = alloc_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool vmemRelease(void *base_ptr) {
|
||||||
|
return VirtualFree(base_ptr, 0, MEM_RELEASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool vmemCommit(void *ptr, usize num_of_pages) {
|
||||||
|
usize page_size = vmemGetPageSize();
|
||||||
|
|
||||||
|
void *new_ptr = VirtualAlloc(ptr, num_of_pages * page_size, MEM_COMMIT, PAGE_READWRITE);
|
||||||
|
|
||||||
|
if (!new_ptr) {
|
||||||
|
debug("ERROR: failed to commit memory: %lu\n", GetLastError());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new_ptr != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void vmem__update_page_size(void) {
|
||||||
|
SYSTEM_INFO info = {0};
|
||||||
|
GetSystemInfo(&info);
|
||||||
|
vmem__page_size = info.dwPageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif COLLA_LIN
|
||||||
|
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
struct vmem__header {
|
||||||
|
usize len;
|
||||||
|
};
|
||||||
|
|
||||||
|
void *vmemInit(usize size, usize *out_padded_size) {
|
||||||
|
size += sizeof(vmem__header);
|
||||||
|
usize alloc_size = padToPage(size);
|
||||||
|
|
||||||
|
vmem__header *header = mmap(NULL, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||||
|
|
||||||
|
if (header == MAP_FAILED) {
|
||||||
|
fatal("could not map %zu memory: %s", size, strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (padded_size) {
|
||||||
|
*padded_size = alloc_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
header->len = alloc_size;
|
||||||
|
|
||||||
|
return header + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool vmemRelease(void *base_ptr) {
|
||||||
|
if (!base_ptr) return false;
|
||||||
|
vmem__header *header = (vmem__header *)base_ptr - 1;
|
||||||
|
|
||||||
|
int res = munmap(header, header->len);
|
||||||
|
if (res == -1) {
|
||||||
|
err("munmap failed: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
return res != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool vmemCommit(void *ptr, usize num_of_pages) {
|
||||||
|
// mmap doesn't need a commit step
|
||||||
|
(VOID)ptr;
|
||||||
|
(VOID)num_of_pages;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void vmem__update_page_size(void) {
|
||||||
|
long lin_page_size = sysconf(_SC_PAGE_SIZE);
|
||||||
|
|
||||||
|
if (lin_page_size < 0) {
|
||||||
|
fatal("could not get page size: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
page_size = (usize)lin_page_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
12
colla/vmem.h
Normal file
12
colla/vmem.h
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
#ifndef VIRTUAL_MEMORY_HEADER
|
||||||
|
#define VIRTUAL_MEMORY_HEADER
|
||||||
|
|
||||||
|
#include "collatypes.h"
|
||||||
|
|
||||||
|
void *vmemInit(usize size, usize *out_padded_size);
|
||||||
|
bool vmemRelease(void *base_ptr);
|
||||||
|
bool vmemCommit(void *ptr, usize num_of_pages);
|
||||||
|
usize vmemGetPageSize(void);
|
||||||
|
usize vmemPadToPage(usize byte_count);
|
||||||
|
|
||||||
|
#endif // VIRTUAL_MEMORY_HEADER
|
||||||
7
colla/warnings/colla_warn_beg.h
Normal file
7
colla/warnings/colla_warn_beg.h
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
#if COLLA_CLANG
|
||||||
|
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Winitializer-overrides"
|
||||||
|
#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
|
||||||
|
|
||||||
|
#endif
|
||||||
5
colla/warnings/colla_warn_end.h
Normal file
5
colla/warnings/colla_warn_end.h
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#if COLLA_CLANG
|
||||||
|
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
|
|
||||||
#ifndef WIN32_LEAN_AND_MEAN
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef WIN32_EXTRA_LEAN
|
|
||||||
#define WIN32_EXTRA_LEAN
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
#endif // _WIN32
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
include = colla/
|
|
||||||
files = colla/*.c
|
|
||||||
|
|
||||||
[windows]
|
|
||||||
link = ws2_32.lib
|
|
||||||
|
|
||||||
[linux]
|
|
||||||
link = pthread
|
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <stdbool.h> // bool
|
|
||||||
#include <string.h> // memset
|
#include <string.h> // memset
|
||||||
|
#include "collatypes.h"
|
||||||
#include "tracelog.h" // fatal
|
#include "tracelog.h" // fatal
|
||||||
|
|
||||||
// heavily inspired by https://gist.github.com/Enichan/5f01140530ff0133fde19c9549a2a973
|
// heavily inspired by https://gist.github.com/Enichan/5f01140530ff0133fde19c9549a2a973
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#include "dir.h"
|
#include "dir.h"
|
||||||
#include "tracelog.h"
|
#include "tracelog.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#if COLLA_WIN
|
||||||
#include "win32_slim.h"
|
#include "win32_slim.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
@ -19,7 +19,7 @@ static dir_entry_t _fillDirEntry(WIN32_FIND_DATAW *data) {
|
||||||
.type =
|
.type =
|
||||||
data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ?
|
data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ?
|
||||||
FS_TYPE_DIR : FS_TYPE_FILE,
|
FS_TYPE_DIR : FS_TYPE_FILE,
|
||||||
.name = strFromWCHAR(data->cFileName, 0)
|
.name = strFromWChar(data->cFileName, 0)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,7 +52,7 @@ static void _getNext(_dir_internal_t *ctx) {
|
||||||
|
|
||||||
dir_t dirOpen(const char *path) {
|
dir_t dirOpen(const char *path) {
|
||||||
DWORD n = GetFullPathName(path, 0, NULL, NULL);
|
DWORD n = GetFullPathName(path, 0, NULL, NULL);
|
||||||
str_ostream_t out = ostrInitLen(n + 3);
|
outstream_t out = ostrInitLen(n + 3);
|
||||||
n = GetFullPathName(path, n, out.buf, NULL);
|
n = GetFullPathName(path, n, out.buf, NULL);
|
||||||
assert(n > 0);
|
assert(n > 0);
|
||||||
out.len += n;
|
out.len += n;
|
||||||
|
|
@ -196,7 +196,7 @@ bool dirRemove(const char *path) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dirClose(dir);
|
dirClose(dir);
|
||||||
#ifdef _WIN32
|
#if COLLA_WIN
|
||||||
return RemoveDirectoryA(path);
|
return RemoveDirectoryA(path);
|
||||||
#else
|
#else
|
||||||
return rmdir(path) == 0;
|
return rmdir(path) == 0;
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include "tracelog.h"
|
#include "tracelog.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#if COLLA_WIN
|
||||||
#include "win32_slim.h"
|
#include "win32_slim.h"
|
||||||
#include "str.h"
|
#include "str.h"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ extern "C" {
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include "collatypes.h"
|
||||||
#include "cthreads.h"
|
#include "cthreads.h"
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
#include "tracelog.h"
|
#include "tracelog.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#if COLLA_WIN
|
||||||
#include "win32_slim.h"
|
#include "win32_slim.h"
|
||||||
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,7 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <stdint.h>
|
#include "collatypes.h"
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include "file.h"
|
#include "file.h"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#include "jobpool.h"
|
#include "jobpool.h"
|
||||||
|
|
||||||
#include <vec.h>
|
#include "vec.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
cthread_func_t func;
|
cthread_func_t func;
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <collatypes.h>
|
#include "collatypes.h"
|
||||||
#include <cthreads.h>
|
#include "cthreads.h"
|
||||||
|
|
||||||
typedef void *jobpool_t;
|
typedef void *jobpool_t;
|
||||||
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#if COLLA_WIN
|
||||||
#define _BUFSZ 128
|
#define _BUFSZ 128
|
||||||
|
|
||||||
#include <lmcons.h>
|
#include <lmcons.h>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ extern "C" {
|
||||||
#include "str.h"
|
#include "str.h"
|
||||||
#include "collatypes.h"
|
#include "collatypes.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#if COLLA_WIN
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "win32_slim.h"
|
#include "win32_slim.h"
|
||||||
isize getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp);
|
isize getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp);
|
||||||
|
|
|
||||||
104
gen.lua
104
gen.lua
|
|
@ -1,104 +0,0 @@
|
||||||
local outfile = "colla.h"
|
|
||||||
local colladir = "src/"
|
|
||||||
|
|
||||||
local function hasArg(value)
|
|
||||||
for k,v in pairs(arg) do
|
|
||||||
if v == value then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local use_namespace = hasArg("-namespace")
|
|
||||||
|
|
||||||
os.remove(outfile)
|
|
||||||
|
|
||||||
local function cat(f)
|
|
||||||
local fp = io.open(f)
|
|
||||||
local text = fp:read("a")
|
|
||||||
fp:close()
|
|
||||||
return text
|
|
||||||
end
|
|
||||||
|
|
||||||
str = [[
|
|
||||||
/*
|
|
||||||
colla.h -- All colla libraries in a single header
|
|
||||||
Do the following in *one* C file to create the implementation
|
|
||||||
#define COLLA_IMPL
|
|
||||||
Use the following in the same C file for options
|
|
||||||
#define COLLA_NO_THREADS // don't include the threads module
|
|
||||||
#define COLLA_NO_NET // don't include networking stuff
|
|
||||||
*/
|
|
||||||
]]
|
|
||||||
|
|
||||||
str = str .. cat(colladir .. "collatypes.h") .. "\n"
|
|
||||||
str = str .. cat(colladir .. "tracelog.h") .. "\n"
|
|
||||||
str = str .. cat(colladir .. "str.h") .. "\n"
|
|
||||||
str = str .. cat(colladir .. "vec.h") .. "\n"
|
|
||||||
str = str .. cat(colladir .. "hashmap.h") .. "\n"
|
|
||||||
str = str .. cat(colladir .. "utf8.h") .. "\n"
|
|
||||||
str = str .. cat(colladir .. "ini.h") .. "\n"
|
|
||||||
str = str .. cat(colladir .. "strstream.h") .. "\n"
|
|
||||||
str = str .. cat(colladir .. "win32_slim.h") .. "\n"
|
|
||||||
str = str .. cat(colladir .. "file.h") .. "\n"
|
|
||||||
str = str .. cat(colladir .. "dir.h") .. "\n"
|
|
||||||
|
|
||||||
str = str .. "#ifndef COLLA_NO_NET\n"
|
|
||||||
str = str .. cat(colladir .. "socket.h") .. "\n"
|
|
||||||
str = str .. cat(colladir .. "http.h") .. "\n"
|
|
||||||
str = str .. "#endif // COLLA_NO_NET\n"
|
|
||||||
|
|
||||||
str = str .. "#if !defined(__TINYC__) && !defined(COLLA_NO_THREADS)\n"
|
|
||||||
str = str .. cat(colladir .. "cthreads.h") .. "\n"
|
|
||||||
str = str .. "#endif // !defined(__TINYC__) && !defined(COLLA_NO_THREADS)\n"
|
|
||||||
|
|
||||||
str = str .. "#ifdef COLLA_IMPL\n"
|
|
||||||
str = str .. cat(colladir .. "tracelog.c") .. "\n"
|
|
||||||
str = str .. cat(colladir .. "strstream.c") .. "\n"
|
|
||||||
str = str .. cat(colladir .. "str.c") .. "\n"
|
|
||||||
str = str .. cat(colladir .. "hashmap.c") .. "\n"
|
|
||||||
str = str .. cat(colladir .. "utf8.c") .. "\n"
|
|
||||||
str = str .. cat(colladir .. "ini.c") .. "\n"
|
|
||||||
str = str .. cat(colladir .. "file.c") .. "\n"
|
|
||||||
str = str .. cat(colladir .. "dir.c") .. "\n"
|
|
||||||
|
|
||||||
str = str .. "#ifndef COLLA_NO_NET\n"
|
|
||||||
str = str .. cat(colladir .. "socket.c") .. "\n"
|
|
||||||
str = str .. cat(colladir .. "http.c") .. "\n"
|
|
||||||
str = str .. "#endif // COLLA_NO_NET\n"
|
|
||||||
|
|
||||||
str = str .. "#if !defined(__TINYC__) && !defined(COLLA_NO_THREADS)\n"
|
|
||||||
str = str .. cat(colladir .. "cthreads.c") .. "\n"
|
|
||||||
str = str .. "#endif // !defined(__TINYC__) && !defined(COLLA_NO_THREADS)\n"
|
|
||||||
str = str .. "#endif /* COLLA_IMPL */\n"
|
|
||||||
|
|
||||||
-- remove includes
|
|
||||||
str = str:gsub('#include "([^"]+)"', '/* #include "%1" */')
|
|
||||||
|
|
||||||
str = str .. string.format([[
|
|
||||||
/*
|
|
||||||
MIT License
|
|
||||||
Copyright (c) 1994-2019 Lua.org, PUC-Rio.
|
|
||||||
Copyright (c) 2020-%s snarmph.
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
*/
|
|
||||||
]], os.date("%Y"))
|
|
||||||
|
|
||||||
local f = io.open(outfile, "w")
|
|
||||||
f:write(str)
|
|
||||||
f:close()
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue