Browse Source

first commit

master
Nick Hayashi 2 years ago
commit
34e4fb08c3
  1. 206
      alloc.cpp
  2. 77
      alloc.h
  3. 275
      array.hpp
  4. 149
      cpuid.cpp
  5. 8
      cpuid.h
  6. 209
      file.cpp
  7. 49
      file.h
  8. 280
      print.cpp
  9. 155
      print.h
  10. 316
      serialize.cpp
  11. 208
      serialize.h
  12. 50
      signal-handler.h
  13. 979
      string.h
  14. 720
      table.hpp
  15. 111
      types.h
  16. 10
      util.h

206
alloc.cpp

@ -0,0 +1,206 @@
#include "alloc.h"
#include "string.h"
#include "print.h"
#include "types.h"
#if false
static void* leakcheckMalloc(size_t size, const char* file, s32 line) {
TYPES_H_FTAG;
return malloc(size);
}
static void* leakcheckCalloc(size_t maxNumOfElements, size_t elementSize, const char* file, s32 line) {
TYPES_H_FTAG;
return calloc(maxNumOfElements, elementSize);
}
static void* leakcheckRealloc(void* buffer, size_t newSize, const char* file, s32 line) {
TYPES_H_FTAG;
return realloc(buffer, newSize);
}
static void leakcheckFree(void* ptr, const char* file, s32 line) {
TYPES_H_FTAG;
free(ptr);
}
#define malloc(size) leakcheckMalloc(size, __FILE__, __LINE__)
#define calloc(numElements, size) leakcheckCalloc(numElements, size, __FILE__, __LINE__)
#define realloc(buffer, newSize) leakcheckRealloc(buffer, newSize, __FILE__, __LINE__)
#define free(ptr) leakcheckFree(ptr, __FILE__, __LINE__)
static void dumpLeaks() {
void* address;
size_t size;
const char* file;
s32 line;
for (;;) {
println("%p - %lu, %s:%d", address, size, file, line);
}
}
#endif
// system allocators
void* pMalloc(size_t size) {
TYPES_H_FTAG;
void* p = malloc(size);
if (!p) {
die("Out of memory!n\nfailed to malloc %p with size %u\n", p, size);
}
return p;
}
void* pMalloc(size_t size, void* allocatorState) {
TYPES_H_FTAG;
return pMalloc(size);
}
void* pCalloc(size_t maxNumOfElements, size_t elementSize) {
TYPES_H_FTAG;
void* p = calloc(maxNumOfElements, elementSize);
if (!p) {
die("Out of memory!\nfailed to calloc %p with %u elements of size %u\n", p, maxNumOfElements, elementSize);
}
return p;
}
void* pCalloc(size_t maxNumOfElements, size_t elementSize, void* allocatorState) {
TYPES_H_FTAG;
return pCalloc(maxNumOfElements, elementSize);
}
void* pRealloc(void* buffer, size_t newSize) {
TYPES_H_FTAG;
void* p = realloc(buffer, newSize);
if (!p) {
//pFree(buffer); // if we ever *don't* terminate the program at this point, we should free |buffer|
die("Out of memory!\nfailed to realloc %p with size: %u\n", buffer, newSize);
}
return p;
}
void* pRealloc(void* buffer, size_t newSize, void* allocatorState) {
TYPES_H_FTAG;
return pRealloc(buffer, newSize);
}
void pFree(void* ptr) {
TYPES_H_FTAG;
free(ptr);
}
void pFree(void* ptr, void* allocatorState) {
TYPES_H_FTAG;
pFree(ptr);
}
void pFree(const void* ptr) {
TYPES_H_FTAG;
pFree((void*) ptr);
}
void pFree(const void* ptr, void* allocatorState) {
TYPES_H_FTAG;
pFree((void*) ptr, allocatorState);
}
#ifdef malloc
#undef malloc
#endif
#ifdef calloc
#undef calloc
#endif
#ifdef realloc
#undef realloc
#endif
#ifdef free
#undef free
#endif
static bool DefaultAllocatorInited = false;
static Allocator DefaultAllocator;
static void defaultAllocatorInit() {
TYPES_H_FTAG;
DefaultAllocator.state = null;
DefaultAllocator.mallocate = pMalloc;
DefaultAllocator.callocate = pCalloc;
DefaultAllocator.reallocate = pRealloc;
DefaultAllocator.free = pFree;
DefaultAllocatorInited = true;
}
Allocator* Allocator::GetDefault() {
TYPES_H_FTAG;
if (!DefaultAllocatorInited) defaultAllocatorInit();
return &DefaultAllocator;
}
//================================================================================
// alignment should be a power of 2
static u64 alignForward2(u64 ptr, size_t alignment) {
TYPES_H_FTAG;
u64 p, a, modulo;
p = ptr;
a = alignment;
modulo = p & (a - 1);
if (modulo != 0) {
p += a - modulo;
}
return p;
}
static u64 alignForward(u64 ptr, size_t alignment) {
TYPES_H_FTAG;
return ((ptr + alignment - 1) / alignment) * alignment;
}
//================================================================================
// Scratch/Arena
Arena* Arena::Init(u32 sizeInBytes) {
TYPES_H_FTAG;
Arena* arena = (Arena*) pMalloc(sizeof(Arena));
arena->index = 0;
arena->buffer = (u8*) pMalloc(sizeof(u8) * sizeInBytes);
arena->bufferSizeInBytes = sizeInBytes;
return arena;
}
void* Arena::Alloc(u32 sizeInBytes) {
TYPES_H_FTAG;
u8* p = this->buffer + this->index;
u32 offset = (u32) alignForward2((u64) p, 64);
if ((void*)(offset + sizeInBytes) <= (this->buffer + this->bufferSizeInBytes)) {
void* ptr = &this->buffer[offset];
this->index += offset + sizeInBytes;
String::memset(ptr, 0, sizeInBytes);
return ptr;
}
return null;
}
void Arena::Clear() {
TYPES_H_FTAG;
this->index = 0;
}
//================================================================================
struct StackAllocator {
u32 bufferSize;
u32 index;
u8* buffer;
};
struct PoolAllocator {
u8* buffer;
};

77
alloc.h

