various changes

This commit is contained in:
Judah Caruso 2025-09-07 15:25:57 -06:00
parent 49973e65ed
commit cd4ad810a0
17 changed files with 569 additions and 339 deletions

View file

@ -1,76 +0,0 @@
AllocatorMode :: enum {
using Allocator_Mode;
// Compatability
Allocate :: ALLOCATE;
Resize :: RESIZE;
Free :: FREE;
Startup :: STARTUP;
Shutdown :: SHUTDOWN;
ThreadStart :: THREAD_START;
ThreadStop :: THREAD_STOP;
CreateHeap :: CREATE_HEAP;
DestroyHeap :: DESTROY_HEAP;
IsThisYours :: IS_THIS_YOURS;
Capabilities :: CAPS;
}
/// PanicAllocator causes the program to panic when used.
PanicAllocator :: () -> Allocator {
return .{ proc = xx PanicAllocatorProc };
}
TrySetAllocator :: (thing: *$T, allocator := context.allocator) #modify {
info := T.(*Type_Info_Struct);
ok := false;
if info.type == .STRUCT
{ ok = true; }
if ok for info.members if it.name == "allocator" && it.type == Allocator.(*Type_Info) {
ok = true;
break;
}
return ok, "can only set allocator on struct with an allocator field or dynamic array";
} #expand {
if thing.allocator.proc == null {
thing.allocator = allocator;
}
}
TrySetAllocator :: (array: *[..]$T, allocator := context.allocator) #expand {
if array.allocator.proc == null {
array.allocator = allocator;
}
}
#scope_module
PanicAllocatorProc :: (mode: AllocatorMode, new_size: int, old_size: int, ptr: *void, allocator_data: *void) -> *void {
if mode == {
case .Allocate;
if new_size > 0 {
Panic("jc: allocation using the PanicAllocator");
}
case .Resize;
if new_size > 0 {
Panic("jc: reallocation using the PanicAllocator");
}
case .Free;
if ptr != null {
Panic("jc: free using the PanicAllocator");
}
case .ThreadStart; #through;
case .ThreadStop;
Panic("jc: start/stop thread using the PanicAllocator");
case .CreateHeap; #through;
case .DestroyHeap;
Panic("jc: create/destroy heap using the PanicAllocator");
}
return null;
} @jc.nodocs

259
+internal/arenas.jai Normal file
View file

