mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
Auto-import std::core. Fix module assignment of declarations. Introspection improvements. Deref null error panics in safe mode. Support for LLVM 15
This commit is contained in:
committed by
Christoffer Lerno
parent
df41caabdd
commit
b1d83e2ccd
205
lib/std/core/builtin.c3
Normal file
205
lib/std/core/builtin.c3
Normal file
@@ -0,0 +1,205 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::builtin;
|
||||
import libc;
|
||||
|
||||
fault IteratorResult
|
||||
{
|
||||
NO_MORE_ELEMENT
|
||||
}
|
||||
|
||||
fault VarCastResult
|
||||
{
|
||||
TYPE_MISMATCH
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a variable on the stack, then restores it at the end of the
|
||||
* macro scope.
|
||||
*
|
||||
* @param variable `the variable to store and restore`
|
||||
**/
|
||||
macro void @scope(&variable; @body) @autoimport
|
||||
{
|
||||
$typeof(variable) temp = variable;
|
||||
defer variable = temp;
|
||||
@body();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a variant type to a type, returning an failure if there is a type mismatch.
|
||||
*
|
||||
* @param v `the variant to convert to the given type.`
|
||||
* @param $Type `the type to convert to`
|
||||
* @return `The variant.ptr converted to its type.`
|
||||
**/
|
||||
macro varcast(variant v, $Type) @autoimport
|
||||
{
|
||||
if (v.type != $Type.typeid) return VarCastResult.TYPE_MISMATCH!;
|
||||
return ($Type*)v.ptr;
|
||||
}
|
||||
|
||||
extern fn void printf(char*, ...);
|
||||
|
||||
struct CallstackElement
|
||||
{
|
||||
CallstackElement* prev;
|
||||
char* function;
|
||||
char* file;
|
||||
uint line;
|
||||
}
|
||||
fn void panic(char* message, char *file, char *function, uint line) @autoimport
|
||||
{
|
||||
CallstackElement* stack = $$stacktrace();
|
||||
$if ($defined(libc::stderr) && $defined(libc::fprintf)):
|
||||
|
||||
if (stack) stack = stack.prev;
|
||||
if (stack)
|
||||
{
|
||||
libc::fprintf(libc::stderr(), "\nERROR: '%s'\n", message);
|
||||
}
|
||||
else
|
||||
{
|
||||
libc::fprintf(libc::stderr(), "\nERROR: '%s', function %s (%s:%d)\n", message, function, file, line);
|
||||
}
|
||||
while (stack)
|
||||
{
|
||||
libc::fprintf(libc::stderr(), " at function %s (%s:%u)\n", stack.function, stack.file, stack.line);
|
||||
if (stack == stack.prev) break;
|
||||
stack = stack.prev;
|
||||
}
|
||||
|
||||
$endif;
|
||||
|
||||
$$trap();
|
||||
}
|
||||
|
||||
macro unreachable($string = "Unreachable statement reached.") @autoimport @noreturn
|
||||
{
|
||||
panic($string, $$FILE, $$FUNC, $$LINE);
|
||||
$$unreachable();
|
||||
}
|
||||
|
||||
enum TypeKind : char
|
||||
{
|
||||
VOID, // 0
|
||||
BOOL, // 1
|
||||
FLOAT, // 2
|
||||
INTEGER,// 3
|
||||
STRUCT, // 4
|
||||
UNION, // 5
|
||||
ENUM, // 6
|
||||
FAULT, // 7
|
||||
ARRAY,
|
||||
POINTER,
|
||||
VAR_ARRAY,
|
||||
SUBARRAY,
|
||||
OPAQUE
|
||||
// ALIAS,
|
||||
}
|
||||
|
||||
struct TypeEnum
|
||||
{
|
||||
TypeKind type;
|
||||
usize elements;
|
||||
}
|
||||
/*
|
||||
enum TypeKind
|
||||
{
|
||||
VOID,
|
||||
BOOL,
|
||||
FLOAT,
|
||||
INTEGER,
|
||||
STRUCT,
|
||||
UNION,
|
||||
ERROR,
|
||||
ENUM,
|
||||
ARRAY,
|
||||
POINTER,
|
||||
VAR_ARRAY,
|
||||
SUBARRAY,
|
||||
OPAQUE
|
||||
// ALIAS,
|
||||
}
|
||||
|
||||
struct TypeData
|
||||
{
|
||||
typeid typeId;
|
||||
TypeKind kind;
|
||||
int size;
|
||||
int alignment;
|
||||
char* name;
|
||||
char* fullName;
|
||||
}
|
||||
|
||||
struct TypeAlias
|
||||
{
|
||||
TypeData data;
|
||||
typeid aliasType;
|
||||
}
|
||||
|
||||
struct TypeError
|
||||
{
|
||||
TypeData data;
|
||||
TypeErrorValue[] errors;
|
||||
}
|
||||
|
||||
struct TypeArray
|
||||
{
|
||||
TypeData data;
|
||||
typeid elementType;
|
||||
ulong elements;
|
||||
}
|
||||
|
||||
struct TypeVarArray
|
||||
{
|
||||
TypeData data;
|
||||
typeid elementType;
|
||||
}
|
||||
|
||||
struct TypeSubarray
|
||||
{
|
||||
TypeData data;
|
||||
typeid elementType;
|
||||
}
|
||||
|
||||
struct TypePointer
|
||||
{
|
||||
TypeData data;
|
||||
typeid baseType;
|
||||
}
|
||||
|
||||
struct TypeStruct
|
||||
{
|
||||
TypeData data;
|
||||
TypeData*[] fields;
|
||||
}
|
||||
|
||||
struct TypeUnion
|
||||
{
|
||||
TypeData data;
|
||||
TypeData*[] variants;
|
||||
}
|
||||
|
||||
struct TypeEnum
|
||||
{
|
||||
TypeData data;
|
||||
typeid valueType;
|
||||
TypeData*[] associated_value_types;
|
||||
}
|
||||
|
||||
|
||||
struct TypeEnumValue
|
||||
{
|
||||
char* name;
|
||||
ulong value;
|
||||
void*[] associated_values;
|
||||
}
|
||||
|
||||
struct TypeErrorValue
|
||||
{
|
||||
char* name;
|
||||
ulong value;
|
||||
}
|
||||
*/
|
||||
87
lib/std/core/cinterop.c3
Normal file
87
lib/std/core/cinterop.c3
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::cinterop;
|
||||
|
||||
const C_INT_SIZE = $$C_INT_SIZE;
|
||||
const C_LONG_SIZE = $$C_LONG_SIZE;
|
||||
const C_SHORT_SIZE = $$C_SHORT_SIZE;
|
||||
const C_LONG_LONG_SIZE = $$C_LONG_LONG_SIZE;
|
||||
|
||||
$assert (C_SHORT_SIZE < 32);
|
||||
$assert (C_INT_SIZE < 128);
|
||||
$assert (C_LONG_SIZE < 128);
|
||||
$assert (C_LONG_LONG_SIZE <= 128);
|
||||
$assert (C_SHORT_SIZE <= C_INT_SIZE);
|
||||
$assert (C_INT_SIZE <= C_LONG_SIZE);
|
||||
$assert (C_LONG_SIZE <= C_LONG_LONG_SIZE);
|
||||
|
||||
$switch ($$C_INT_SIZE):
|
||||
$case 64:
|
||||
define CInt = long;
|
||||
define CUInt = ulong;
|
||||
$case 32:
|
||||
define CInt = int;
|
||||
define CUInt = uint;
|
||||
$case 16:
|
||||
define CInt = short;
|
||||
define CUInt = ushort;
|
||||
$default:
|
||||
$assert(false, "Invalid C int size");
|
||||
$endswitch;
|
||||
|
||||
$switch ($$C_LONG_SIZE):
|
||||
$case 64:
|
||||
define CLong = long;
|
||||
define CULong = ulong;
|
||||
$case 32:
|
||||
define CLong = int;
|
||||
define CULong = uint;
|
||||
$case 16:
|
||||
define CLong = short;
|
||||
define CULong = ushort;
|
||||
$default:
|
||||
$assert(false, "Invalid C long size");
|
||||
$endswitch;
|
||||
|
||||
$switch ($$C_SHORT_SIZE):
|
||||
$case 32:
|
||||
define CShort = int;
|
||||
define CUShort = uint;
|
||||
$case 16:
|
||||
define CShort = short;
|
||||
define CUShort = ushort;
|
||||
$case 8:
|
||||
define CShort = ichar;
|
||||
define CUShort = char;
|
||||
$default:
|
||||
$assert(false, "Invalid C short size");
|
||||
$endswitch;
|
||||
|
||||
$switch ($$C_LONG_LONG_SIZE):
|
||||
$case 128:
|
||||
define CLongLong = int128;
|
||||
define CULongLong = uint128;
|
||||
$case 64:
|
||||
define CLongLong = long;
|
||||
define CULongLong = ulong;
|
||||
$case 32:
|
||||
define CLongLong = int;
|
||||
define CULongLong = uint;
|
||||
$case 16:
|
||||
define CLongLong = short;
|
||||
define CULongLong = ushort;
|
||||
$default:
|
||||
$assert(false, "Invalid C long long size");
|
||||
$endswitch;
|
||||
|
||||
|
||||
|
||||
define CSChar = ichar;
|
||||
define CUChar = char;
|
||||
|
||||
$if ($$C_CHAR_IS_SIGNED):
|
||||
define CChar = ichar;
|
||||
$else:
|
||||
define CChar = char;
|
||||
$endif;
|
||||
58
lib/std/core/env.c3
Normal file
58
lib/std/core/env.c3
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::env;
|
||||
|
||||
enum CompilerOptLevel
|
||||
{
|
||||
O0,
|
||||
O1,
|
||||
O2,
|
||||
O3
|
||||
}
|
||||
|
||||
enum OsType
|
||||
{
|
||||
UNKNOWN,
|
||||
NONE,
|
||||
ANANAS,
|
||||
CLOUD_ABI,
|
||||
DRAGON_FLY,
|
||||
FREEBSD,
|
||||
FUCHSIA,
|
||||
IOS,
|
||||
KFREEBSD,
|
||||
LINUX,
|
||||
PS3,
|
||||
MACOSX,
|
||||
NETBSD,
|
||||
OPENBSD,
|
||||
SOLARIS,
|
||||
WIN32,
|
||||
HAIKU,
|
||||
MINIX,
|
||||
RTEMS,
|
||||
NACL, // Native Client
|
||||
CNK, // BG/P Compute-Node Kernel
|
||||
AIX,
|
||||
CUDA,
|
||||
NVOPENCL,
|
||||
AMDHSA,
|
||||
PS4,
|
||||
ELFIAMCU,
|
||||
TVOS,
|
||||
WATCHOS,
|
||||
MESA3D,
|
||||
CONTIKI,
|
||||
AMDPAL,
|
||||
HERMITCORE,
|
||||
HURD,
|
||||
WASI,
|
||||
EMSCRIPTEN,
|
||||
}
|
||||
|
||||
const OsType OS_TYPE = (OsType)($$OS_TYPE);
|
||||
const CompilerOptLevel COMPILER_OPT_LEVEL = (CompilerOptLevel)($$COMPILER_OPT_LEVEL);
|
||||
const bool BIG_ENDIAN = $$PLATFORM_BIG_ENDIAN;
|
||||
const bool I128_SUPPORT = $$PLATFORM_I128_SUPPORTED;
|
||||
const bool COMPILER_SAFE_MODE = $$COMPILER_SAFE_MODE;
|
||||
214
lib/std/core/mem.c3
Normal file
214
lib/std/core/mem.c3
Normal file
@@ -0,0 +1,214 @@
|
||||
// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module std::core::mem;
|
||||
|
||||
macro @volatile_load(&x)
|
||||
{
|
||||
return $$volatile_load(&x);
|
||||
}
|
||||
|
||||
macro @volatile_store(&x, y)
|
||||
{
|
||||
return $$volatile_store(&x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require math::is_power_of_2(alignment)
|
||||
**/
|
||||
fn usize aligned_offset(usize offset, usize alignment)
|
||||
{
|
||||
return alignment * ((offset + alignment - 1) / alignment);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @require math::is_power_of_2(alignment)
|
||||
**/
|
||||
fn bool ptr_is_aligned(void* ptr, usize alignment) @inline
|
||||
{
|
||||
return (uptr)ptr & ((uptr)alignment - 1) == 0;
|
||||
}
|
||||
|
||||
fn void copy(char* dst, char* src, usize size) @inline
|
||||
{
|
||||
memcpy(dst, src, size);
|
||||
}
|
||||
|
||||
macro void memcpy(void* dst, void* src, usize size, bool $is_volatile = false, usize $dst_align = 0, usize $src_align = 0)
|
||||
{
|
||||
$$memcpy(dst, src, size, $is_volatile, $dst_align, $src_align);
|
||||
}
|
||||
|
||||
fn void set(void* dst, char val, usize bytes) @inline
|
||||
{
|
||||
memset(dst, val, bytes);
|
||||
}
|
||||
|
||||
macro void memset(void* dst, char val, usize bytes, bool $is_volatile = false, usize $dst_align = 0)
|
||||
{
|
||||
$$memset(dst, val, bytes, $is_volatile, $dst_align);
|
||||
}
|
||||
|
||||
macro bitcast(expr, $Type)
|
||||
{
|
||||
var $size = (usize)($sizeof(expr));
|
||||
$assert($size == $Type.sizeof, "Cannot bitcast between types of different size.");
|
||||
$Type x = void;
|
||||
@memcpy(&x, &expr, $size, false, $alignof($Type), $alignof(expr));
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
enum AllocationKind
|
||||
{
|
||||
ALLOC,
|
||||
CALLOC,
|
||||
REALLOC,
|
||||
FREE,
|
||||
RESET,
|
||||
}
|
||||
|
||||
fault AllocationFailure
|
||||
{
|
||||
OUT_OF_MEMORY,
|
||||
UNSUPPORTED_OPERATION,
|
||||
}
|
||||
|
||||
private tlocal Allocator thread_allocator = { null, SYSTEM_ALLOCATOR };
|
||||
|
||||
macro Allocator current_allocator()
|
||||
{
|
||||
return thread_allocator;
|
||||
}
|
||||
|
||||
struct Allocator
|
||||
{
|
||||
void* data;
|
||||
AllocatorFunction function;
|
||||
}
|
||||
|
||||
macro malloc($Type)
|
||||
{
|
||||
return ($Type*)(mem::alloc($Type.sizeof));
|
||||
}
|
||||
|
||||
fn char[] alloc_bytes(usize bytes) @inline
|
||||
{
|
||||
return ((char*)thread_allocator.alloc(bytes, 1))[0..bytes - 1]!!;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
*/
|
||||
fn void* alloc(usize size, usize alignment = 0)
|
||||
{
|
||||
return thread_allocator.alloc(size, alignment)!!;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
*/
|
||||
fn void*! alloc_checked(usize size, usize alignment = 0)
|
||||
{
|
||||
return thread_allocator.alloc(size, alignment);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
*/
|
||||
fn void* calloc(usize size, usize alignment = 0)
|
||||
{
|
||||
return thread_allocator.calloc(size, alignment)!!;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
*/
|
||||
fn void*! calloc_checked(usize size, usize alignment = 0)
|
||||
{
|
||||
return thread_allocator.calloc(size, alignment);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
*/
|
||||
fn void* realloc(void *ptr, usize new_size, usize alignment = 0)
|
||||
{
|
||||
return thread_allocator.realloc(ptr, new_size, alignment)!!;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
*/
|
||||
fn void*! realloc_checked(void *ptr, usize new_size, usize alignment = 0)
|
||||
{
|
||||
return thread_allocator.realloc(ptr, new_size, alignment);
|
||||
}
|
||||
|
||||
fn void free(void* ptr)
|
||||
{
|
||||
return thread_allocator.free(ptr)!!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run with a specific allocator inside of the macro body.
|
||||
**/
|
||||
macro void @with_allocator(Allocator allocator; @body())
|
||||
{
|
||||
Allocator old_allocator = thread_allocator;
|
||||
thread_allocator = allocator;
|
||||
defer thread_allocator = old_allocator;
|
||||
@body();
|
||||
}
|
||||
|
||||
fn void*! talloc(usize size)
|
||||
{
|
||||
return temp_allocator.alloc(size);
|
||||
}
|
||||
|
||||
struct MemoryArena
|
||||
{
|
||||
void* memory;
|
||||
usize total;
|
||||
usize used;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require alignment > 0 `alignment must be non zero`
|
||||
* @require math::is_power_of_2(alignment)
|
||||
* @require size > 0
|
||||
* @require alignment <= MAX_MEMORY_ALIGNMENT `alignment too big`
|
||||
* @require this != null
|
||||
**/
|
||||
fn void*! MemoryArena.alloc(MemoryArena* this, usize size, usize alignment, usize prefixed_bytes = 0)
|
||||
{
|
||||
void* start_mem = this.memory;
|
||||
void* unaligned_pointer = start_mem + this.used + prefixed_bytes;
|
||||
if ((uptr)unaligned_pointer < (uptr)start_mem) return AllocationFailure.OUT_OF_MEMORY!;
|
||||
usize offset_start = aligned_offset((usize)(uptr)unaligned_pointer, alignment) - (usize)(uptr)start_mem;
|
||||
usize end = offset_start + size;
|
||||
if (end > this.total || end < offset_start) return AllocationFailure.OUT_OF_MEMORY!;
|
||||
this.used = end;
|
||||
return start_mem + offset_start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a memory arena for use using the provided bytes.
|
||||
*
|
||||
* @require this != null
|
||||
**/
|
||||
fn void MemoryArena.init(MemoryArena* this, char[] data)
|
||||
{
|
||||
this.memory = data.ptr;
|
||||
this.total = data.len;
|
||||
this.used = 0;
|
||||
}
|
||||
/**
|
||||
* @require this != null
|
||||
**/
|
||||
fn void MemoryArena.reset(MemoryArena* this)
|
||||
{
|
||||
this.used = 0;
|
||||
}
|
||||
355
lib/std/core/mem_allocator.c3
Normal file
355
lib/std/core/mem_allocator.c3
Normal file
@@ -0,0 +1,355 @@
|
||||
module std::core::mem;
|
||||
|
||||
define AllocatorFunction = fn void*!(void* alloc_data, usize new_size, usize alignment, void* old_pointer, AllocationKind kind);
|
||||
|
||||
const MAX_MEMORY_ALIGNMENT = 0x1000_0000;
|
||||
const DEFAULT_MEM_ALIGNMENT = $alignof(void*) * 2;
|
||||
const DEFAULT_SIZE_PREFIX = usize.sizeof;
|
||||
const DEFAULT_SIZE_PREFIX_ALIGNMENT = $alignof(usize);
|
||||
|
||||
Allocator main_allocator = { null, SYSTEM_ALLOCATOR };
|
||||
|
||||
const AllocatorFunction NULL_ALLOCATOR = &null_allocator_fn;
|
||||
const AllocatorFunction SYSTEM_ALLOCATOR = &libc_allocator_fn;
|
||||
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
*/
|
||||
fn void*! Allocator.alloc(Allocator *allocator, usize size, usize alignment = 0) @inline
|
||||
{
|
||||
return allocator.function(allocator.data, size, alignment, null, ALLOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
*/
|
||||
fn void*! Allocator.realloc(Allocator *allocator, void* old_pointer, usize size, usize alignment = 0) @inline
|
||||
{
|
||||
return allocator.function(allocator.data, size, alignment, old_pointer, REALLOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
*/
|
||||
fn void*! Allocator.calloc(Allocator *allocator, usize size, usize alignment = 0) @inline
|
||||
{
|
||||
return allocator.function(allocator.data, size, alignment, null, CALLOC);
|
||||
}
|
||||
|
||||
fn void! Allocator.free(Allocator *allocator, void* old_pointer) @inline
|
||||
{
|
||||
allocator.function(allocator.data, 0, 0, old_pointer, FREE)?;
|
||||
}
|
||||
|
||||
fn void Allocator.reset(Allocator *allocator)
|
||||
{
|
||||
allocator.function(allocator.data, 0, 0, null, RESET)!!;
|
||||
}
|
||||
|
||||
|
||||
fn Allocator arena_allocator(MemoryArena *arena)
|
||||
{
|
||||
return { arena, &arena_allocator_function };
|
||||
}
|
||||
|
||||
fn Allocator dynamic_arena_allocator(DynamicArenaAllocator* allocator)
|
||||
{
|
||||
return { allocator, &dynamic_arena_allocator_function };
|
||||
}
|
||||
|
||||
|
||||
private fn usize alignment_for_allocation(usize alignment) @inline
|
||||
{
|
||||
if (alignment < DEFAULT_MEM_ALIGNMENT)
|
||||
{
|
||||
alignment = DEFAULT_SIZE_PREFIX_ALIGNMENT;
|
||||
}
|
||||
return alignment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
* @require data `unexpectedly missing the allocator`
|
||||
*/
|
||||
fn void*! arena_allocator_function(void* data, usize size, usize alignment, void* old_pointer, AllocationKind kind)
|
||||
{
|
||||
MemoryArena* arena = data;
|
||||
switch (kind)
|
||||
{
|
||||
case CALLOC:
|
||||
case ALLOC:
|
||||
assert(!old_pointer, "Unexpected old pointer for alloc.");
|
||||
if (!size) return null;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
void* mem = arena.alloc(size, alignment, DEFAULT_SIZE_PREFIX)?;
|
||||
*(usize*)(mem - DEFAULT_SIZE_PREFIX) = size;
|
||||
if (kind == AllocationKind.CALLOC) mem::set(mem, 0, size);
|
||||
return mem;
|
||||
case REALLOC:
|
||||
if (!size) nextcase FREE;
|
||||
if (!old_pointer) nextcase ALLOC;
|
||||
assert((uptr)old_pointer >= (uptr)arena.memory, "Pointer originates from a different allocator.");
|
||||
if (size > arena.total) return AllocationFailure.OUT_OF_MEMORY!;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
usize* old_size_ptr = (usize*)(old_pointer - DEFAULT_SIZE_PREFIX);
|
||||
usize old_size = *old_size_ptr;
|
||||
// Do last allocation and alignment match?
|
||||
if (arena.memory + arena.used == old_pointer + old_size && ptr_is_aligned(old_pointer, alignment))
|
||||
{
|
||||
if (old_size >= size)
|
||||
{
|
||||
*old_size_ptr = size;
|
||||
arena.used -= old_size - size;
|
||||
return old_pointer;
|
||||
}
|
||||
usize new_used = arena.used + size - old_size;
|
||||
if (new_used > arena.total) return AllocationFailure.OUT_OF_MEMORY!;
|
||||
arena.used = new_used;
|
||||
*old_size_ptr = size;
|
||||
return old_pointer;
|
||||
}
|
||||
// Otherwise just allocate new memory.
|
||||
void* mem = arena.alloc(size, alignment, DEFAULT_SIZE_PREFIX)?;
|
||||
*(usize*)(mem - DEFAULT_SIZE_PREFIX) = size;
|
||||
copy(mem, old_pointer, old_size);
|
||||
return mem;
|
||||
case FREE:
|
||||
if (!old_pointer) return null;
|
||||
assert((uptr)old_pointer >= (uptr)arena.memory, "Pointer originates from a different allocator.");
|
||||
usize old_size = *(usize*)(old_pointer - DEFAULT_SIZE_PREFIX);
|
||||
if (old_pointer + old_size == arena.memory + arena.used)
|
||||
{
|
||||
arena.used -= old_size;
|
||||
}
|
||||
return null;
|
||||
case RESET:
|
||||
arena.used = 0;
|
||||
return null;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
struct DynamicArenaAllocator
|
||||
{
|
||||
Allocator backing_allocator;
|
||||
DynamicArenaPage* page;
|
||||
DynamicArenaPage* unused_page;
|
||||
usize page_size;
|
||||
}
|
||||
|
||||
private struct DynamicArenaPage
|
||||
{
|
||||
void* memory;
|
||||
void* prev_arena;
|
||||
usize total;
|
||||
usize used;
|
||||
void* last_ptr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
* @require data `unexpectedly missing the allocator`
|
||||
*/
|
||||
fn void*! dynamic_arena_allocator_function(void* data, usize size, usize alignment, void* old_pointer, AllocationKind kind)
|
||||
{
|
||||
DynamicArenaAllocator* allocator = data;
|
||||
switch (kind)
|
||||
{
|
||||
case CALLOC:
|
||||
assert(!old_pointer, "Unexpected no old pointer for calloc.");
|
||||
if (!size) return null;
|
||||
void* mem = allocator.alloc(size, alignment)?;
|
||||
set(mem, 0, size);
|
||||
return mem;
|
||||
case ALLOC:
|
||||
assert(!old_pointer, "Unexpected no old pointer for alloc.");
|
||||
if (!size) return null;
|
||||
return allocator.alloc(size, alignment);
|
||||
case REALLOC:
|
||||
if (!size)
|
||||
{
|
||||
if (!old_pointer) return null;
|
||||
allocator.free(old_pointer);
|
||||
return null;
|
||||
}
|
||||
if (!old_pointer) return allocator.alloc(size, alignment);
|
||||
void* mem = allocator.realloc(old_pointer, size, alignment)?;
|
||||
return mem;
|
||||
case FREE:
|
||||
if (!old_pointer) return null;
|
||||
allocator.free(old_pointer);
|
||||
return null;
|
||||
case RESET:
|
||||
allocator.reset();
|
||||
return null;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @require page_size >= 128
|
||||
* @require this != null
|
||||
**/
|
||||
fn void DynamicArenaAllocator.init(DynamicArenaAllocator* this, usize page_size, Allocator* backing_allocator = null)
|
||||
{
|
||||
this.page = null;
|
||||
this.unused_page = null;
|
||||
this.page_size = page_size;
|
||||
this.backing_allocator = backing_allocator ? *backing_allocator : main_allocator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require this != null
|
||||
**/
|
||||
fn void DynamicArenaAllocator.destroy(DynamicArenaAllocator* this)
|
||||
{
|
||||
DynamicArenaPage* page = this.page;
|
||||
while (page)
|
||||
{
|
||||
DynamicArenaPage* next_page = page.prev_arena;
|
||||
this.backing_allocator.free(page);
|
||||
page = next_page;
|
||||
}
|
||||
page = this.unused_page;
|
||||
while (page)
|
||||
{
|
||||
DynamicArenaPage* next_page = page.prev_arena;
|
||||
this.backing_allocator.free(page);
|
||||
page = next_page;
|
||||
}
|
||||
this.page = null;
|
||||
this.unused_page = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require ptr && this
|
||||
* @require this.page `tried to free pointer on invalid allocator`
|
||||
*/
|
||||
private fn void DynamicArenaAllocator.free(DynamicArenaAllocator* this, void* ptr)
|
||||
{
|
||||
DynamicArenaPage* current_page = this.page;
|
||||
if (ptr == current_page.last_ptr)
|
||||
{
|
||||
current_page.used = (usize)((ptr - DEFAULT_SIZE_PREFIX) - current_page.memory);
|
||||
}
|
||||
current_page.last_ptr = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require old_pointer && size > 0
|
||||
* @require this.page `tried to realloc pointer on invalid allocator`
|
||||
*/
|
||||
private fn void*! DynamicArenaAllocator.realloc(DynamicArenaAllocator* this, void* old_pointer, usize size, usize alignment)
|
||||
{
|
||||
DynamicArenaPage* current_page = this.page;
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
usize* old_size_ptr = old_pointer - DEFAULT_SIZE_PREFIX;
|
||||
usize old_size = *old_size_ptr;
|
||||
// We have the old pointer and it's correctly aligned.
|
||||
if (old_size >= size && ptr_is_aligned(old_pointer, alignment))
|
||||
{
|
||||
*old_size_ptr = size;
|
||||
if (current_page.last_ptr == old_pointer)
|
||||
{
|
||||
current_page.used = (usize)((old_pointer - DEFAULT_SIZE_PREFIX) - current_page.memory);
|
||||
}
|
||||
return old_pointer;
|
||||
}
|
||||
if REUSE: (current_page.last_ptr == old_pointer && ptr_is_aligned(old_pointer, alignment))
|
||||
{
|
||||
assert(size > old_size);
|
||||
usize add_size = size - old_size;
|
||||
if (add_size + current_page.used > current_page.total) break REUSE;
|
||||
*old_size_ptr = size;
|
||||
current_page.used += add_size;
|
||||
return old_pointer;
|
||||
}
|
||||
void* new_mem = this.alloc(size, alignment)?;
|
||||
copy(new_mem, old_pointer, old_size);
|
||||
return new_mem;
|
||||
}
|
||||
|
||||
private fn void DynamicArenaAllocator.reset(DynamicArenaAllocator* this)
|
||||
{
|
||||
DynamicArenaPage* page = this.page;
|
||||
DynamicArenaPage** unused_page_ptr = &this.unused_page;
|
||||
while (page)
|
||||
{
|
||||
DynamicArenaPage* next_page = page.prev_arena;
|
||||
page.used = 0;
|
||||
DynamicArenaPage* prev_unused = *unused_page_ptr;
|
||||
*unused_page_ptr = page;
|
||||
page.prev_arena = prev_unused;
|
||||
page = next_page;
|
||||
}
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require math::is_power_of_2(alignment)
|
||||
* @require size > 0
|
||||
*/
|
||||
private fn void*! DynamicArenaAllocator.alloc_new(DynamicArenaAllocator* this, usize size, usize alignment)
|
||||
{
|
||||
usize page_size = max(this.page_size, size + DEFAULT_SIZE_PREFIX + alignment);
|
||||
void* mem = this.backing_allocator.alloc(page_size)?;
|
||||
DynamicArenaPage*! page = this.backing_allocator.alloc(DynamicArenaPage.sizeof);
|
||||
if (catch err = page)
|
||||
{
|
||||
this.backing_allocator.free(mem);
|
||||
return err!;
|
||||
}
|
||||
page.memory = mem;
|
||||
usize offset = aligned_offset((usize)mem + DEFAULT_SIZE_PREFIX, alignment) - (usize)mem;
|
||||
usize* size_ptr = mem + offset - DEFAULT_SIZE_PREFIX;
|
||||
*size_ptr = size;
|
||||
page.prev_arena = this.page;
|
||||
page.total = page_size;
|
||||
page.used = size + offset;
|
||||
this.page = page;
|
||||
|
||||
return page.last_ptr = page.memory + offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @require !alignment || math::is_power_of_2(alignment)
|
||||
* @require size > 0
|
||||
* @require this
|
||||
*/
|
||||
private fn void*! DynamicArenaAllocator.alloc(DynamicArenaAllocator* this, usize size, usize alignment)
|
||||
{
|
||||
alignment = alignment_for_allocation(alignment);
|
||||
DynamicArenaPage* page = this.page;
|
||||
if (!page && this.unused_page)
|
||||
{
|
||||
this.page = page = this.unused_page;
|
||||
this.unused_page = page.prev_arena;
|
||||
page.prev_arena = null;
|
||||
}
|
||||
if (!page) return this.alloc_new(size, alignment);
|
||||
usize start = aligned_offset((uptr)page.memory + page.used + DEFAULT_SIZE_PREFIX, alignment) - (usize)page.memory;
|
||||
usize new_used = start + size;
|
||||
if ALLOCATE_NEW: (new_used > page.total)
|
||||
{
|
||||
if ((page = this.unused_page))
|
||||
{
|
||||
start = aligned_offset((uptr)page.memory + DEFAULT_SIZE_PREFIX, alignment) - (usize)page.memory;
|
||||
new_used = start + size;
|
||||
if (page.total >= new_used)
|
||||
{
|
||||
this.unused_page = page.prev_arena;
|
||||
page.prev_arena = this.page;
|
||||
this.page = page;
|
||||
break ALLOCATE_NEW;
|
||||
}
|
||||
}
|
||||
return this.alloc_new(size, alignment);
|
||||
}
|
||||
page.used = new_used;
|
||||
void* mem = page.memory + start;
|
||||
usize* size_offset = mem - DEFAULT_SIZE_PREFIX;
|
||||
*size_offset = size;
|
||||
return mem;
|
||||
}
|
||||
|
||||
65
lib/std/core/mem_allocator_fn.c3
Normal file
65
lib/std/core/mem_allocator_fn.c3
Normal file
@@ -0,0 +1,65 @@
|
||||
module std::core::mem;
|
||||
import libc;
|
||||
|
||||
private fn void*! null_allocator_fn(void *data, usize bytes, usize alignment, void* old_pointer, AllocationKind kind)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case ALLOC:
|
||||
case CALLOC:
|
||||
case REALLOC:
|
||||
return AllocationFailure.OUT_OF_MEMORY!;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
fn void*! libc_allocator_fn(void *unused, usize bytes, usize alignment, void* old_pointer, AllocationKind kind) @inline
|
||||
{
|
||||
if (!alignment) alignment = DEFAULT_MEM_ALIGNMENT;
|
||||
assert(math::is_power_of_2(alignment), "Alignment was not a power of 2");
|
||||
|
||||
void* data;
|
||||
switch (kind)
|
||||
{
|
||||
case ALLOC:
|
||||
if (alignment > DEFAULT_MEM_ALIGNMENT)
|
||||
{
|
||||
data = (void*)aligned_offset((iptr)libc::malloc(bytes + alignment), alignment);
|
||||
}
|
||||
else
|
||||
{
|
||||
data = libc::malloc(bytes);
|
||||
}
|
||||
if (!data) return AllocationFailure.OUT_OF_MEMORY!;
|
||||
return data;
|
||||
case CALLOC:
|
||||
if (alignment > DEFAULT_MEM_ALIGNMENT)
|
||||
{
|
||||
data = (void*)aligned_offset((iptr)libc::calloc(bytes + alignment, 1), alignment);
|
||||
}
|
||||
else
|
||||
{
|
||||
data = libc::malloc(bytes);
|
||||
}
|
||||
if (!data) return AllocationFailure.OUT_OF_MEMORY!;
|
||||
return data;
|
||||
case REALLOC:
|
||||
if (alignment > DEFAULT_MEM_ALIGNMENT)
|
||||
{
|
||||
data = (void*)aligned_offset((iptr)libc::realloc(old_pointer, bytes + alignment), alignment);
|
||||
}
|
||||
else
|
||||
{
|
||||
data = libc::realloc(old_pointer, bytes);
|
||||
}
|
||||
if (!data) return AllocationFailure.OUT_OF_MEMORY!;
|
||||
return data;
|
||||
case RESET:
|
||||
return AllocationFailure.UNSUPPORTED_OPERATION!;
|
||||
case FREE:
|
||||
libc::free(old_pointer);
|
||||
return null;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
106
lib/std/core/mem_temp_allocator.c3
Normal file
106
lib/std/core/mem_temp_allocator.c3
Normal file
@@ -0,0 +1,106 @@
|
||||
module std::core::mem;
|
||||
|
||||
const TEMP_BLOCK_SIZE = 1024;
|
||||
const TEMP_PAGES = 64;
|
||||
|
||||
private char[TEMP_BLOCK_SIZE * TEMP_PAGES] allocator_static_storage;
|
||||
private void*[TEMP_PAGES] allocator_static_page_storage;
|
||||
|
||||
SlotAllocator temp_allocator = {
|
||||
.pages = &allocator_static_storage,
|
||||
.page_size = TEMP_BLOCK_SIZE,
|
||||
.page_count = TEMP_PAGES,
|
||||
.bitmask = TEMP_PAGES - 1,
|
||||
.current_page = 0,
|
||||
};
|
||||
|
||||
struct SlotAllocator
|
||||
{
|
||||
void* pages;
|
||||
usize page_size;
|
||||
usize page_count;
|
||||
usize bitmask;
|
||||
usize current_page;
|
||||
}
|
||||
|
||||
fn void*! SlotAllocator.alloc(SlotAllocator *allocator, usize size)
|
||||
{
|
||||
void* active_page = (char*)(allocator.pages) + allocator.current_page * allocator.page_size;
|
||||
void** page_pointer = (void**)(active_page);
|
||||
if (*page_pointer)
|
||||
{
|
||||
// TODO fix
|
||||
main_allocator.free(*page_pointer)?;
|
||||
*page_pointer = null;
|
||||
}
|
||||
if (size > allocator.page_size - $sizeof(page_pointer))
|
||||
{
|
||||
void* mem = main_allocator.alloc(size)?;
|
||||
*page_pointer = mem;
|
||||
allocator.current_page = (allocator.current_page + 1) & (allocator.bitmask);
|
||||
return mem;
|
||||
}
|
||||
allocator.current_page = (allocator.current_page + 1) & (allocator.bitmask);
|
||||
return &page_pointer[1];
|
||||
}
|
||||
|
||||
|
||||
struct RingAllocator
|
||||
{
|
||||
char *data;
|
||||
usize size;
|
||||
usize offset;
|
||||
}
|
||||
|
||||
|
||||
fn void* RingAllocator.alloc(RingAllocator *allocator, usize size)
|
||||
{
|
||||
if (size > allocator.size) return null;
|
||||
// Wraparound? If so, start at the beginning.
|
||||
if (allocator.offset + size > allocator.size)
|
||||
{
|
||||
allocator.offset = size;
|
||||
return allocator.data;
|
||||
}
|
||||
void* data = allocator.offset + allocator.data;
|
||||
allocator.offset = (allocator.offset + size) & allocator.size;
|
||||
return data;
|
||||
}
|
||||
|
||||
fn void* RingAllocator.realloc(RingAllocator *allocator, void* ptr, usize size)
|
||||
{
|
||||
if (size > allocator.size) return null;
|
||||
assert(allocator.data >= ptr && ptr < allocator.data + size, "Realloc on other allocator.");
|
||||
// 1. The pointer is before the allocator
|
||||
if (allocator.data + allocator.offset > ptr)
|
||||
{
|
||||
if (allocator.data + allocator.size < ptr + size)
|
||||
{
|
||||
// 1a. There is not enough space, we need to copy to the start.
|
||||
usize pointer_offset = ptr - allocator.data;
|
||||
usize copy_len = pointer_offset + size > allocator.offset ? allocator.offset - pointer_offset : size;
|
||||
//memcpy(allocator.data, ptr, copy_len);
|
||||
allocator.offset = size;
|
||||
return allocator.data;
|
||||
}
|
||||
// 1b. There is enough space, so we just change the offset:
|
||||
allocator.offset = ptr - allocator.data + size;
|
||||
return ptr;
|
||||
}
|
||||
// 2. The pointer is after the allocator
|
||||
// 2a. Is there sufficient space?
|
||||
if (ptr + size <= allocator.data + allocator.size)
|
||||
{
|
||||
// Good, if so we simply change the offset and return the pointer.
|
||||
allocator.offset = ptr - allocator.data + size;
|
||||
return ptr;
|
||||
}
|
||||
// 2b. Not sufficient space, we copy to the beginning.
|
||||
usize pointer_offset = ptr - allocator.data;
|
||||
usize copy_len = allocator.size - (ptr - allocator.data);
|
||||
if (copy_len > size) copy_len = size;
|
||||
//memcpy(allocator.data, ptr, copy_len);
|
||||
allocator.offset = size;
|
||||
return allocator.data;
|
||||
}
|
||||
|
||||
17
lib/std/core/os/linux.c3
Normal file
17
lib/std/core/os/linux.c3
Normal file
@@ -0,0 +1,17 @@
|
||||
module std::core::os::linux;
|
||||
|
||||
$if (env::OS_TYPE == OsType.LINUX):
|
||||
|
||||
extern fn int* __errno_location();
|
||||
|
||||
fn int errno() @inline
|
||||
{
|
||||
return *__errno_location();
|
||||
}
|
||||
|
||||
fn void errno_set(int err)
|
||||
{
|
||||
*(__errno_location()) = err;
|
||||
}
|
||||
|
||||
$endif;
|
||||
14
lib/std/core/os/macos.c3
Normal file
14
lib/std/core/os/macos.c3
Normal file
@@ -0,0 +1,14 @@
|
||||
module std::core::os::macos;
|
||||
$if (env::OS_TYPE == OsType.MACOSX):
|
||||
|
||||
extern fn int* __error();
|
||||
|
||||
fn int errno() @inline
|
||||
{
|
||||
return *__error();
|
||||
}
|
||||
fn void errno_set(int err)
|
||||
{
|
||||
*(__error()) = err;
|
||||
}
|
||||
$endif;
|
||||
10
lib/std/core/os/windows.c3
Normal file
10
lib/std/core/os/windows.c3
Normal file
@@ -0,0 +1,10 @@
|
||||
module std::core::os::windows;
|
||||
|
||||
$if (env::OS_TYPE == OsType.WIN32):
|
||||
|
||||
extern fn int getLastError() @stdcall @extname("GetLastError");
|
||||
fn int errno() @inline
|
||||
{
|
||||
return getLastError();
|
||||
}
|
||||
$endif;
|
||||
166
lib/std/core/str.c3
Normal file
166
lib/std/core/str.c3
Normal file
@@ -0,0 +1,166 @@
|
||||
module std::core::str;
|
||||
|
||||
|
||||
define ZString = distinct char*;
|
||||
define Char32 = uint;
|
||||
|
||||
fn String join(char[][] s, char[] joiner)
|
||||
{
|
||||
if (!s.len) return (String)null;
|
||||
usize total_size = joiner.len * s.len;
|
||||
foreach (char[]* &str : s)
|
||||
{
|
||||
total_size += str.len;
|
||||
}
|
||||
String res = string::new_with_capacity(total_size);
|
||||
res.append(s[0]);
|
||||
foreach (char[]* &str : s[1..])
|
||||
{
|
||||
res.append(joiner);
|
||||
res.append(*str);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
fn ZString copy_zstring(char[] s)
|
||||
{
|
||||
usize len = s.len;
|
||||
char* str = mem::alloc(len + 1);
|
||||
mem::copy(str, s.ptr, len);
|
||||
str[len] = 0;
|
||||
return (ZString)str;
|
||||
}
|
||||
|
||||
fn ZString tcopy_zstring(char[] s)
|
||||
{
|
||||
usize len = s.len;
|
||||
char* str = mem::talloc(len + 1)!!;
|
||||
mem::copy(str, s.ptr, len);
|
||||
str[len] = 0;
|
||||
return (ZString)str;
|
||||
}
|
||||
|
||||
fault UnicodeResult
|
||||
{
|
||||
INVALID_UTF8
|
||||
}
|
||||
|
||||
fn Char32! utf8CharTo32(char* ptr, int* size)
|
||||
{
|
||||
int max_size = *size;
|
||||
if (max_size < 1) return UnicodeResult.INVALID_UTF8!;
|
||||
char c = (ptr++)[0];
|
||||
|
||||
if ((c & 0x80) == 0)
|
||||
{
|
||||
*size = 1;
|
||||
return c;
|
||||
}
|
||||
if ((c & 0xE0) == 0xC0)
|
||||
{
|
||||
if (max_size < 2) return UnicodeResult.INVALID_UTF8!;
|
||||
*size = 2;
|
||||
Char32 uc = (c & 0x1F) << 6;
|
||||
c = *ptr;
|
||||
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8!;
|
||||
return uc + c & 0x3F;
|
||||
}
|
||||
if ((c & 0xF0) == 0xE0)
|
||||
{
|
||||
if (max_size < 3) return UnicodeResult.INVALID_UTF8!;
|
||||
*size = 3;
|
||||
Char32 uc = (c & 0x0F) << 12;
|
||||
c = ptr++[0];
|
||||
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8!;
|
||||
uc += (c & 0x3F) << 6;
|
||||
c = ptr++[0];
|
||||
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8!;
|
||||
return uc + c & 0x3F;
|
||||
}
|
||||
if (max_size < 4) return UnicodeResult.INVALID_UTF8!;
|
||||
*size = 4;
|
||||
Char32 uc = (c & 0x07) << 18;
|
||||
c = ptr++[0];
|
||||
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8!;
|
||||
uc += (c & 0x3F) << 12;
|
||||
c = ptr++[0];
|
||||
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8!;
|
||||
uc += (c & 0x3F) << 6;
|
||||
c = ptr++[0];
|
||||
if (c & 0xC0 != 0x80) return UnicodeResult.INVALID_UTF8!;
|
||||
return uc + c & 0x3F;
|
||||
}
|
||||
|
||||
fn usize utf8_codepoints(char[] utf8)
|
||||
{
|
||||
usize len = 0;
|
||||
foreach (char c : utf8)
|
||||
{
|
||||
if (c & 0xC0 != 0x80) len++;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
fn Char32[]! utf8to32(char[] utf8, Allocator allocator = { null, null })
|
||||
{
|
||||
if (!allocator.function) allocator = mem::current_allocator();
|
||||
usize len = utf8.len;
|
||||
Char32* data = allocator.alloc((len + 1) * Char32.sizeof)?;
|
||||
usize len32 = 0;
|
||||
for (usize i = 0; i < len;)
|
||||
{
|
||||
int width = (int)min(len - i, 4);
|
||||
Char32 uc = utf8CharTo32(&utf8[i], &width) @inline?;
|
||||
i += width;
|
||||
data[len32++] = uc;
|
||||
}
|
||||
return data[0 .. len32 - 1];
|
||||
}
|
||||
|
||||
fn char[] copy(char[] s)
|
||||
{
|
||||
usize len = s.len;
|
||||
ZString str_copy = copy_zstring(s) @inline;
|
||||
return str_copy[..len];
|
||||
}
|
||||
|
||||
fn char[] tcopy(char[] s)
|
||||
{
|
||||
usize len = s.len;
|
||||
ZString str_copy = tcopy_zstring(s) @inline;
|
||||
return str_copy[..len];
|
||||
}
|
||||
|
||||
fn char[] tconcat(char[] s1, char[] s2)
|
||||
{
|
||||
usize full_len = s1.len + s2.len;
|
||||
char* str = mem::talloc(full_len + 1)!!;
|
||||
usize s1_len = s1.len;
|
||||
mem::copy(str, s1.ptr, s1_len);
|
||||
mem::copy(str + s1_len, s2.ptr, s2.len);
|
||||
str[full_len] = 0;
|
||||
return str[..full_len];
|
||||
}
|
||||
|
||||
fn char[] concat(char[] s1, char[] s2)
|
||||
{
|
||||
usize full_len = s1.len + s2.len;
|
||||
char* str = mem::alloc(full_len + 1);
|
||||
usize s1_len = s1.len;
|
||||
mem::copy(str, s1.ptr, s1_len);
|
||||
mem::copy(str + s1_len, s2.ptr, s2.len);
|
||||
str[full_len] = 0;
|
||||
return str[..full_len];
|
||||
}
|
||||
|
||||
fn usize ZString.len(ZString *str)
|
||||
{
|
||||
usize len = 0;
|
||||
char* ptr = (char*)*str;
|
||||
while (char c = ptr++[0])
|
||||
{
|
||||
if (c & 0xC0 != 0x80) len++;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
255
lib/std/core/string.c3
Normal file
255
lib/std/core/string.c3
Normal file
@@ -0,0 +1,255 @@
|
||||
module std::core::string;
|
||||
import libc;
|
||||
|
||||
define String = distinct void*;
|
||||
|
||||
private struct StringData
|
||||
{
|
||||
Allocator allocator;
|
||||
usize len;
|
||||
usize capacity;
|
||||
char[*] chars;
|
||||
}
|
||||
|
||||
const usize MIN_CAPACITY = 16;
|
||||
|
||||
fn String new_with_capacity(usize capacity, Allocator allocator = { null, null })
|
||||
{
|
||||
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
|
||||
if (!allocator.function)
|
||||
{
|
||||
allocator = mem::current_allocator();
|
||||
}
|
||||
assert(allocator.function, "Expected an allocator to be present.");
|
||||
StringData* data = allocator.alloc(StringData.sizeof + capacity)!!;
|
||||
data.allocator = allocator;
|
||||
data.len = 0;
|
||||
data.capacity = capacity;
|
||||
return (String)data;
|
||||
}
|
||||
|
||||
fn String new(char[] c)
|
||||
{
|
||||
usize len = c.len;
|
||||
String str = new_with_capacity(len);
|
||||
StringData* data = str.data();
|
||||
if (len)
|
||||
{
|
||||
data.len = len;
|
||||
mem::copy(&data.chars, c.ptr, len);
|
||||
}
|
||||
return (String)data;
|
||||
}
|
||||
|
||||
fn ZString String.zstr(String* str)
|
||||
{
|
||||
StringData* data = str.data();
|
||||
if (!str) return (ZString)"";
|
||||
if (data.capacity == data.len)
|
||||
{
|
||||
libc::printf("feofk\n");
|
||||
str.reserve(1);
|
||||
data.chars[data.len] = 0;
|
||||
}
|
||||
else if (data.chars[data.len] != 0)
|
||||
{
|
||||
data.chars[data.len] = 0;
|
||||
}
|
||||
return (ZString)&data.chars[0];
|
||||
}
|
||||
|
||||
fn usize String.len(String* this)
|
||||
{
|
||||
if (!*this) return 0;
|
||||
return this.data().len;
|
||||
}
|
||||
/**
|
||||
* @require new_size <= this.len()
|
||||
*/
|
||||
fn void String.chop(String* this, usize new_size)
|
||||
{
|
||||
if (!*this) return;
|
||||
this.data().len = new_size;
|
||||
}
|
||||
|
||||
fn char[] String.str(String* str)
|
||||
{
|
||||
StringData* data = (StringData*)*str;
|
||||
return data.chars[0..data.len - 1];
|
||||
}
|
||||
|
||||
fn void String.append_utf32(String* str, Char32[] chars)
|
||||
{
|
||||
str.reserve(chars.len);
|
||||
foreach (Char32 c : chars)
|
||||
{
|
||||
str.append_char32(c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @require c < 0x10ffff
|
||||
*/
|
||||
fn void String.append_char32(String* str, Char32 c)
|
||||
{
|
||||
if (c < 0x7f)
|
||||
{
|
||||
str.reserve(1);
|
||||
StringData* data = str.data();
|
||||
data.chars[data.len++] = (char)c;
|
||||
return;
|
||||
}
|
||||
if (c < 0x7ff)
|
||||
{
|
||||
str.reserve(2);
|
||||
StringData* data = str.data();
|
||||
data.chars[data.len++] = (char)(0xC0 | c >> 6);
|
||||
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
|
||||
return;
|
||||
}
|
||||
if (c < 0xffff)
|
||||
{
|
||||
str.reserve(3);
|
||||
StringData* data = str.data();
|
||||
data.chars[data.len++] = (char)(0xE0 | c >> 12);
|
||||
data.chars[data.len++] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
|
||||
return;
|
||||
}
|
||||
str.reserve(4);
|
||||
StringData* data = str.data();
|
||||
data.chars[data.len++] = (char)(0xF0 | c >> 18);
|
||||
data.chars[data.len++] = (char)(0x80 | (c >> 12 & 0x3F));
|
||||
data.chars[data.len++] = (char)(0x80 | (c >> 6 & 0x3F));
|
||||
data.chars[data.len++] = (char)(0x80 | (c & 0x3F));
|
||||
}
|
||||
|
||||
fn String String.copy(String* str, Allocator allocator = { null, null })
|
||||
{
|
||||
if (!*str)
|
||||
{
|
||||
if (allocator.function) return new_with_capacity(0, allocator);
|
||||
return (String)null;
|
||||
}
|
||||
if (!allocator.function) allocator = mem::current_allocator();
|
||||
StringData* data = str.data();
|
||||
String new_string = new_with_capacity(data.capacity, allocator);
|
||||
mem::copy((char*)new_string.data(), (char*)data, StringData.sizeof + data.len);
|
||||
return new_string;
|
||||
}
|
||||
|
||||
fn ZString String.copy_zstr(String* str, Allocator allocator = { null, null })
|
||||
{
|
||||
usize str_len = str.len();
|
||||
if (!str_len)
|
||||
{
|
||||
if (allocator.function) return (ZString)allocator.calloc(1, 1)!!;
|
||||
return (ZString)mem::calloc(1, 1);
|
||||
}
|
||||
if (!allocator.function) allocator = mem::current_allocator();
|
||||
char* zstr = allocator.alloc(str_len + 1)!!;
|
||||
StringData* data = str.data();
|
||||
mem::copy(zstr, &data.chars, str_len);
|
||||
zstr[str_len] = 0;
|
||||
return (ZString)zstr;
|
||||
}
|
||||
|
||||
fn bool String.equals(String* str, String other_string)
|
||||
{
|
||||
StringData *str1 = str.data();
|
||||
StringData *str2 = other_string.data();
|
||||
if (str1 == str2) return true;
|
||||
if (!str1) return str2.len == 0;
|
||||
if (!str2) return str1.len == 0;
|
||||
usize str1_len = str1.len;
|
||||
if (str1_len != str2.len) return false;
|
||||
for (int i = 0; i < str1_len; i++)
|
||||
{
|
||||
if (str1.chars[i] != str2.chars[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn void String.destroy(String* str)
|
||||
{
|
||||
if (!*str) return;
|
||||
StringData* data = str.data();
|
||||
data.allocator.free(data);
|
||||
*str = (String)null;
|
||||
}
|
||||
|
||||
fn bool String.less_than(String* str, String other_string)
|
||||
{
|
||||
StringData* str1 = str.data();
|
||||
StringData* str2 = other_string.data();
|
||||
if (str1 == str2) return false;
|
||||
if (!str1) return str2.len != 0;
|
||||
if (!str2) return str1.len == 0;
|
||||
usize str1_len = str1.len;
|
||||
usize str2_len = str2.len;
|
||||
if (str1_len != str2_len) return str1_len < str2_len;
|
||||
for (int i = 0; i < str1_len; i++)
|
||||
{
|
||||
if (str1.chars[i] >= str2.chars[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn void String.append(String* this, char[] str)
|
||||
{
|
||||
usize other_len = str.len;
|
||||
if (!other_len) return;
|
||||
if (!*this)
|
||||
{
|
||||
*this = new(str);
|
||||
return;
|
||||
}
|
||||
this.reserve(other_len);
|
||||
StringData* data = (StringData*)*this;
|
||||
mem::copy(&data.chars[data.len], str.ptr, other_len);
|
||||
data.len += other_len;
|
||||
}
|
||||
|
||||
fn Char32[] String.copy_utf32(String* this, Allocator allocator = { null, null })
|
||||
{
|
||||
return str::utf8to32(this.str(), allocator) @inline!!;
|
||||
}
|
||||
|
||||
fn void String.append_string(String* this, String str)
|
||||
{
|
||||
StringData* other = (StringData*)str;
|
||||
if (!other) return;
|
||||
this.append(str.str());
|
||||
}
|
||||
|
||||
fn void String.append_char(String* str, char c)
|
||||
{
|
||||
if (!*str)
|
||||
{
|
||||
*str = new_with_capacity(MIN_CAPACITY);
|
||||
}
|
||||
str.reserve(1);
|
||||
StringData* data = (StringData*)*str;
|
||||
data.chars[data.len++] = c;
|
||||
}
|
||||
|
||||
private fn StringData* String.data(String* str) @inline
|
||||
{
|
||||
return (StringData*)*str;
|
||||
}
|
||||
|
||||
|
||||
private fn void String.reserve(String* str, usize addition)
|
||||
{
|
||||
StringData* data = str.data();
|
||||
if (!data)
|
||||
{
|
||||
*str = string::new_with_capacity(addition);
|
||||
return;
|
||||
}
|
||||
usize len = data.len + addition;
|
||||
if (data.capacity >= len) return;
|
||||
usize new_capacity = data.capacity * 2;
|
||||
if (new_capacity < MIN_CAPACITY) new_capacity = MIN_CAPACITY;
|
||||
*str = (String)data.allocator.realloc(data, StringData.sizeof + new_capacity)!!;
|
||||
}
|
||||
25
lib/std/core/string_iterator.c3
Normal file
25
lib/std/core/string_iterator.c3
Normal file
@@ -0,0 +1,25 @@
|
||||
module std::core::string::iterator;
|
||||
|
||||
|
||||
|
||||
struct StringIterator
|
||||
{
|
||||
char[] utf8;
|
||||
usize current;
|
||||
}
|
||||
|
||||
fn void StringIterator.reset(StringIterator* this)
|
||||
{
|
||||
this.current = 0;
|
||||
}
|
||||
|
||||
fn Char32! StringIterator.next(StringIterator* this)
|
||||
{
|
||||
usize len = this.utf8.len;
|
||||
usize current = this.current;
|
||||
if (current >= len) return IteratorResult.NO_MORE_ELEMENT!;
|
||||
int read = (int)(len - current < 4 ? len - current : 4);
|
||||
Char32 res = str::utf8CharTo32(&this.utf8[current], &read)?;
|
||||
this.current += read;
|
||||
return res;
|
||||
}
|
||||
Reference in New Issue
Block a user