@ -0,0 +1,77 @@
#ifndef ALLOC_H
#define ALLOC_H
#include "types.h"
// define a consistent memory allocation interface
// trailing void* is a pointer to some allocator state, if relevant.
// will be unused for malloc/calloc/realloc/free, as the allocator state is internal to the OS.
// overloads should exist which do nothing with the trailing paramter.
typedef void* (*mallocator) (size_t, void*);
typedef void* (*callocator) (size_t, size_t, void*);
typedef void* (*reallocator) (void*, size_t, void*);
typedef void (*freeer) (void*, void*);
typedef void (*clearer) ( void*);
typedef void (*destroyer) ( void*);
// operating system allocator wrappers
extern void* pMalloc(size_t size);
extern void* pMalloc(size_t size, void* allocatorState);
extern void* pCalloc(size_t maxNumOfElements, size_t elementSize);
extern void* pCalloc(size_t maxNumOfElements, size_t elementSize, void* allocatorState);
extern void* pRealloc(void* buffer, size_t newSize);
extern void* pRealloc(void* buffer, size_t newSize, void* allocatorState);
extern void pFree(void* ptr);
extern void pFree(void* ptr, void* allocatorState);
extern void pFree(const void* ptr);
extern void pFree(const void* ptr, void* allocatorState);
struct Allocator {
void* state;
mallocator mallocate;
callocator callocate;
reallocator reallocate;
freeer free; // releases a specific piece of memory
clearer clear; // should release all the memory owned by this allocator at once.
destroyer destroy; // releases all the memory owned by this allocator, and also destroys the allocator.
static Allocator* GetDefault();
Allocator() {
this->state = null;
this->mallocate = pMalloc;
this->callocate = pCalloc;
this->reallocate = pRealloc;
this->free = pFree;
this->clear = null;
this->destroy = null;
}
Allocator(mallocator mallocate, freeer free) {
this->state = null;
this->mallocate = mallocate;
this->callocate = null;
this->reallocate = null;
this->free = free;
this->clear = null;
this->destroy = null;
}
};
struct Arena {
u32 bufferSizeInBytes;
u32 index;
u8* buffer;
static Arena* Init(u32 sizeInBytes = 1024);
void* Alloc(u32 sizeInBytes);
void Clear();
};
#endif

275
array.hpp

@ -0,0 +1,275 @@
#ifndef ARRAY_H
#define ARRAY_H
#include <new> // operator new, operator delete
#include "alloc.h" // allocators...
#include "serialize.h" // serialization
#include "string.h" // String::memcpy
#include "types.h" // type definitions
// this is a dynamic array (grows as needed)
// should work with any data type for T including primitive types
// some initial |capacity| is heap-allocated and a pointer is stored to it as |data|
// the |length| of the array, or number of filled slots is also tracked.
//
// it implements a single constructor, and operator new. no destructor, or operator overloading besides that.
// remember to use ->data or .data to actually access the underlying array.
//
// overhead:
// size of the struct will be 128 bits on 64-bit platforms
// note that if you heap allocate this structure and store it on another struct, you will have to chase two pointers to get at the data.
// to avoid this, I often include this struct in other structs so there's only one pointer dereference,
// just like including a raw pointer array + length in your struct.
// because I like to do this, automatic destructors are not useful.
//
template <typename T>
struct Array {
u32 length;
u32 capacity;
T* data;
Array<T>(u32 _capacity = 8) {
TYPES_H_FTAG;
this->length = 0;
this->capacity = _capacity;
this->data = (T*) pCalloc(sizeof (T), _capacity);
}
void* operator new(size_t size) {
TYPES_H_FTAG;
return pMalloc((u32) size);
}
void checkIfShouldGrow() {
TYPES_H_FTAG;
if (this->isFull()) {
// optimal number as you approach infinite elements approaches PHI, but 1.5 sometimes works better for finite sizes
// more testing is probably needed
this->capacity = (u32) (this->capacity * 1.5);
this->data = (T*) pRealloc(data, sizeof(T) * this->capacity);
}
}
// for when the order in the array doesn't matter, move the end of the array into the removed slot
void removeSwapWithEnd(u32 index) {
TYPES_H_FTAG;
if (this->isEmpty()) return; // overhead, maybe assert instead?
u32 end = this->length - 1;
if (index != end) {
this->data[index] = this->data[end];
}
this->pop();
}
void removeSwapWithEnd(T* addr) {
TYPES_H_FTAG;
for (u32 i = 0; i < this->length; i++) {
if ((this->data + i) == addr) {
removeSwapWithEnd(i);
return;
}
}
}
void removeAndShrink(u32 index) {
TYPES_H_FTAG;
for (u32 i = index + 1; i < this->length; i++) {
String::memcpy(this->data[i - 1], this->data[i], sizeof(T));
}
this->length--;
}
void removeAndShrink(T* elementAddr) {
TYPES_H_FTAG;
s32 index = -1;
for (u32 i = 0; i < this->length; i++) {
if ((this->data + i) == elementAddr) {
index = i;
break;
}
}
if (index == -1) {
return;
}
for (u32 i = index + 1; i < this->length; i++) {
String::memcpy((void*)(this->data + i - 1), (void*)(this->data + i), sizeof(T));
}
this->length--;
}
T pop() {
TYPES_H_FTAG;
if (this->isEmpty()) {
die("empty");
}
return this->data[--this->length];
}
// sometimes, you want to copy some POD data on the stack to the next position in the internal array
// that's what this does
u32 pushCopy(T* e) {
TYPES_H_FTAG;
this->checkIfShouldGrow();
String::memcpy((void*) &this->data[this->length++], e, sizeof(T));
return this->length - 1;
}
// returns the next address into which you can store a T. makes sure there's enough room first.
// it is irresponsible to call this and then not store a T in that address. this increments length,
// reserving the next spot for you.
T* pushNextAddrPromise() {
TYPES_H_FTAG;
this->checkIfShouldGrow();
return &this->data[this->length++];
}
u32 push(T e) {
TYPES_H_FTAG;
this->checkIfShouldGrow();
this->data[this->length++] = e;
return this->length - 1;
}
u32 pushMany(T* elements, u32 count) {
TYPES_H_FTAG;
// ensure we have capacity. if we have to realloc multiple times that can suck,
// but should be avoidable in practice by having an appropriately large initial capacity
while (this->capacity < (this->length + count)) {
this->capacity *= 1.5;
this->data = (T*) pRealloc(data, sizeof (T) * this->capacity);
}
u32 start = this->length;
for (u32 i1 = start, i2 = 0; i1 < count; i1++, i2++) {
this->data[this->length++] = elements[i2];
}
return start;
}
void reverse() {
TYPES_H_FTAG;
u32 count = this->length / 2;
for (u32 i = 0; i < count; i++) {
u32 offset = this->length - 1 - i;
T temp = this->data[i];
this->data[i] = this->data[offset];
this->data[offset] = temp;
}
}
T shift() {
TYPES_H_FTAG;
if (this->length == 0) {
return null;
}
T out = this->data[0];
this->length -= 1;
for (u32 i = 0; i < this->length; i++) {
*(this->data + i) = *(this->data + i + 1);
}
return out;
}
T unshift(T e) {
TYPES_H_FTAG;
this->checkIfShouldGrow();
for (u32 i = 0; i < this->length; i++) {
*(this->data + i + 1) = *(this->data + i);
}
this->data[0] = e;
this->length += 1;
return this->length;
}
T peek() const {
TYPES_H_FTAG;
if (this->isEmpty()) {
return null;
}
return this->data[this->length - 1];
}
bool isEmpty() const {
TYPES_H_FTAG;
return this->length == 0;
}
bool isFull() const {
TYPES_H_FTAG;
return this->length == this->capacity;
}
void clear() {
TYPES_H_FTAG;
this->length = 0;
}
};
template <typename T>
static void serialize(String* str, Array<T> array) {
TYPES_H_FTAG;
serialize(str, array.length);
serialize(str, array.capacity);
for (u32 i = 0; i < array.length; i++) {
serialize(str, array.data[i]);
}
}
template <typename T>
static void serialize(String* str, Array<T>* array) {
TYPES_H_FTAG;
SERIALIZE_HANDLE_NULL(str, array);
serialize(str, array->length);
serialize(str, array->capacity);
for (u32 i = 0; i < array->length; i++) {
serialize(str, array->data[i]);
}
}
template <typename T>
static void deserialize(char** buffer, Array<T>* array) {
TYPES_H_FTAG;
deserialize(buffer, &array->length);
deserialize(buffer, &array->capacity);
for (u32 i = 0; i < array->length; i++) {
deserialize(buffer, array->data + i);
}
}
template <typename T>
static void deserialize(char** buffer, Array<T>** array) {
TYPES_H_FTAG;
DESERIALIZE_HANDLE_NULL(buffer, array);
u32 length, capacity;
deserialize(buffer, &length);
deserialize(buffer, &capacity);
Array<T>* _array = new Array<T>(capacity);
_array->length = length;
for (u32 i = 0; i < _array->length; i++) {
deserialize(buffer, _array->data + i);
}
*array = _array;
}
#endif