@ -0,0 +1,259 @@
// @todo(judah): Conditionally added to the context because it causes some weirdness in tests
#if !RunTests {
#add_context arena := Arena.{ proc = PagingArenaProc };
#add_context temp_arena := Arena.{ proc = TempArenaProc };
}
Arena :: struct {
proc: ArenaProc;
data: *void;
}
ArenaProc :: #type (
mode: ArenaMode,
arena_data: *void,
new_size: int, old_size: int, align: int,
new_ptr: *void, old_ptr: *void,
caller: Source_Code_Location
) -> *void;
ArenaMode :: enum {
Setup;
Teardown;
Acquire;
Reset;
Save;
Restore;
}
ArenaPush :: ($T: Type, loc := #caller_location) -> *T {
return ArenaPushSize(size_of(T), loc = loc).(*T);
}
ArenaPushSize :: (size: int, align := DefaultAlign, loc := #caller_location) -> *void {
if size < 0
{ Panic("jc: size cannot be negative", loc = loc); }
TrySetupArena(*context.arena);
return context.arena.proc(.Acquire, context.arena.data, size, 0, align, null, null, loc);
}
ArenaSaveResult :: #type,distinct *void;
ArenaSave :: (loc := #caller_location) -> ArenaSaveResult {
TrySetupArena(*context.arena, loc);
return context.arena.proc(.Save, context.arena.data, 0, 0, 0, null, null, loc).(ArenaSaveResult);
}
ArenaRestore :: (savepoint: ArenaSaveResult, loc := #caller_location) {
context.arena.proc(.Restore, context.arena.data, 0, 0, 0, null, savepoint.(*void), loc);
}
ArenaAutoRestore :: () #expand {
wm := ArenaSave();
`defer ArenaRestore(wm);
}
ArenaReset :: (loc := #caller_location) {
TrySetupArena(*context.arena);
context.arena.proc(.Reset, context.arena.data, 0, 0, 0, null, null, loc);
}
ArenaRelease :: (loc := #caller_location) {
TrySetupArena(*context.arena, loc);
context.arena.proc(.Teardown, context.arena.data, 0, 0, 0, null, null, loc);
}
/// ArenaToAllocator wraps an arena, allowing it to be used with Jai's builtin Allocator system.
ArenaToAllocator :: (arena: *Arena) -> Allocator {
return .{ proc = JaiAllocatorProc, data = arena };
}
PanicArena :: () -> Arena {
return .{ proc = PanicArenaProc };
}
BumpData :: struct {
memory: []u8;
offset: int;
}
BumpArena :: (bump: *BumpData) -> Arena {
return .{ proc = BumpArenaProc, data = bump };
}
PagingArena :: () -> Arena {
return .{ proc = PagingArenaProc };
}
#scope_module
TrySetupArena :: (arena: *Arena, loc := #caller_location) #expand {
if arena.data != null
{ return; }
data := arena.proc(.Setup, null, 0, 0, 0, null, null, loc);
if data != null
{ arena.data = data; }
}
PanicArenaProc :: (
mode: ArenaMode,
arena_data: *void,
new_size: int, old_size: int, align: int,
new_ptr: *void, old_ptr: *void,
caller: Source_Code_Location
) -> *void {
if mode == {
case .Acquire;
if new_size > 0
{ Panic("jc: cannot acquire memory using the PanicArena", loc = caller); }
case .Save;
Panic("jc: cannot save using the PanicArean", loc = caller);
case .Restore;
Panic("jc: cannot restore using the PanicArean", loc = caller);
}
return null;
} @jc.nodocs
BumpArenaProc :: (
mode: ArenaMode,
arena_data: *void,
new_size: int, old_size: int, align: int,
new_ptr: *void, old_ptr: *void,
caller: Source_Code_Location
) -> *void #no_abc {
bump := arena_data.(*BumpData);
if mode == {
case .Setup;
if bump.memory.data == null
{ Panic("jc: BumpArena has no memory", loc = caller); }
return arena_data;
case .Teardown;
// Do nothing, we don't own this memory
case .Acquire;
end := bump.memory.data + bump.offset;
if new_size == 0
{ return end; }
ptr := MemAlignForward(end, align.(uint));
size := new_size + (ptr - end);
if bump.offset + size > bump.memory.count
{ Panic("jc: BumpArena out of memory", loc = caller); }
bump.offset += size;
return ptr;
case .Reset;
bump.offset = 0;
case .Save;
return bump.offset.(*void);
case .Restore;
wm := old_ptr.(int);
if wm < 0 || wm >= bump.memory.count
{ Panic("jc: BumpArena restored invalid savepoint", loc = caller); }
bump.offset = wm;
}
return null;
} @jc.nodocs
PagingArenaProc :: (
mode: ArenaMode,
arena_data: *void,
new_size: int, old_size: int, align: int,
new_ptr: *void, old_ptr: *void,
caller: Source_Code_Location
) -> *void {
Panic("not implemented for PagingArena", loc = caller);
return null;
} @jc.nodocs
// @note(judah): TempArenaProc just wraps BumpArenaProc, but provides its own backing memory.
// This is needed so we can use TempArenaProc without forcing the user to set everything up.
TempArenaProc :: (
mode: ArenaMode,
arena_data: *void,
new_size: int, old_size: int, align: int,
new_ptr: *void, old_ptr: *void,
caller: Source_Code_Location
) -> *void {
// @temp(judah)
libc :: #library,system "libc";
malloc :: (size: int) -> *void #c_call #foreign libc;
free :: (ptr: *void) #c_call #foreign libc;
if mode == {
// @note(judah): allows the temp arena to initialize itself without relying on another arena to provide its memory.
case .Setup;
mcount := AlignForward(size_of(BumpData) + 32768).(int);
memory := malloc(mcount).(*u8);
MemZero(memory, mcount);
bump := memory.(*BumpData);
bump.memory.data = memory + size_of(BumpData);
bump.memory.count = mcount - size_of(BumpData);
return bump;
case .Teardown;
free(arena_data);
}
return BumpArenaProc(mode, arena_data, new_size, old_size, align, new_ptr, old_ptr, caller);
} @jc.nodocs
#scope_file
JaiAllocatorProc :: (mode: Allocator_Mode, requested_size: s64, old_size: s64, old_memory: *void, allocator_data: *void) -> *void {
CallerFromStackTrace :: () -> Source_Code_Location #expand {
node := context.stack_trace;
if node.next != null {
node = node.next;
}
return node.info.location;
}
arena := allocator_data.(*Arena);
if mode == {
case .STARTUP;
TrySetupArena(arena);
return null;
case .ALLOCATE;
loc := CallerFromStackTrace();
return ArenaPushSize(requested_size, loc = loc,, arena = arena);
case .RESIZE;
loc := CallerFromStackTrace();
new := ArenaPushSize(requested_size, loc = loc,, arena = arena);
MemCopy(new, old_memory, old_size);
return new;
case .FREE;
// Arenas don't free single pointers so give this a value that's obviously incorrect
MemOverwrite(old_memory, old_size, 0xAA);
}
return null;
} @jc.nodocs

View file

@ -1,28 +1,25 @@
/// Append pushes value to the end of an array, resizing if necessary. /// ArrayAppend pushes values to the end of an array, resizing if necessary.
/// A pointer to the first value appended will be returned.
/// ///
/// Note: If no allocator has been set, Append will use the current context allocator. /// Note: If no allocator has been set, ArrayAppend will use the current context allocator.
/// Note: Calls to Append may invalidate pre-existing pointers. /// Note: Calls to Append may invalidate pre-existing pointers.
Append :: inline (arr: *[..]$T, value: T) -> *T { ArrayAppend :: inline (arr: *[..]$T, values: ..T) -> *T {
TrySetAllocator(arr); TrySetAllocator(arr);
ptr := basic.array_add(arr);
ptr.* = value; if values.count == 0 {
return ptr; return basic.array_add(arr);
}
count := arr.count;
basic.array_add(arr, ..values);
return *arr.data[count];
} }
/// Append pushes a default initialized value to the end of an array, /// ArrayGrow resizes the memory associated with an array to hold new_count values.
/// returning a pointer to the newly pushed value.
/// ///
/// Note: If no allocator has been set, Append will use the current context allocator. /// Note: If the array has enough allocated memory to accomodate new_count, ArrayGrow does nothing.
/// Note: Calls to Append may invalidate pre-existing pointers. /// Note: ArrayGrow does not guarantee pointer stability.
Append :: inline (arr: *[..]$T) -> *T { ArrayGrow :: inline (arr: *[..]$T, new_count: int) {
return Append(arr, default_of(T));
}
/// Resize grows the memory associated with an array to hold new_count values.
///
/// Note: If the array has enough allocated memory to accomodate new_count, Resize does nothing.
/// Note: Resize does not guarantee pointer stability.
Resize :: inline (arr: *[..]$T, new_count: int) {
if new_count <= arr.allocated if new_count <= arr.allocated
{ return; } { return; }
@ -30,32 +27,32 @@ Resize :: inline (arr: *[..]$T, new_count: int) {
basic.array_reserve(arr, new_count,, allocator = arr.allocator); basic.array_reserve(arr, new_count,, allocator = arr.allocator);
} }
/// Slice returns a subsection of an array. /// ArraySlice returns a subsection of an array.
Slice :: (view: []$T, start_idx: int, count := -1, loc := #caller_location) -> []T { ArraySlice :: (arr: []$T, start_idx: int, count := -1, loc := #caller_location) -> []T {
AssertCallsite(start_idx >= +0 && start_idx < view.count, "jc: incorrect slice bounds"); AssertCallsite(start_idx >= +0 && start_idx < arr.count, "jc: incorrect slice bounds");
AssertCallsite(count >= -1 && count < view.count, "jc: incorrect slice length"); AssertCallsite(count >= -1 && count < arr.count, "jc: incorrect slice length");
if count == -1 if count == -1
{ count = view.count - start_idx; } { count = arr.count - start_idx; }
return .{ data = view.data + start_idx, count = count }; return .{ data = arr.data + start_idx, count = count };
} }
/// Reset sets an array's length to 0, but leaves its memory intact. /// ArrayReset sets an array's length to 0, but leaves its memory intact.
/// Note: To reset the associated memory as well, see Clear. /// Note: To reset the associated memory as well, see ArrayClear.
Reset :: (view: *[]$T) { ArrayReset :: (arr: *[]$T) {
view.count = 0; arr.count = 0;
} }
/// Clear zeroes an array's memory and sets its length to 0. /// ArrayClear zeroes an array's memory and sets its length to 0.
/// Note: To leave the associated memory intact, see Reset. /// Note: To leave the associated memory intact, see ArrayReset.
Clear :: (view: *[]$T) { ArrayClear :: (arr: *[]$T) {
MemZero(view.data, view.count * size_of(T)); MemZero(arr.data, arr.count * size_of(T));
view.count = 0; arr.count = 0;
} }
/// Equal checks the equality of two arrays. /// ArrayEquals checks equality between two arrays.
Equal :: (lhs: []$T, rhs: []T) -> bool { ArrayEquals :: (lhs: []$T, rhs: []T) -> bool {
if lhs.count != rhs.count if lhs.count != rhs.count
{ return false; } { return false; }
return MemEqual(lhs.data, rhs.data, lhs.count * size_of(T)); return MemEqual(lhs.data, rhs.data, lhs.count * size_of(T));
@ -73,23 +70,21 @@ CheckBounds :: ($$index: $T, $$count: T, loc := #caller_location) #expand {
} }
} }
ArrayIndex :: struct(T: Type) {
FindFlags :: enum_flags {
Last; // Return the last matching element.
FromEnd; // Search in reverse.
}
FindResult :: struct(T: Type) {
value: T = ---; value: T = ---;
index: int; index: int;
} }
/// Find searches through an array, returning the first element ArrayFindFlags :: enum_flags {
/// that matches the given value. Last; // Return the last matching element.
Find :: (view: []$T, value: T, $flags: FindFlags = 0) -> (bool, FindResult(T)) { FromEnd; // Search in reverse.
}
/// ArrayFind searches through an array, returning the first element that matches the given value.
ArrayFind :: (view: []$T, value: T, $flags: ArrayFindFlags = 0) -> (bool, ArrayIndex(T)) {
found: bool; found: bool;
result: FindResult(T); result: ArrayIndex(T);
result.index = -1; result.index = -1;
REVERSE :: #run (flags & .FromEnd).(bool); REVERSE :: #run (flags & .FromEnd).(bool);
@ -103,21 +98,21 @@ Find :: (view: []$T, value: T, $flags: FindFlags = 0) -> (bool, FindResult(T)) {
return found, result; return found, result;
} }
/// Contains checks if the given value exists in an array. /// ArrayContains checks if the given value exists in an array.
Contains :: (view: []$T, value: T) -> bool { ArrayContains :: (view: []$T, value: T) -> bool {
return Find(view, value); return ArrayFind(view, value);
} }
TrimFlags :: enum_flags { ArrayTrimFlags :: enum_flags {
FromStart; // Trim the start of the array. FromStart; // ArrayTrim the start of the array.
FromEnd; // Trim the end of the array. FromEnd; // ArrayTrim the end of the array.
MatchInFull; // Only trim when the cutset matches exactly. MatchInFull; // Only trim when the cutset matches exactly.
} }
/// Trim returns a subsection of an array with all leading/trailing values /// ArrayTrim returns a subsection of an array with all leading/trailing values
/// from cutset removed. /// from cutset removed.
Trim :: (view: []$T, cutset: []T, $flags: TrimFlags = .FromStart) -> []T { ArrayTrim :: (view: []$T, cutset: []T, $flags: ArrayTrimFlags = .FromStart) -> []T {
result := view; result := view;
if cutset.count == 0 || cutset.count > view.count { if cutset.count == 0 || cutset.count > view.count {
return result; return result;
@ -125,13 +120,13 @@ Trim :: (view: []$T, cutset: []T, $flags: TrimFlags = .FromStart) -> []T {
#if flags & .FromStart { #if flags & .FromStart {
#if flags & .MatchInFull { #if flags & .MatchInFull {
if Equal(Slice(view, 0, cutset.count), cutset) { if ArrayEquals(ArraySlice(view, 0, cutset.count), cutset) {
result = Slice(view, cutset.count, -1); result = ArraySlice(view, cutset.count, -1);
} }
} }
else { else {
while result.count > 0 { while result.count > 0 {
if !Contains(cutset, result[0]) { if !ArrayContains(cutset, result[0]) {
break; break;
} }
@ -143,13 +138,13 @@ Trim :: (view: []$T, cutset: []T, $flags: TrimFlags = .FromStart) -> []T {
#if flags & .FromEnd { #if flags & .FromEnd {
#if flags & .MatchInFull { #if flags & .MatchInFull {
if Equal(Slice(view, view.count - cutset.count), cutset) { if ArrayEquals(ArraySlice(view, view.count - cutset.count), cutset) {
result.count -= cutset.count; result.count -= cutset.count;
} }
} }
else { else {
while result.count > 0 { while result.count > 0 {
if !Contains(cutset, result[result.count - 1]) { if !ArrayContains(cutset, result[result.count - 1]) {
break; break;
} }
@ -169,82 +164,82 @@ basic :: #import "Basic"; // @future
#if RunTests #run,stallable { #if RunTests #run,stallable {
Test("slice", t => { Test("slice", t => {
a1 := int.[ 1, 2, 3, 4, 5 ]; a1 := int.[ 1, 2, 3, 4, 5 ];
a2 := Slice(a1, 2); a2 := ArraySlice(a1, 2);
Expect(a2.count == 3); Expect(a2.count == 3);
Expect(Equal(a2, int.[ 3, 4, 5 ])); Expect(ArrayEquals(a2, int.[ 3, 4, 5 ]));
b1 := int.[ 1, 2, 3, 4, 5 ]; b1 := int.[ 1, 2, 3, 4, 5 ];
b2 := Slice(b1, 2, 0); b2 := ArraySlice(b1, 2, 0);
Expect(b2.count == 0); Expect(b2.count == 0);
Expect(b2.data == b1.data + 2); Expect(b2.data == b1.data + 2);
c1 := int.[ 1, 2, 3, 4, 5 ]; c1 := int.[ 1, 2, 3, 4, 5 ];
c2 := Slice(c1, 3, 1); c2 := ArraySlice(c1, 3, 1);
Expect(c2.count == 1); Expect(c2.count == 1);
Expect(Equal(c2, int.[ 4 ])); Expect(ArrayEquals(c2, int.[ 4 ]));
d1 := int.[ 1, 2, 3 ]; d1 := int.[ 1, 2, 3 ];
d2 := Slice(d1, 2); d2 := ArraySlice(d1, 2);
Expect(d2.count == 1); Expect(d2.count == 1);
Expect(Equal(d2, int.[ 3 ])); Expect(ArrayEquals(d2, int.[ 3 ]));
}); });
Test("find", t => { Test("find", t => {
a := int.[ 1, 2, 3, 4, 5 ]; a := int.[ 1, 2, 3, 4, 5 ];
ok, res := Find(a, 3); ok, res := ArrayFind(a, 3);
Expect(ok && res.index == 2); Expect(ok && res.index == 2);
Expect(res.value == 3); Expect(res.value == 3);
ok, res = Find(a, -1); ok, res = ArrayFind(a, -1);
Expect(!ok && res.index == -1); Expect(!ok && res.index == -1);
b := int.[ 1, 2, 2, 3, 4, 5, 2 ]; b := int.[ 1, 2, 2, 3, 4, 5, 2 ];
ok, res = Find(b, 2); ok, res = ArrayFind(b, 2);
Expect(ok && res.index == 1); Expect(ok && res.index == 1);
ok, res = Find(b, 2, .FromEnd); ok, res = ArrayFind(b, 2, .FromEnd);
Expect(ok && res.index == 6); Expect(ok && res.index == 6);
c := int.[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ]; c := int.[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ];
ok, res = Find(c, 0, .Last); ok, res = ArrayFind(c, 0, .Last);
Expect(ok && res.index == 8); Expect(ok && res.index == 8);
ok, res = Find(c, 0, .FromEnd | .Last); ok, res = ArrayFind(c, 0, .FromEnd | .Last);
Expect(ok && res.index == 0); Expect(ok && res.index == 0);
}); });
Test("contains", t => { Test("contains", t => {
a := int.[ 1, 2, 3, 4, 5 ]; a := int.[ 1, 2, 3, 4, 5 ];
Expect(Contains(a, 3)); Expect(ArrayContains(a, 3));
Expect(!Contains(a, -1)); Expect(!ArrayContains(a, -1));
}); });
Test("trim", t => { Test("trim", t => {
a1 := int.[ 0, 0, 0, 1, 2, 3 ]; a1 := int.[ 0, 0, 0, 1, 2, 3 ];
a2 := Trim(a1, .[ 0 ]); a2 := ArrayTrim(a1, .[ 0 ]);
Expect(Equal(a1, .[ 0, 0, 0, 1, 2, 3 ])); Expect(ArrayEquals(a1, .[ 0, 0, 0, 1, 2, 3 ]));
Expect(Equal(a2, .[ 1, 2, 3 ])); Expect(ArrayEquals(a2, .[ 1, 2, 3 ]));
b1 := int.[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ]; b1 := int.[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ];
b2 := Trim(b1, .[ 0 ], .FromEnd); b2 := ArrayTrim(b1, .[ 0 ], .FromEnd);
Expect(Equal(b1, .[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ])); Expect(ArrayEquals(b1, .[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ]));
Expect(Equal(b2, .[ 0, 0, 0, 1, 2, 3 ])); Expect(ArrayEquals(b2, .[ 0, 0, 0, 1, 2, 3 ]));
c1 := int.[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ]; c1 := int.[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ];
c2 := Trim(c1, .[ 0 ], .FromStart | .FromEnd); c2 := ArrayTrim(c1, .[ 0 ], .FromStart | .FromEnd);
Expect(Equal(c1, .[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ])); Expect(ArrayEquals(c1, .[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ]));
Expect(Equal(c2, .[ 1, 2, 3 ])); Expect(ArrayEquals(c2, .[ 1, 2, 3 ]));
d1 := int.[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ]; d1 := int.[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ];
d2 := Trim(d1, .[ 0, 0, 0 ], .FromStart | .MatchInFull); d2 := ArrayTrim(d1, .[ 0, 0, 0 ], .FromStart | .MatchInFull);
d3 := Trim(d1, .[ 0, 0, 0 ], .FromEnd | .MatchInFull); d3 := ArrayTrim(d1, .[ 0, 0, 0 ], .FromEnd | .MatchInFull);
d4 := Trim(d1, .[ 0, 0, 0 ], .FromStart | .FromEnd | .MatchInFull); d4 := ArrayTrim(d1, .[ 0, 0, 0 ], .FromStart | .FromEnd | .MatchInFull);
Expect(Equal(d1, .[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ])); Expect(ArrayEquals(d1, .[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ]));
Expect(Equal(d2, .[ 1, 2, 3, 0, 0, 0 ])); Expect(ArrayEquals(d2, .[ 1, 2, 3, 0, 0, 0 ]));
Expect(Equal(d3, .[ 0, 0, 0, 1, 2, 3 ])); Expect(ArrayEquals(d3, .[ 0, 0, 0, 1, 2, 3 ]));
Expect(Equal(d4, .[ 1, 2, 3 ])); Expect(ArrayEquals(d4, .[ 1, 2, 3 ]));
}); });
} }

View file

@ -64,6 +64,13 @@ b16 :: enum u16 { false_ :: (0 != 0).(u16); true_ :: (0 == 0).(u16); }; /// b16
b32 :: enum u32 { false_ :: (0 != 0).(u32); true_ :: (0 == 0).(u32); }; /// b32 is a 32-bit boolean. b32 :: enum u32 { false_ :: (0 != 0).(u32); true_ :: (0 == 0).(u32); }; /// b32 is a 32-bit boolean.
b64 :: enum u64 { false_ :: (0 != 0).(u64); true_ :: (0 == 0).(u64); }; /// b64 is a 64-bit boolean. b64 :: enum u64 { false_ :: (0 != 0).(u64); true_ :: (0 == 0).(u64); }; /// b64 is a 64-bit boolean.
#if size_of(int) == {
case 8; uint :: u64;
case 4; uint :: u32;
case 2; uint :: u16;
case 1; uint :: u8;
}
/// Panic displays the given message and crashes the program. /// Panic displays the given message and crashes the program.
/// ///
/// Note: Defers will not run when Panic is called. /// Note: Defers will not run when Panic is called.
@ -74,6 +81,11 @@ Panic :: (message := "jc: runtime panic", loc := #caller_location) #expand #no_d
} }
WriteStderrString(message, "\n"); WriteStderrString(message, "\n");
#if DebugBuild {
PrintStackTrace();
}
Trap(); Trap();
} }
@ -89,7 +101,12 @@ Unreachable :: (message := "jc: unreachable code hit", loc := #caller_location)
} }
WriteStderrString(message, "\n"); WriteStderrString(message, "\n");
trap();
#if DebugBuild {
PrintStackTrace();
}
Trap();
} }
/// CompileError displays the given message and stops compilation. /// CompileError displays the given message and stops compilation.
@ -179,3 +196,22 @@ DebugBuild :: #run -> bool {
opts := get_build_options(); opts := get_build_options();
return opts.emit_debug_info != .NONE; return opts.emit_debug_info != .NONE;
}; };
PrintStackTrace :: () {
WriteStderrString("\n----- Stack Trace -----\n");
node := context.stack_trace.next;
while node != null {
loc := node.info.location;
loc.line_number = node.line_number;
WriteStderrLocation(loc);
name := node.info.name;
if name.count <= 0
{ name = "(unknown)"; }
WriteStderrString(": in ", name, "\n");
if node.next != null
{ node = node.next; }
}
}

View file

@ -7,8 +7,7 @@
/// ///
offset_of :: ($T: Type, ident: Code, loc := #caller_location) -> int #expand { offset_of :: ($T: Type, ident: Code, loc := #caller_location) -> int #expand {
#run (loc: Source_Code_Location) { #run (loc: Source_Code_Location) {
info := type_info(T); if !TypeIsStruct(T) {
if info.type != .STRUCT {
CompileError("jc: offset_of can only be used on struct types", loc = loc); CompileError("jc: offset_of can only be used on struct types", loc = loc);
} }
}(loc); }(loc);
@ -29,14 +28,17 @@ offset_of :: ($T: Type, ident: Code, loc := #caller_location) -> int #expand {
offset_of :: (#discard value: $T, ident: Code, loc := #caller_location) -> int #expand { offset_of :: (#discard value: $T, ident: Code, loc := #caller_location) -> int #expand {
type :: #run -> Type { type :: #run -> Type {
info := T.(*Type_Info); info := T.(*Type_Info);
if info.type == .POINTER {
info = info.(*Type_Info_Pointer).pointer_to; ok, pinfo := TypeIsPointer(T);
if ok {
// @question(judah): do we want it to traverse all the way up to a non-pointer type? // @question(judah): do we want it to traverse all the way up to a non-pointer type?
// I opted against because if you have a *T, you only want offset_of to get an offset // I opted against because if you have a *T, you only want offset_of to get an offset
// from that pointer. What would you do with a field offset from **T? // from that pointer. What would you do with a field offset from **T?
if info.type == .POINTER { if TypeIsPointer(pinfo.pointer_to) {
CompileError("jc: offset_of only allows one level of pointer indirection.", loc = loc); CompileError("jc: offset_of only allows one level of pointer indirection.", loc = loc);
} }
info = pinfo.pointer_to;
} }
return get_type(info); return get_type(info);
@ -50,9 +52,7 @@ align_of :: ($T: Type) -> int #expand {
return #run -> int { return #run -> int {
if size_of(T) == 0 if size_of(T) == 0
{ return 0; } { return 0; }
return offset_of(struct{ _: u8; t: T; }, #code t);
info := type_info(struct{ p: u8; t: T; });
return info.members[1].offset_in_bytes.(int);
}; };
} }
@ -192,46 +192,9 @@ range_of :: ($T: Type, loc := #caller_location) -> (T, T) #expand {
return min_of(T, loc = loc), max_of(T, loc = loc); return min_of(T, loc = loc), max_of(T, loc = loc);
} }
/// sector creates a named block that can exit early via the 'break' keyword.
///
/// Note: The block created by sector is called 'early' by default.
///
/// for sector() {
/// break;
/// break early; // automatically created
/// }
///
/// for sector("render_player") {
/// break render_player;
/// }
///
sector :: ($name := Sector().Name) -> Sector(name) #expand { return .{}; }
// @note(judah): there seems to be a weird race condition in the compiler
// that causes this to hit a null reference check error if running at compile-time.
for_expansion :: (v: Sector, code: Code, _: For_Flags) #expand {
// @todo(judah): fix this case?
// 'for this: sector() { break early; break this; }'
// both names valid here!
#insert #run basic.tprint(#string END
for `%1: 0..0 {
`it :: #run zero_of(void);
`it_index :: #run zero_of(void);
#insert,scope(code) code;
}
END,
// @note(judah): guards against calling this_block with
// an empty string which results in weird error messages.
ifx v.Name.count != 0 v.Name else Sector().Name);
}
#scope_file #scope_file
Sector :: struct(Name: string = "early") {}
basic :: #import "Basic"; // @future
#if RunTests #run { #if RunTests #run {
Test("min_of/max_of:enums", t => { Test("min_of/max_of:enums", t => {
U8Enum :: enum u8 { lo :: -1; hi :: +1; } U8Enum :: enum u8 { lo :: -1; hi :: +1; }

View file

@ -1,5 +1,5 @@
// Dead simple key-value pair type (aka. hash table or hash map) // Dead simple key-value pair type (aka. hash table or hash map)
Record :: struct(Key: Type, Value: Type) { Map :: struct(Key: Type, Value: Type) {
allocator: Allocator; allocator: Allocator;
slots: [..]Slot; slots: [..]Slot;
free_slots: [..]int; free_slots: [..]int;
@ -16,12 +16,7 @@ Record :: struct(Key: Type, Value: Type) {
AllocatedItemsAtStart :: 16; AllocatedItemsAtStart :: 16;
} }
// @note(judah): Not sure if I like these names, but I want to give them a try MapGet :: (r: *Map, key: r.Key) -> r.Value, bool {
Fetch :: Get;
Update :: Set;
Exists :: Has;
Get :: (r: *Record, key: r.Key) -> r.Value, bool {
slot, ok := FindSlot(r, GetHash(r, key)); slot, ok := FindSlot(r, GetHash(r, key));
if !ok if !ok
{ return zero_of(r.Value), false; } { return zero_of(r.Value), false; }
@ -29,7 +24,7 @@ Get :: (r: *Record, key: r.Key) -> r.Value, bool {
return slot.value, true; return slot.value, true;
} }
Set :: (r: *Record, key: r.Key, value: r.Value) { MapSet :: (r: *Map, key: r.Key, value: r.Value) {
hash := GetHash(r, key); hash := GetHash(r, key);
slot, exists := FindSlot(r, hash); slot, exists := FindSlot(r, hash);
if !exists { if !exists {
@ -41,12 +36,12 @@ Set :: (r: *Record, key: r.Key, value: r.Value) {
slot.value = value; slot.value = value;
} }
Has :: (r: *Record, key: r.Key) -> bool { MapHas :: (r: *Map, key: r.Key) -> bool {
_, exists := FindSlot(r, GetHash(r, key)); _, exists := FindSlot(r, GetHash(r, key));
return exists; return exists;
} }
Remove :: (r: *Record, key: r.Key) -> bool, r.Value { MapRemove :: (r: *Map, key: r.Key) -> bool, r.Value {
slot, ok, idx := FindSlot(r, GetHash(r, key)); slot, ok, idx := FindSlot(r, GetHash(r, key));
if !ok if !ok
{ return false, zero_of(r.Value); } { return false, zero_of(r.Value); }
@ -57,13 +52,13 @@ Remove :: (r: *Record, key: r.Key) -> bool, r.Value {
return true, last_value; return true, last_value;
} }
Reset :: (t: *Record) { MapReset :: (t: *Map) {
t.count = 0; t.count = 0;
t.slots.count = 0; t.slots.count = 0;
t.free_slots.count = 0; t.free_slots.count = 0;
} }
for_expansion :: (r: *Record, body: Code, flags: For_Flags) #expand { for_expansion :: (r: *Map, body: Code, flags: For_Flags) #expand {
#assert (flags & .POINTER == 0) "cannot iterate by pointer"; #assert (flags & .POINTER == 0) "cannot iterate by pointer";
for <=(flags & .REVERSE == .REVERSE) slot: r.slots if slot.hash != r.InvalidHash { for <=(flags & .REVERSE == .REVERSE) slot: r.slots if slot.hash != r.InvalidHash {
`it := slot.value; `it := slot.value;
@ -73,15 +68,15 @@ for_expansion :: (r: *Record, body: Code, flags: For_Flags) #expand {
} }
#scope_file; #scope_file
GetHash :: inline (r: *Record, key: r.Key) -> u32 { GetHash :: inline (r: *Map, key: r.Key) -> u32 {
hash := r.HashProc(key); hash := r.HashProc(key);
Assert(hash != r.InvalidHash, "key collided with invalid hash"); Assert(hash != r.InvalidHash, "key collided with invalid hash");
return hash; return hash;
} }
FindSlot :: (r: *Record, hash: u32) -> *r.Slot, bool, int { FindSlot :: (r: *Map, hash: u32) -> *r.Slot, bool, int {
for * r.slots if it.hash == hash { for * r.slots if it.hash == hash {
return it, true, it_index; return it, true, it_index;
} }
@ -89,7 +84,7 @@ FindSlot :: (r: *Record, hash: u32) -> *r.Slot, bool, int {
return null, false, -1; return null, false, -1;
} }
CreateOrReuseSlot :: (r: *Record) -> *r.Slot { CreateOrReuseSlot :: (r: *Map) -> *r.Slot {
inline LazyInit(r); inline LazyInit(r);
if r.free_slots.count > 0 { if r.free_slots.count > 0 {
@ -99,27 +94,27 @@ CreateOrReuseSlot :: (r: *Record) -> *r.Slot {
} }
if r.slots.allocated == 0 { if r.slots.allocated == 0 {
Resize(*r.slots, r.AllocatedItemsAtStart); ArrayGrow(*r.slots, r.AllocatedItemsAtStart);
} }
else if r.slots.count >= r.slots.allocated { else if r.slots.count >= r.slots.allocated {
Resize(*r.slots, NextPowerOfTwo(r.slots.allocated)); ArrayGrow(*r.slots, NextPowerOfTwo(r.slots.allocated));
} }
slot := Append(*r.slots); slot := ArrayAppend(*r.slots);
r.count = r.slots.count; r.count = r.slots.count;
return slot; return slot;
} }
MarkSlotForReuse :: (t: *Record, index: int) { MarkSlotForReuse :: (t: *Map, index: int) {
inline LazyInit(t); inline LazyInit(t);
t.count -= 1; t.count -= 1;
t.slots[index] = .{ hash = t.InvalidHash }; t.slots[index] = .{ hash = t.InvalidHash };
Append(*t.free_slots, index); ArrayAppend(*t.free_slots, index);
} }
LazyInit :: inline (t: *Record) { LazyInit :: inline (t: *Map) {
TrySetAllocator(t); TrySetAllocator(t);
TrySetAllocator(*t.slots); TrySetAllocator(*t.slots);
TrySetAllocator(*t.free_slots); TrySetAllocator(*t.free_slots);
@ -127,52 +122,52 @@ LazyInit :: inline (t: *Record) {
#if RunTests #run { #if RunTests #run {
Test("kv:basic operations", t => { Test("map:basic operations", t => {
ITERATIONS :: 64; ITERATIONS :: 64;
values: Record(int, int); values: Map(int, int);
for 0..ITERATIONS { for 0..ITERATIONS {
Set(*values, it, it * it); MapSet(*values, it, it * it);
} }
for 0..ITERATIONS { for 0..ITERATIONS {
v, ok := Get(*values, it); v, ok := MapGet(*values, it);
Expect(v == it * it); Expect(v == it * it);
} }
for 0..ITERATIONS if it % 2 == 0 { for 0..ITERATIONS if it % 2 == 0 {
ok := Remove(*values, it); ok := MapRemove(*values, it);
Expect(ok); Expect(ok);
} }
for 0..ITERATIONS if it % 2 == 0 { for 0..ITERATIONS if it % 2 == 0 {
_, ok := Get(*values, it); _, ok := MapGet(*values, it);
Expect(!ok); Expect(!ok);
} }
}); });
Test("kv:free slots", t => { Test("map:free slots", t => {
values: Record(int, int); values: Map(int, int);
Set(*values, 1, 100); MapSet(*values, 1, 100);
Set(*values, 2, 200); MapSet(*values, 2, 200);
Set(*values, 3, 300); MapSet(*values, 3, 300);
Expect(values.count == 3); Expect(values.count == 3);
Expect(values.slots.allocated == values.AllocatedItemsAtStart); Expect(values.slots.allocated == values.AllocatedItemsAtStart);
// deleting something that doesn't exist should do nothing // deleting something that doesn't exist should do nothing
ok := Remove(*values, 0); ok := MapRemove(*values, 0);
Expect(!ok); Expect(!ok);
Expect(values.count == 3); Expect(values.count == 3);
Remove(*values, 2); MapRemove(*values, 2);
Expect(values.count == 2); Expect(values.count == 2);
}); });
Test("kv:iteration", t => { Test("map:iteration", t => {
values: Record(int, int); values: Map(int, int);
for 0..10 Set(*values, it, it * it); for 0..10 MapSet(*values, it, it * it);
Expect(values.count == 11); Expect(values.count == 11);
for v, k: values Expect(v == k * k); for v, k: values Expect(v == k * k);

View file

@ -2,7 +2,7 @@ Kilobyte :: 1024;
Megabyte :: 1024 * Kilobyte; Megabyte :: 1024 * Kilobyte;
Gigabyte :: 1024 * Megabyte; Gigabyte :: 1024 * Megabyte;
DefaultAlign :: #run 2 * align_of(*void); DefaultAlign :: size_of(*void);
/// MemEqual checks the equality of two pieces of memory. /// MemEqual checks the equality of two pieces of memory.
/// ///
@ -60,22 +60,40 @@ MemReset :: (p: *$T) {
} }
} }
AlignUpwards :: (ptr: int, align: int = DefaultAlign) -> int { MemAligned :: (p: *void, align: uint = DefaultAlign) -> bool {
Assert(PowerOfTwo(align), "alignment must be a power of two"); return Aligned(p.(uint), align);
p := ptr;
mod := p & (align - 1);
if mod != 0 then p += align - mod;
return p;
} }
PowerOfTwo :: (x: int) -> bool { MemAlignForward :: (p: *void, align: uint = DefaultAlign) -> *void {
return AlignForward(p.(uint), align).(*void);
}
MemAlignBackward :: (p: *void, align: uint = DefaultAlign) -> *void {
return AlignBackward(p.(uint), align).(*void);
}
Aligned :: (a: uint, align: uint = DefaultAlign) -> bool {
return (a & (align - 1)) == 0;
}
AlignForward :: (a: uint, align: uint = DefaultAlign) -> uint {
Assert(PowerOfTwo(align), "jc: must be a power of two");
return (a + align - 1) & ~(align - 1);
}
AlignBackward :: (a: uint, align: uint = DefaultAlign) -> uint {
Assert(PowerOfTwo(align), "jc: must be a power of two");
return a & ~(align - 1);
}
PowerOfTwo :: (x: uint) -> bool {
if x == 0 return false; if x == 0 return false;
return x & (x - 1) == 0; return x & (x - 1) == 0;
} }
NextPowerOfTwo :: (x: int) -> int #no_aoc { NextPowerOfTwo :: (x: uint) -> uint #no_aoc {
Assert(PowerOfTwo(x), "value must be a power of two"); Assert(PowerOfTwo(x), "jc: must be a power of two");
// Bit twiddling hacks next power of two // Bit twiddling hacks next power of two
x |= x >> 1; x |= x >> 1;
@ -87,3 +105,28 @@ NextPowerOfTwo :: (x: int) -> int #no_aoc {
return x + 1; return x + 1;
} }
TrySetAllocator :: (thing: *$T, allocator := context.allocator) #modify {
info := T.(*Type_Info_Struct);
ok := false;
if info.type == .STRUCT
{ ok = true; }
if ok for info.members if it.name == "allocator" && it.type == Allocator.(*Type_Info) {
ok = true;
break;
}
return ok, "can only set allocator on struct with an allocator field or dynamic array";
} #expand {
if thing.allocator.proc == null {
thing.allocator = allocator;
}
}
TrySetAllocator :: (array: *[..]$T, allocator := context.allocator) #expand {
if array.allocator.proc == null {
array.allocator = allocator;
}
}

View file

@ -27,33 +27,7 @@
/// ///
Test :: (name: string, proc: (t: *void) -> (), loc := #caller_location) { Test :: (name: string, proc: (t: *void) -> (), loc := #caller_location) {
// @note(judah): incredibly dumb way to get nicer test runs // @note(judah): incredibly dumb way to get nicer test runs
path := loc.fully_pathed_filename; WriteString(SmallerPath(loc.fully_pathed_filename), ",");
i := path.count - 1;
found_first_slash := false;
while i >= 0 {
if path[i] == "/" {
if found_first_slash {
i += 1;
break;
}
found_first_slash = true;
}
i -= 1;
}
if !found_first_slash {
path = strings.path_filename(loc.fully_pathed_filename);
}
else {
path.count -= i;
path.data += i;
}
WriteString(path, ",");
WriteNumber(loc.line_number); WriteNumber(loc.line_number);
WriteString(": ", name, "... "); WriteString(": ", name, "... ");
@ -111,6 +85,35 @@ TestRun :: struct {
failed: bool; failed: bool;
} }
SmallerPath :: (path: string) -> string {
p := path;
i := p.count - 1;
found_first_slash := false;
while i >= 0 {
if p[i] == "/" {
if found_first_slash {
i += 1;
break;
}
found_first_slash = true;
}
i -= 1;
}
if !found_first_slash {
p = strings.path_filename(path);
}
else {
p.count -= i;
p.data += i;
}
return p;
}
basic :: #import "Basic"; // @future basic :: #import "Basic"; // @future
strings :: #import "String"; // @future strings :: #import "String"; // @future
compiler :: #import "Compiler"; // @future compiler :: #import "Compiler"; // @future

View file

@ -1,6 +1,6 @@
// @todo for jesse these should be PascalCase but I didn't want to give you an annoying merge conflict // @todo for jesse these should be PascalCase but I didn't want to give you an annoying merge conflict
check_type_tag :: ($$T: Type, tag: Type_Info_Tag) -> bool, *Type_Info { TypeTagIs :: ($$T: Type, tag: Type_Info_Tag) -> (bool, *Type_Info) {
#if is_constant(T) { #if is_constant(T) {
info :: type_info(T); info :: type_info(T);
if info.type == tag return true, info; if info.type == tag return true, info;
@ -13,31 +13,36 @@ check_type_tag :: ($$T: Type, tag: Type_Info_Tag) -> bool, *Type_Info {
return false, null; return false, null;
} }
type_is_integer :: ($$T: Type) -> bool, *Type_Info_Integer { TypeIsInteger :: ($$T: Type) -> (bool, *Type_Info_Integer) {
ok, info := check_type_tag(T, .INTEGER); ok, info := TypeTagIs(T, .INTEGER);
return ok, info.(*Type_Info_Integer); return ok, info.(*Type_Info_Integer);
} }
type_is_float :: ($$T: Type) -> bool, *Type_Info_Float { TypeIsFloat :: ($$T: Type) -> (bool, *Type_Info_Float) {
ok, info := check_type_tag(T, .FLOAT); ok, info := TypeTagIs(T, .FLOAT);
return ok, info.(*Type_Info_Float); return ok, info.(*Type_Info_Float);
} }
type_is_scalar :: (t: Type) -> bool { TypeIsScalar :: ($$T: Type) -> bool {
return type_is_integer(t) || type_is_float(t); return TypeIsInteger(T) || TypeIsFloat(T);
} }
type_is_array :: ($$T: Type) -> bool, *Type_Info_Array { TypeIsArray :: ($$T: Type) -> (bool, *Type_Info_Array) {
ok, info := check_type_tag(T, .ARRAY); ok, info := TypeTagIs(T, .ARRAY);
return ok, info.(*Type_Info_Array); return ok, info.(*Type_Info_Array);
} }
type_is_struct :: ($$T: Type) -> bool, *Type_Info_Struct { TypeIsStruct :: ($$T: Type) -> (bool, *Type_Info_Struct) {
ok, info := check_type_tag(T, .STRUCT); ok, info := TypeTagIs(T, .STRUCT);
return ok, info.(*Type_Info_Struct); return ok, info.(*Type_Info_Struct);
} }
type_is_enum :: ($$T: Type) -> bool, *Type_Info_Enum { TypeIsEnum :: ($$T: Type) -> (bool, *Type_Info_Enum) {
ok, info := check_type_tag(T, .ENUM); ok, info := TypeTagIs(T, .ENUM);
return ok, info.(*Type_Info_Enum); return ok, info.(*Type_Info_Enum);
} }
TypeIsPointer :: ($$T: Type) -> (bool, *Type_Info_Pointer) {
ok, info := TypeTagIs(T, .POINTER);
return ok, info.(*Type_Info_Pointer);
}

1
.gitignore vendored
View file

@ -2,3 +2,4 @@
**.dSYM **.dSYM
.DS_Store .DS_Store
docs/ docs/
*.scratch.*

View file

@ -9,15 +9,28 @@ This is what I'm doing day-to-day.
: notes, todos, etc. : notes, todos, etc.
/ 09.07.25
+ replaced allocators with custom arenas
+ Panic/Unreachable prints stack trace in debug builds
+ type_info names are now pascal case
: not sure how [..]T should interface with arenas
/ 09.06.25 / 09.06.25
+ fixed doc generator + fixed doc generator
+ encoding is now fmt + encoding is now fmt
+ math tests pass after reorg + math tests pass after reorg
+ docs look nicer + docs look nicer
+ talked to Jesse about various things
+ name pass to add prefixes
+ Record is Map now
+ removed sector because it's not good (just use 'for 0..0' or 'while true { defer break; ... }')
: need to reimplement dynamic arrays : need to reimplement dynamic arrays
: stable arrays too? < stable arrays too?
/ 09.05.25 / 09.05.25
@ -35,7 +48,7 @@ This is what I'm doing day-to-day.
+ error messages now start with 'jc:' + error messages now start with 'jc:'
+ decided to start using John Carmack-styled plan files to keep track of what I'm working on. would be cool to document the repo as it changes over time. + decided to start using John Carmack-styled plan files to keep track of what I'm working on. would be cool to document the repo as it changes over time.
: planning out how I want allocators to work. I want to go big into arenas, but don't want to completely abandon Jai's builtin allocators > planning out how I want allocators to work. I want to go big into arenas, but don't want to completely abandon Jai's builtin allocators

View file

@ -1,16 +1,6 @@
#module_parameters(RunTests := false); #module_parameters(RunTests := false);
Base64Encode :: (str: string, $for_url := false) -> string, bool { Base64Encode :: (data: []u8, $for_url := false) -> []u8, bool {
enc, ok := Base64Encode(str.([]u8), for_url);
return enc.(string), ok;
}
Base64Decode :: (str: string) -> string, bool {
enc, ok := Base64Decode(str.([]u8));
return enc.(string), ok;
}
Base64Encode :: ($$data: []u8, $for_url := false) -> []u8, bool {
if data.count == 0 return .[], false; if data.count == 0 return .[], false;
#if for_url { #if for_url {
@ -144,6 +134,16 @@ Base64Decode :: (data: []u8) -> []u8, bool {
return decoded, true; return decoded, true;
} }
Base64Encode :: (str: string, $for_url := false) -> string, bool {
enc, ok := Base64Encode(str.([]u8), for_url);
return enc.(string), ok;
}
Base64Decode :: (str: string) -> string, bool {
enc, ok := Base64Decode(str.([]u8));
return enc.(string), ok;
}
#scope_file #scope_file
@ -162,10 +162,6 @@ basic :: #import "Basic"; // @future
strings :: #import "String"; // @future strings :: #import "String"; // @future
// ----------------------------------------------------------
// TESTS
// ----------------------------------------------------------
#if RunTests #run { #if RunTests #run {
Test("encodes", t => { Test("encodes", t => {
str :: "Hello, World"; str :: "Hello, World";

View file

@ -88,7 +88,7 @@ min :: (x: float, y: float) -> float {
} }
abs :: (v: $T) -> T abs :: (v: $T) -> T
#modify { return type_is_scalar(T), "Only accepting scalar types, integer and float"; } { #modify { return TypeIsScalar(T), "Only accepting scalar types, integer and float"; } {
return ifx v < 0 then -v else v; return ifx v < 0 then -v else v;
} }

View file

@ -28,7 +28,7 @@ Transition :: enum {
@Note: Progress must be between 0.0 and 1.0 @Note: Progress must be between 0.0 and 1.0
*/ */
ease :: (progress: $T, $$ease: Ease = .linear, $$transition: Transition = .in) -> T ease :: (progress: $T, $$ease: Ease = .linear, $$transition: Transition = .in) -> T
#modify { return type_is_float(T); } #modify { return TypeIsFloat(T); }
{ {
p := progress; p := progress;

View file

@ -5,7 +5,7 @@
RUN_TESTS := false RUN_TESTS := false
); );
#assert type_is_scalar(RECT_TYPE); #assert TypeIsScalar(RECT_TYPE);
#load "common.jai"; #load "common.jai";
#load "vec.jai"; #load "vec.jai";

View file

@ -187,7 +187,7 @@ operator / :: inline (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc {
} }
operator + :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc #symmetric operator + :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc #symmetric
#modify { return type_is_scalar(R), "type is not integer or float"; } { #modify { return TypeIsScalar(R), "type is not integer or float"; } {
res: Vec(l.N, l.T) = ---; res: Vec(l.N, l.T) = ---;
#if l.N <= 4 { #if l.N <= 4 {
res.x = l.x + r; res.x = l.x + r;
@ -202,7 +202,7 @@ operator + :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc #symmetric
} }
operator - :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc operator - :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc
#modify { return type_is_scalar(R), "type is not integer or float"; } { #modify { return TypeIsScalar(R), "type is not integer or float"; } {
res: Vec(l.N, l.T) = ---; res: Vec(l.N, l.T) = ---;
#if l.N <= 4 { #if l.N <= 4 {
res.x = l.x - r; res.x = l.x - r;
@ -216,7 +216,7 @@ operator - :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc
return res; return res;
} }
operator - :: inline (l: $R, r: Vec) -> Vec(l.N, l.T) #no_abc operator - :: inline (l: $R, r: Vec) -> Vec(l.N, l.T) #no_abc
#modify { return type_is_scalar(R), "type is not integer or float"; } { #modify { return TypeIsScalar(R), "type is not integer or float"; } {
res: Vec(l.N, l.T) = ---; res: Vec(l.N, l.T) = ---;
#if l.N <= 4 { #if l.N <= 4 {
res.x = l - r.x; res.x = l - r.x;
@ -245,7 +245,7 @@ operator- :: inline(v: Vec) -> Vec(v.N, v.T) #no_abc {
} }
operator * :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc #symmetric operator * :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc #symmetric
#modify { return type_is_scalar(R), "type is not integer or float"; } { #modify { return TypeIsScalar(R), "type is not integer or float"; } {
res: Vec(l.N, l.T) = ---; res: Vec(l.N, l.T) = ---;
#if l.N <= 4 { #if l.N <= 4 {
res.x = l.x*r; res.x = l.x*r;
@ -260,7 +260,7 @@ operator * :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc #symmetric
} }
operator / :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc operator / :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc
#modify { return type_is_scalar(R), "type is not integer or float"; } { #modify { return TypeIsScalar(R), "type is not integer or float"; } {
res: Vec(l.N, l.T) = ---; res: Vec(l.N, l.T) = ---;
#if l.N <= 4 { #if l.N <= 4 {
res.x = l.x/r; res.x = l.x/r;
@ -486,7 +486,7 @@ reflect :: (v: Vec2, p: Vec2) -> Vec2 #no_abc {
} }
round :: (v: Vec($N, $T)) -> Vec(N, T) #no_abc round :: (v: Vec($N, $T)) -> Vec(N, T) #no_abc
#modify { return type_is_float(T), "Used non-float vector on round"; } { #modify { return TypeIsFloat(T), "Used non-float vector on round"; } {
r: Vec(N, T) = ---; r: Vec(N, T) = ---;
n := N - 1; n := N - 1;
while n >= 0 { while n >= 0 {
@ -507,31 +507,31 @@ Vec4 :: Vec(4, float);
Quat :: #type,distinct Vec4; // Note(Jesse): I Had to make this distinct, otherwise operators stomp on eachother Quat :: #type,distinct Vec4; // Note(Jesse): I Had to make this distinct, otherwise operators stomp on eachother
v2f :: (x: $T = 0, y: T = 0) -> Vec2 v2f :: (x: $T = 0, y: T = 0) -> Vec2
#modify { return type_is_float(T), "use v2i for integer arguments"; } #modify { return TypeIsFloat(T), "use v2i for integer arguments"; }
#expand { #expand {
return .{ x = x, y = y }; return .{ x = x, y = y };
} }
v2i :: (x: $T = 0, y: T = 0) -> Vec(2, T) v2i :: (x: $T = 0, y: T = 0) -> Vec(2, T)
#modify { return type_is_integer(T), "use v2f for float arguments"; } #modify { return TypeIsInteger(T), "use v2f for float arguments"; }
#expand { #expand {
return .{ x = x, y = y }; return .{ x = x, y = y };
} }
v3f :: (x: $T = 0, y: T = 0, z: T = 0) -> Vec3 v3f :: (x: $T = 0, y: T = 0, z: T = 0) -> Vec3
#modify { return type_is_float(T), "use v3i for integer arguments"; } #modify { return TypeIsFloat(T), "use v3i for integer arguments"; }
#expand { #expand {
return .{ x = x, y = y, z = z }; return .{ x = x, y = y, z = z };
} }
v3i :: (x: $T = 0, y: T = 0, z: T = 0) -> Vec(3, T) v3i :: (x: $T = 0, y: T = 0, z: T = 0) -> Vec(3, T)
#modify { return type_is_integer(T), "use v3f for float arguments"; } #modify { return TypeIsInteger(T), "use v3f for float arguments"; }
#expand { #expand {
return .{ x = x, y = y, z = z }; return .{ x = x, y = y, z = z };
} }
v4f :: (x: $T = 0, y: T = 0, z: T = 0, w: T = 0) -> Vec4 v4f :: (x: $T = 0, y: T = 0, z: T = 0, w: T = 0) -> Vec4
#modify { return type_is_float(T), "use v4i for integer arguments"; } #modify { return TypeIsFloat(T), "use v4i for integer arguments"; }
#expand { #expand {
return .{ x = x, y = y, z = z, w = w }; return .{ x = x, y = y, z = z, w = w };
} }
@ -541,7 +541,7 @@ v4f :: (v: Vec3, $$w: float) -> Vec4 #expand {
} }
v4i :: (x: $T = 0, y: T = 0, z: T = 0, w: T = 0) -> Vec(4, T) v4i :: (x: $T = 0, y: T = 0, z: T = 0, w: T = 0) -> Vec(4, T)
#modify { return type_is_integer(T), "use v4f for float arguments"; } #modify { return TypeIsInteger(T), "use v4f for float arguments"; }
#expand { #expand {
return .{ x = x, y = y, z = z, w = w }; return .{ x = x, y = y, z = z, w = w };
} }

View file

@ -7,15 +7,12 @@
/// system. /// system.
#module_parameters(RunTests := false); #module_parameters(RunTests := false);
// @note(judah): this causes some very weird issues in tests so it's ifdef'd out
#if !RunTests #add_context temp_allocator: Allocator;
#load "+internal/builtin.jai"; #load "+internal/builtin.jai";
#load "+internal/array.jai"; #load "+internal/array.jai";
#load "+internal/kv.jai"; #load "+internal/map.jai";
#load "+internal/hashing.jai"; #load "+internal/hashing.jai";
#load "+internal/memory.jai"; #load "+internal/memory.jai";
#load "+internal/allocators.jai"; #load "+internal/arenas.jai";
#load "+internal/testing.jai"; #load "+internal/testing.jai";
#load "+internal/keywords.jai"; #load "+internal/keywords.jai";
#load "+internal/type_info.jai"; #load "+internal/type_info.jai";