149
cpuid.cpp

@ -0,0 +1,149 @@
#include "types.h"
#include "string.h"
#include "print.h"
static const char* szFeatures[] = {
"x87 FPU On Chip",
"Virtual-8086 Mode Enhancement",
"Debugging Extensions",
"Page Size Extensions",
"Time Stamp Counter",
"RDMR and WRMSR Support",
"Physical Address Extensions",
"Machine Check Exception",
"CMPXCHG8B Instruction",
"APIC On Chip",
"Unknown1",
"SYSENTER and SYSEXIT",
"Memory Type Range Registers",
"PTE Global Bit",
"Machine Check Architecture",
"Conditional Move/Compare Instruction",
"Page Attribute Table",
"36-bit Page Size Extension",
"Processor Serial Number",
"CFLUSH Extension",
"Unknown2",
"Debug Store",
"ThermalMonitor and Clock Ctrl",
"MMX Technology",
"FXSAVE/FXRSTOR",
"SSE Extensions",
"SSE2 Extensions",
"Self Snoop",
"Multithreading Technology",
"Thermal Monitor",
"Unknown4",
"Pending Break Enable"
};
#if defined(_WIN32)
// implementation originally from:
// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2008/hskdteyh(v=vs.90)?redirectedfrom=MSDN
#include <intrin.h>
void cpuid() {
TYPES_H_FTAG;
int nSteppingID = 0;
int nModel = 0;
int nFamily = 0;
int nProcessorType = 0;
int nExtendedmodel = 0;
int nExtendedfamily = 0;
int nBrandIndex = 0;
int nCLFLUSHcachelinesize = 0;
int nLogicalProcessors = 0;
int nAPICPhysicalID = 0;
int nFeatureInfo = 0;
int nCacheLineSize = 0;
int nL2Associativity = 0;
int nCacheSizeK = 0;
int nPhysicalAddress = 0;
int nVirtualAddress = 0;
int nRet = 0;
int nCores = 0;
int nCacheType = 0;
int nCacheLevel = 0;
int nMaxThread = 0;
int nSysLineSize = 0;
int nPhysicalLinePartitions = 0;
int nWaysAssociativity = 0;
int nNumberSets = 0;
unsigned nIds, nExIds, i;
bool bSSE3Instructions = false;
bool bMONITOR_MWAIT = false;
bool bCPLQualifiedDebugStore = false;
bool bVirtualMachineExtensions = false;
bool bEnhancedIntelSpeedStepTechnology = false;
bool bThermalMonitor2 = false;
bool bSupplementalSSE3 = false;
bool bL1ContextID = false;
bool bCMPXCHG16B = false;
bool bxTPRUpdateControl = false;
bool bPerfDebugCapabilityMSR = false;
bool bSSE41Extensions = false;
bool bSSE42Extensions = false;
bool bPOPCNT = false;
bool bMultithreading = false;
bool bLAHF_SAHFAvailable = false;
bool bCmpLegacy = false;
bool bSVM = false;
bool bExtApicSpace = false;
bool bAltMovCr8 = false;
bool bLZCNT = false;
bool bSSE4A = false;
bool bMisalignedSSE = false;
bool bPREFETCH = false;
bool bSKINITandDEV = false;
bool bSYSCALL_SYSRETAvailable = false;
bool bExecuteDisableBitAvailable = false;
bool bMMXExtensions = false;
bool bFFXSR = false;
bool b1GBSupport = false;
bool bRDTSCP = false;
bool b64Available = false;
bool b3DNowExt = false;
bool b3DNow = false;
bool bNestedPaging = false;
bool bLBRVisualization = false;
bool bFP128 = false;
bool bMOVOptimization = false;
bool bSelfInit = false;
bool bFullyAssociative = false;
int cpuinfo[4] = { -1 };
__cpuid(cpuinfo, 0);
unsigned count = cpuinfo[0];
char cpustring[0x20];
String::memset((void*) cpustring, '\0', sizeof(cpustring));
*((int*) cpustring) = cpuinfo[1];
*((int*) (cpustring + 4)) = cpuinfo[3];
*((int*) (cpustring + 8)) = cpuinfo[2];
println("CPU Identification String: %s", cpustring);
//for (s32 i = 0; i < count; i++) {
// __cpuid(cpuinfo, i);
// println("info type: %d", i);
// println("cpuinfo[0] = 0x%x", cpuinfo[0]);
// println("cpuinfo[1] = 0x%x", cpuinfo[1]);
// println("cpuinfo[2] = 0x%x", cpuinfo[2]);
// println("cpuinfo[3] = 0x%x", cpuinfo[3]);
//}
}
#else
void cpuid() {
TYPES_H_FTAG;
}
#endif

8
cpuid.h

@ -0,0 +1,8 @@
#ifndef CPUID_H
#define CPUID_H
void cpuid();
#endif

209
file.cpp

@ -0,0 +1,209 @@
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h> // fopen, fseek, ftell, fclose
#undef _CRT_SECURE_NO_WARNINGS
#include "alloc.h"
#include "array.hpp"
#include "file.h"
#include "print.h"
#include "types.h"
FILE* File::Open(const char* path, const char* mode) {
TYPES_H_FTAG;
return fopen(path, mode);
}
FILE* File::Open(const char* path, size_t* outSize, const char* mode) {
TYPES_H_FTAG;
FILE* fp = File::Open(path, mode);
if (fp == null) {
return null;
}
// get the file's size in bytes
fseek(fp, 0, SEEK_END);
*outSize = ftell(fp);
fseek(fp, 0L, SEEK_SET);
return fp;
}
void File::Close(FILE* file) {
fclose(file);
}
size_t File::Size(const char* path) {
TYPES_H_FTAG;
FILE* fp = File::Open(path);
// get the file's size in bytes
fseek(fp, 0, SEEK_END);
size_t size = ftell(fp);
fseek(fp, 0L, SEEK_SET);
fclose(fp);
return size;
}
size_t File::Size(FILE* fp) {
TYPES_H_FTAG;
fseek(fp, 0, SEEK_END);
size_t size = ftell(fp);
fseek(fp, 0L, SEEK_SET);
return size;
}
u8* File::Read(const char* path) {
TYPES_H_FTAG;
FILE* fp = File::Open(path, "rb");
if (fp == null) {
return null;
}
// get the file's size in bytes
fseek(fp, 0, SEEK_END);
u32 size = ftell(fp);
fseek(fp, 0L, SEEK_SET);
char* buffer = (char*) pMalloc(size + 1);
fread(buffer, sizeof (char), size + 1, fp);
buffer[size] = '\0';
fclose(fp);
return (u8*) buffer;
}
u8* File::Read(const char* path, size_t* outSize) {
TYPES_H_FTAG;
FILE* fp = File::Open(path, "rb");
if (fp == null) {
return null;
}
// get the file's size in bytes
fseek(fp, 0, SEEK_END);
size_t size = ftell(fp);
// if we got a valid pointer to a size_t we should report the size back.
if (outSize != null) {
*outSize = size;
}
fseek(fp, 0L, SEEK_SET);
char* buffer = (char*) pMalloc(size + 1);
fread(buffer, sizeof (char), size + 1, fp);
buffer[size] = '\0';
fclose(fp);
return (u8*) buffer;
}
size_t File::Read(FILE* fp, void* destination) {
TYPES_H_FTAG;
fseek(fp, 0, SEEK_END);
size_t size = ftell(fp);
fseek(fp, 0L, SEEK_SET);
fread(destination, sizeof (char), size + 1, fp);
return size;
}
size_t File::Read(FILE* fp, void* destination, size_t size) {
TYPES_H_FTAG;
return fread(destination, sizeof (char), size + 1, fp);
}
s32 File::Write(const char* path, char* data, u32 count) {
TYPES_H_FTAG;
FILE* fp = File::Open(path, "wb");
if (fp == null) {
return -1; // failed to open the file
}
size_t writtenCount = fwrite(data, 1, count, fp);
fclose(fp);
if (writtenCount != count) {
return -2; // wrote only partially
}
return 0;
}
#if _WIN32
#include <windows.h>
// writes the filenames into the provided array |outFileNames|, must be allocated ahead of time.
void File::GetFileNamesInFolder(const char* path, Array<char*>* outFileNames) {
TYPES_H_FTAG;
massert(path != null, "provided 'null' for path argument");
massert(outFileNames != null, "provided 'null' for array argument");
WIN32_FIND_DATAA findData;
HANDLE hFind = INVALID_HANDLE_VALUE;
String str;
if (path[String::len(path) - 1] != '/') {
str = String64f("%s/*", path);
} else {
str = String64f("%s*", path);
}
hFind = FindFirstFileA(str.c_str(), &findData);
if (hFind == INVALID_HANDLE_VALUE) die("failed to open folder: %s", path);
while (FindNextFileA(hFind, &findData) != 0) {
if (findData.cFileName[0] != '.') {
outFileNames->push(String::cpy(findData.cFileName));
}
}
FindClose(hFind);
}
#else
#include <dirent.h>
void File::GetFileNamesInFolder(const char* path, Array<char*>* outFileNames) {
TYPES_H_FTAG;
massert(path != null, "provided 'null' for path argument");
massert(outFileNames != null, "provided 'null' for array argument");
DIR* dir = opendir(path);
struct dirent* d;
if (dir == null) die("failed to open folder: %s", path);
do {
d = readdir(dir);
if (d == null) break;
if (d->d_name[0] != '.') outFileNames->push(String::cpy(d->d_name));
} while (1);
closedir(dir);
}
#endif
#include <sys/types.h>
#include <sys/stat.h>
#ifndef WIN32
#include <unistd.h>
#endif
// msvc provides a 'stat' equivalent as _stat.
#ifdef WIN32
#define stat _stat
#endif
time_t File::LastModified(const char* path) {
TYPES_H_FTAG;
struct stat result;
if (stat(path, &result) == 0) {
return result.st_mtime;
}
massert(false, "failed to get last modified timestamp.");
return -1;
}
#undef stat
s32 File::Rename(const char* oldFilename, const char* newFilename) {
return rename(oldFilename, newFilename);
}
s32 File::Remove(const char* path) {
return remove(path);
}

49
file.h

@ -0,0 +1,49 @@
#ifndef FILE_H
#define FILE_H
#include <stdio.h> // FILE
#include <sys/types.h> // time_t
#include "array.hpp"
namespace File {
FILE* Open(const char* path, const char* mode = "rb");
FILE* Open(const char* path, size_t* outSize, const char* mode = "rb");
void Close(FILE* file);
size_t Size(const char* path);
size_t Size(FILE* fp);
// most commonly this is what you want - provide a filesystem path, read the whole file,
// and return a buffer with the file's contents.
// the file is opened and closed internally.
// the buffer you get returned is allocated internally and must be freed by the caller.
u8* Read(const char* path);
// if you also want to know the size of the file.
u8* Read(const char* path, size_t* outSize);
// for apis that want a more incremental approach, this function will read |file|'s contents into |destination|,
// which must be pre-allocated. the provided file must be closed by the caller.
// returns the number of bytes read.
size_t Read(FILE* fp, void* destination); // calls fseek to determine file size
size_t Read(FILE* fp, void* destination, size_t size); // you know and provide the file size
s32 Write(const char* path, char* data, u32 count);
// writes the filenames into the provided array |outFileNames|, must be allocated ahead of time.
void GetFileNamesInFolder(const char* path, Array<char*>* outFileNames);
// get the last-modified timestamp on a file located at 'path'.
// there's no caching done, so it's the real, current value.
// specifically, it's the field 'st_mtime' on the 'stat' struct.
time_t LastModified(const char* path);
s32 Rename(const char* oldFilename, const char* newFilename);
s32 Remove(const char* path);
}
#endif

280
print.cpp

@ -0,0 +1,280 @@
#include <stdarg.h> // va_list, va_start, va_end
#include <stdio.h> // FILE, stderr, stdout | vfprintf
#include <stdlib.h> // exit
#include "alloc.h"
#include "string.h"
#include "print.h"
#include "types.h"
void vprint(const char* format, va_list args) {
TYPES_H_FTAG;
vfprintf(stdout, format, args);
}
void vprintln(const char* format, va_list args) {
TYPES_H_FTAG;
vprint(format, args);
print("\n");
}
/**
* The entire purpose of this is so we don't have to #import <stdio.h> everywhere
* +we intend to replace printf at some point with this
*/
void print(const char* format, ...) {
TYPES_H_FTAG;
if (format == null) { print("null"); return; }
va_list args;
va_start(args, format);
vprint(format, args);
va_end(args);
}
void println(const char* format, ...) {
TYPES_H_FTAG;
if (format == null) { print("null\n"); return; }
va_list args;
va_start(args, format);
vprintln(format, args);
va_end(args);
}
/**
* Prints a stack trace.
* Implementation varies for Win32 vs. *nix
*/
#define BACKTRACE_MAX_FRAMES 63
#ifdef _WIN32
#include <windows.h>
#include <dbghelp.h>
// if |string| is non-null, then the stack trace will be concatenated to it instead of being printed to stdout.
void trace(String* string) {
TYPES_H_FTAG;
#define BACKTRACE_MAX_FUNCTION_NAME_LENGTH 1024
HANDLE processHandle = GetCurrentProcess();
SymInitialize(processHandle, null, true);
void* stack[BACKTRACE_MAX_FRAMES];
unsigned short numFrames = CaptureStackBackTrace(0, BACKTRACE_MAX_FRAMES, stack, null);
char buffer[sizeof(SYMBOL_INFO) + (BACKTRACE_MAX_FUNCTION_NAME_LENGTH - 1) * sizeof(TCHAR)];
SYMBOL_INFO* symbol = (SYMBOL_INFO*) buffer;
symbol->MaxNameLen = BACKTRACE_MAX_FUNCTION_NAME_LENGTH;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
DWORD displacement;
IMAGEHLP_LINE64 line;
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
for (u32 i = 0; i < numFrames; i++) {
DWORD64 address = (DWORD64) stack[i];
SymFromAddr(processHandle, address, null, symbol);
if (SymGetLineFromAddr64(processHandle, address, &displacement, &line)) {
if (string == null) {
print("\tat %s in %s: line: %lu: address %0x%0X\n", symbol->Name, line.FileName, line.LineNumber, symbol->Address);
} else {
string->appendf("\tat %s in %s: line: %lu: address %0x%0X\n", symbol->Name, line.FileName, line.LineNumber, symbol->Address);
}
} else {
if (string == null) {
print("\tSymGetLineFromAddr64 returned error code %lu.\n", GetLastError());
print("\tat %s, address 0x%0X.\n", symbol->Name, symbol->Address);
} else {
string->appendf("\tSymGetLineFromAddr64 returned error code %lu.\n", GetLastError());
string->appendf("\tat %s, address 0x%0X.\n", symbol->Name, symbol->Address);
}
}
}
#undef BACKTRACE_MAX_FUNCTION_NAME_LENGTH
}
#else
// OSX and Linux stacktrace stuff.
#include <execinfo.h> // backtrace, backtrace_symbols
#include <cxxabi.h> // abi::__cxa_demangle
// if |string| is non-null, then the stack trace will be concatenated to it instead of being printed to stdout.
void trace(String* string) {
TYPES_H_FTAG;
void* stack[BACKTRACE_MAX_FRAMES];
u32 stackSize = backtrace(stack, BACKTRACE_MAX_FRAMES);
// resolve addresses into strings containing "filename(function+address)"
// this array must be free()-ed
char** traces = backtrace_symbols(stack, stackSize);
// iterate over the returned symbol lines. skip the first, it is the address of this function
for (u32 i = 1; i < stackSize; i++) {
// the names as provided by 'backtrace_symbols' are mangled for some reason.
// we have to demangle them, using this weird api
char buffer[1024];
const char* mangledNameBegin = String::firstCharOccurence(traces[i], '_');
const char* mangledNameEnd = String::lastCharOccurence(traces[i], '+');
if (mangledNameBegin == null || mangledNameEnd == null) {
// we can't demangle this name for some reason, just copy the mangled name to the buffer
size_t length = String::len(traces[i]);
String::memcpy(buffer, (void*)traces[i], length);
buffer[length] = '\0';
} else {
size_t length = mangledNameEnd - 1 - mangledNameBegin;
String::memcpy(buffer, (void*)mangledNameBegin, length);
buffer[length] = '\0';
}
s32 status = -1;
char* trace = abi::__cxa_demangle(buffer, null, null, &status);
if (trace == null) {
println("warning: failed to demangle name: %s", traces[i]);
continue;
}
if (string == null) {
print("%s\n", trace);
} else {
string->appendf("%s\n", trace);
}
}
pFree(traces);
}
#undef BACKTRACE_MAX_FRAMES
#endif
void _debug(const char* format, ...) {
TYPES_H_FTAG;
if (format == null) {
print("%sdebug:%s null\n", ANSI_BLUE, ANSI_RESET);
return;
}
va_list args;
va_start(args, format);
print("%sdebug:%s ", ANSI_BLUE, ANSI_RESET);
vprintln(format, args);
va_end(args);
}
void _warn(const char* format, ...) {
TYPES_H_FTAG;
if (format == null) {
print("%swarning:%s null\n", ANSI_YELLOW, ANSI_RESET);
return;
}
va_list args;
va_start(args, format);
print("%swarning:%s ", ANSI_YELLOW, ANSI_RESET);
vprintln(format, args);
va_end(args);
}
static void (*customDie)(const char* string) = null;
// if you want to override what happens by default when your program calls 'die', you can do so here.
// just keep in mind the intention is for 'die' to be for when your program has encountered a fatal, unrecoverable error.
void setCustomDieBehavior(void (*dieBehavior)(const char* string)) {
customDie = dieBehavior;
}
// for fatal errors which may occur at runtime, even on a release binary.
// if a fatal error should not occur at runtime on a release binary, consider preferring 'massert'
// it's unclear when you should use asserts vs. die actually. idk man
void die(const char* format, ...) {
TYPES_H_FTAG;
if (format == null) {
if (customDie == null) {
print("%serror:%s (unspecified error)\n", ANSI_RED, ANSI_RESET);
trace();
exit(1);
return;
} else {
String string = String128f("error: (unspecified error)\n");
trace(&string);
customDie(string.c_str());
return;
}
}
va_list args;
va_start(args, format);
if (customDie == null) {
println("%serror:%s", ANSI_RED, ANSI_RESET);
vprintln(format, args);
println();
va_end(args);
trace();
exit(1);
} else {
String string = String128f("");
string.setfv(format, args);
string.append("\n");
trace(&string);
va_end(args);
customDie(string.c_str());
}
}
void print(bool b) { TYPES_H_FTAG; print("%s", b ? "true" : "false"); }
void print(char c) { TYPES_H_FTAG; print("%c", c); }
void print(signed int i) { TYPES_H_FTAG; print("%d", i); }
void print(unsigned int i) { TYPES_H_FTAG; print("%u", i); }
void print(float f) { TYPES_H_FTAG; print("%.14g", f); }
void print(double d) { TYPES_H_FTAG; print("%.14g", d); }
void print(void* p) { TYPES_H_FTAG; print("%p", p); }
void print(char* s) { TYPES_H_FTAG; print("%s", s); }
#ifndef _WIN32
void print(size_t i) { TYPES_H_FTAG; print("%u", i); }
void println(size_t i) { TYPES_H_FTAG; print(i); print("\n"); }
#endif
void println(bool b) { TYPES_H_FTAG; print(b); print("\n"); }
void println(char c) { TYPES_H_FTAG; print(c); print("\n"); }
void println(signed int i) { TYPES_H_FTAG; print(i); print("\n"); }
void println(unsigned int i) { TYPES_H_FTAG; print(i); print("\n"); }
void println(float f) { TYPES_H_FTAG; print(f); print("\n"); }
void println(double d) { TYPES_H_FTAG; print(d); print("\n"); }
void println(void* p) { TYPES_H_FTAG; print(p); print("\n"); }
void println(char* s) { TYPES_H_FTAG; print(s); print("\n"); }
void println() { TYPES_H_FTAG; print("\n"); }
#ifdef _USING_GLM_TYPES__
void print(glm::vec<2, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; print("vec2: %.14g,%.14g", v.x, v.y); }
void print(glm::vec<3, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; print("vec3: %.14g,%.14g,%.14g", v.x, v.y, v.z); }
void print(glm::vec<4, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; print("vec4: %.14g,%.14g,%.14g,%.14g", v.x, v.y, v.z, v.w); }
void print(glm::mat<2, 2, float, (glm::qualifier) 3> m) { TYPES_H_FTAG; print("mat2: "); print(m[0]); print(m[1]); }
void print(glm::mat<3, 3, float, (glm::qualifier) 3> m) { TYPES_H_FTAG; print("mat3: "); print(m[0]); print(m[1]); print(m[2]); }
void print(glm::mat<4, 4, float, (glm::qualifier) 3> m) { TYPES_H_FTAG; print("mat4: "); print(m[0]); print(m[1]); print(m[2]); print(m[3]); }
void println(glm::vec<2, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; print(v); print("\n"); }
void println(glm::vec<3, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; print(v); print("\n"); }
void println(glm::vec<4, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; print(v); print("\n"); }
void println(glm::mat<2, 2, float, (glm::qualifier) 3> m) { TYPES_H_FTAG; print(m); print("\n"); }
void println(glm::mat<3, 3, float, (glm::qualifier) 3> m) { TYPES_H_FTAG; print(m); print("\n"); }
void println(glm::mat<4, 4, float, (glm::qualifier) 3> m) { TYPES_H_FTAG; print(m); print("\n"); }
#endif

155
print.h

@ -0,0 +1,155 @@
#ifndef PRINT_H
#define PRINT_H
#include <stdarg.h> // va_list
#include "string.h"
#include "types.h"
#ifndef _WIN32
// comment this line out if you don't want terminal output to contain ANSI escape codes
// by default on windows executables it's disabled, since things like the command prompt still doesn't support ANSI color codes
#define PRINT_H_USE_ANSI_COLOR_ESCAPE_CODES
#endif
#ifdef PRINT_H_USE_ANSI_COLOR_ESCAPE_CODES
#define ANSI_BLACK "\x001B[30m"
#define ANSI_RED "\x001B[31m"
#define ANSI_GREEN "\x001B[32m"
#define ANSI_YELLOW "\x001B[33m"
#define ANSI_BLUE "\x001B[34m"
#define ANSI_PURPLE "\x001B[35m"
#define ANSI_CYAN "\x001B[36m"
#define ANSI_WHITE "\x001B[37m"
#define ANSI_BRIGHT_BLACK "\x001B[90m"
#define ANSI_BRIGHT_RED "\x001B[91m"
#define ANSI_BRIGHT_GREEN "\x001B[92m"
#define ANSI_BRIGHT_YELLOW "\x001B[93m"
#define ANSI_BRIGHT_BLUE "\x001B[94m"
#define ANSI_BRIGHT_MAGENTA "\x001B[95m"
#define ANSI_BRIGHT_CYAN "\x001B[96m"
#define ANSI_BRIGHT_WHITE "\x001B[97m"
#define ANSI_BLACK_BACKGROUND "\x001B[40m"
#define ANSI_RED_BACKGROUND "\x001B[41m"
#define ANSI_GREEN_BACKGROUND "\x001B[42m"
#define ANSI_YELLOW_BACKGROUND "\x001B[43m"
#define ANSI_BLUE_BACKGROUND "\x001B[44m"
#define ANSI_PURPLE_BACKGROUND "\x001B[45m"
#define ANSI_CYAN_BACKGROUND "\x001B[46m"
#define ANSI_WHITE_BACKGROUND "\x001B[47m"
#define ANSI_WRAP "\x001B[7m"
#define ANSI_BLINK "\x001B[5m"
#define ANSI_CLEAR "\x001B[2J\x001B[;H"
#define ANSI_RESET "\x001B[0m"
#else
#define ANSI_BLACK ""
#define ANSI_RED ""
#define ANSI_GREEN ""
#define ANSI_YELLOW ""
#define ANSI_BLUE ""
#define ANSI_PURPLE ""
#define ANSI_CYAN ""
#define ANSI_WHITE ""
#define ANSI_BRIGHT_BLACK ""
#define ANSI_BRIGHT_RED ""
#define ANSI_BRIGHT_GREEN ""
#define ANSI_BRIGHT_YELLOW ""
#define ANSI_BRIGHT_BLUE ""
#define ANSI_BRIGHT_MAGENTA ""
#define ANSI_BRIGHT_CYAN ""
#define ANSI_BRIGHT_WHITE ""
#define ANSI_BLACK_BACKGROUND ""
#define ANSI_RED_BACKGROUND ""
#define ANSI_GREEN_BACKGROUND ""
#define ANSI_YELLOW_BACKGROUND ""
#define ANSI_BLUE_BACKGROUND ""
#define ANSI_PURPLE_BACKGROUND ""
#define ANSI_CYAN_BACKGROUND ""
#define ANSI_WHITE_BACKGROUND ""
#define ANSI_WRAP ""
#define ANSI_BLINK ""
#define ANSI_CLEAR ""
#define ANSI_RESET ""
#endif
extern void vprint(const char* format, va_list args);
extern void vprintln(const char* format, va_list args);
extern void print(const char* format, ...);
extern void println(const char* format, ...);
// Prints a stack trace, or concatenates the stack trace to |string| if it is not null.
extern void trace(String* string = null);
// This ends the program and calls trace(). generally you should use 'massert' instead
extern void die(const char* format, ...);
// when calling 'die', instead of the default behavior,
// (print a stack trace and then call 'exit(1)')
// do something else.
//
// The function will get passed a string which is the formatted string you passed to 'die' + a stack trace.
extern void setCustomDieBehavior(void (*dieBehavior)(const char* string));
#ifdef PRINT_DEBUG
// simple wrapper to allow custom messages...
//#define massert(test, message) (((void)(message), test))
#define massert(test, message) if (!(test)) die("%s\n", message);
extern void _debug(const char* format, ...);
#define debug(format, ...) _debug(format, ##__VA_ARGS__)
// @NOTE there's a conflict on win32 with the name 'warning'...
extern void _warn(const char* format, ...);
#define warn(format, ...) _warn(format, ##__VA_ARGS__)
#else
// define some empty macros
#define massert(test, message) ((void) 0)
#define debug(format, ...)
#define warn(format, ...)
#endif
extern void print(bool b);
extern void print(char c);
extern void print(signed int i);
extern void print(unsigned int i);
extern void print(float f);
extern void print(double d);
extern void print(void* p);
extern void print(char* s);
#ifndef _WIN32
extern void print(size_t i);
extern void println(size_t i);
#endif
extern void println(bool b);
extern void println(char c);
extern void println(signed int i);
extern void println(unsigned int i);
extern void println(float f);
extern void println(double d);
extern void println(void* p);
extern void println(char* s);
extern void println();
#ifdef _USING_GLM_TYPES__
extern void print(glm::vec<2, float, (glm::qualifier) 3>);
extern void print(glm::vec<3, float, (glm::qualifier) 3>);
extern void print(glm::vec<4, float, (glm::qualifier) 3>);
extern void print(glm::mat<2, 2, float, (glm::qualifier) 3>);
extern void print(glm::mat<3, 3, float, (glm::qualifier) 3>);
extern void print(glm::mat<4, 4, float, (glm::qualifier) 3>);
extern void println(glm::vec<2, float, (glm::qualifier) 3> v);
extern void println(glm::vec<3, float, (glm::qualifier) 3> v);
extern void println(glm::vec<3, float, (glm::qualifier) 3> v);
extern void println(glm::mat<2, 2, float, (glm::qualifier) 3> m);
extern void println(glm::mat<3, 3, float, (glm::qualifier) 3> m);
extern void println(glm::mat<4, 4, float, (glm::qualifier) 3> m);
#endif
#endif

316
serialize.cpp

@ -0,0 +1,316 @@
#include <fast_float/fast_float.h>
#include "types.h"
#include "serialize.h"
#include "string.h"
#include "print.h"
static inline const char* getFormatStringOut(u8 v) { TYPES_H_FTAG; return "%hu\n"; }
static inline const char* getFormatStringOut(u16 v) { TYPES_H_FTAG; return "%hu\n"; }
static inline const char* getFormatStringOut(u32 v) { TYPES_H_FTAG; return "%u\n"; }
static inline const char* getFormatStringOut(u64 v) { TYPES_H_FTAG; return "%llu\n"; }
static inline const char* getFormatStringOut(s8 v) { TYPES_H_FTAG; return "%hd\n"; }
static inline const char* getFormatStringOut(s16 v) { TYPES_H_FTAG; return "%hd\n"; }
static inline const char* getFormatStringOut(s32 v) { TYPES_H_FTAG; return "%d\n"; }
static inline const char* getFormatStringOut(s64 v) { TYPES_H_FTAG; return "%lld\n"; }
static inline const char* getFormatStringOut(float v) { TYPES_H_FTAG; return "%f\n"; }
static inline const char* getFormatStringOut(double v) { TYPES_H_FTAG; return "%f\n"; }
// important constraint - strings need to be wrapped in double-quotes.
// the sentinel value 'null' without quotations is used to denote null values, which means
// if strings were not wrapped in double quotes, you would not be able to distinguish null
// values from the literal string "null".
static inline const char* getFormatStringOut(char* v) { TYPES_H_FTAG; return "\"%s\"\n"; }
static inline const char* getFormatStringOut(const char* v) { TYPES_H_FTAG; return "\"%s\"\n"; }
#ifdef _USING_GLM_TYPES__
static inline const char* getFormatStringOut(glm::vec<2, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; return "%f %f\n"; }
static inline const char* getFormatStringOut(glm::vec<3, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; return "%f %f %f\n"; }
static inline const char* getFormatStringOut(glm::vec<4, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; return "%f %f %f %f\n"; }
static inline const char* getFormatStringOut(glm::mat<2, 2, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; return "%f %f %f %f\n"; }
static inline const char* getFormatStringOut(glm::mat<3, 3, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; return "%f %f %f %f %f %f %f %f %f\n"; }
static inline const char* getFormatStringOut(glm::mat<4, 4, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; return "%f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f\n"; }
#endif
#define SERIALIZE_H_FUNC_BODY str->appendf(getFormatStringOut(v), v);
void serialize(String* str, u8 v) { TYPES_H_FTAG; SERIALIZE_H_FUNC_BODY }
void serialize(String* str, u16 v) { TYPES_H_FTAG; SERIALIZE_H_FUNC_BODY }
void serialize(String* str, u32 v) { TYPES_H_FTAG; SERIALIZE_H_FUNC_BODY }
void serialize(String* str, u64 v) { TYPES_H_FTAG; SERIALIZE_H_FUNC_BODY }
void serialize(String* str, s8 v) { TYPES_H_FTAG; SERIALIZE_H_FUNC_BODY }
void serialize(String* str, s16 v) { TYPES_H_FTAG; SERIALIZE_H_FUNC_BODY }
void serialize(String* str, s32 v) { TYPES_H_FTAG; SERIALIZE_H_FUNC_BODY }
void serialize(String* str, s64 v) { TYPES_H_FTAG; SERIALIZE_H_FUNC_BODY }
void serialize(String* str, float v) { TYPES_H_FTAG; SERIALIZE_H_FUNC_BODY }
void serialize(String* str, double v) { TYPES_H_FTAG; SERIALIZE_H_FUNC_BODY }
template<typename T> // do I really need a template for this?
static inline void deserializeInteger(char** buffer, T* v) {
TYPES_H_FTAG;
char* _buffer = *buffer;
T value = 0;
// skip leading whitespace - all our functions do this, so gotta
while (String::isAsciiWhitespace(*_buffer)) {
_buffer++;
}
// check the first character for a negative sign
bool negative = false;
if (_buffer[0] == '-') {
negative = true;
_buffer++;
}
// parse the digits of the number. doesn't account for overflow
while (String::isDigit(*_buffer)) {
value = 10 * value + (*_buffer++ - '0');
}
// make it negative if the first character was '-'
if (negative) value *= -1;
// store the value back into v
*v = value;
// if the original pointer is the same as the current, we didn't parse anything
massert(_buffer != *buffer, "tried to parse an integer, and found nothing");
// report back how far we advanced the buffer
*buffer = _buffer;
}
// just for integers, signed/unsigned including size_t
#define SERIALIZE_H_DESERIALIZE_FUNC_BODY deserializeInteger(buffer, v);
void deserialize(char** buffer, u8* v) { SERIALIZE_H_DESERIALIZE_FUNC_BODY }
void deserialize(char** buffer, u16* v) { SERIALIZE_H_DESERIALIZE_FUNC_BODY }
void deserialize(char** buffer, u32* v) { SERIALIZE_H_DESERIALIZE_FUNC_BODY }
void deserialize(char** buffer, u64* v) { SERIALIZE_H_DESERIALIZE_FUNC_BODY }
void deserialize(char** buffer, s8* v) { SERIALIZE_H_DESERIALIZE_FUNC_BODY }
void deserialize(char** buffer, s16* v) { SERIALIZE_H_DESERIALIZE_FUNC_BODY }
void deserialize(char** buffer, s32* v) { SERIALIZE_H_DESERIALIZE_FUNC_BODY }
void deserialize(char** buffer, s64* v) { SERIALIZE_H_DESERIALIZE_FUNC_BODY }
// base value = ceil(log10(2^64)) or ceil(log10(2^32))
// is this right? lemire's paper mentions you only need 17 digits for 64 bit floats: https://lemire.me/en/publication/arxiv2101/
// +1 for 'e'
// +1 for '.'
// +1 for '^'
// +1 for '-'/'+'
static const u32 BINARY32_MAX_CHARS = 14;
static const u32 BINARY64_MAX_CHARS = 24;
void deserialize(char** buffer, float* v) {
TYPES_H_FTAG;
char* _buffer = *buffer;
while (String::isAsciiWhitespace(*_buffer)) _buffer++;
fast_float::from_chars_result result = fast_float::from_chars(_buffer, _buffer + BINARY32_MAX_CHARS, *v);
massert(result.ec == std::errc(), "failed to parse a float");
*buffer = (