Compare commits
No commits in common. "49973e65ed0a4198ae808ed8fc7e9d1750731e68" and "324bfcf2c05462e6ca1e05076d54e569d1d18803" have entirely different histories.
49973e65ed
...
324bfcf2c0
60 changed files with 2589 additions and 3132 deletions
|
|
@ -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
|
|
||||||
|
|
@ -1,250 +0,0 @@
|
||||||
/// Append pushes value to the end of an array, resizing if necessary.
|
|
||||||
///
|
|
||||||
/// Note: If no allocator has been set, Append will use the current context allocator.
|
|
||||||
/// Note: Calls to Append may invalidate pre-existing pointers.
|
|
||||||
Append :: inline (arr: *[..]$T, value: T) -> *T {
|
|
||||||
TrySetAllocator(arr);
|
|
||||||
ptr := basic.array_add(arr);
|
|
||||||
ptr.* = value;
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Append pushes a default initialized value to the end of an array,
|
|
||||||
/// returning a pointer to the newly pushed value.
|
|
||||||
///
|
|
||||||
/// Note: If no allocator has been set, Append will use the current context allocator.
|
|
||||||
/// Note: Calls to Append may invalidate pre-existing pointers.
|
|
||||||
Append :: inline (arr: *[..]$T) -> *T {
|
|
||||||
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
|
|
||||||
{ return; }
|
|
||||||
|
|
||||||
TrySetAllocator(arr);
|
|
||||||
basic.array_reserve(arr, new_count,, allocator = arr.allocator);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Slice returns a subsection of an array.
|
|
||||||
Slice :: (view: []$T, start_idx: int, count := -1, loc := #caller_location) -> []T {
|
|
||||||
AssertCallsite(start_idx >= +0 && start_idx < view.count, "jc: incorrect slice bounds");
|
|
||||||
AssertCallsite(count >= -1 && count < view.count, "jc: incorrect slice length");
|
|
||||||
|
|
||||||
if count == -1
|
|
||||||
{ count = view.count - start_idx; }
|
|
||||||
|
|
||||||
return .{ data = view.data + start_idx, count = count };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reset sets an array's length to 0, but leaves its memory intact.
|
|
||||||
/// Note: To reset the associated memory as well, see Clear.
|
|
||||||
Reset :: (view: *[]$T) {
|
|
||||||
view.count = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clear zeroes an array's memory and sets its length to 0.
|
|
||||||
/// Note: To leave the associated memory intact, see Reset.
|
|
||||||
Clear :: (view: *[]$T) {
|
|
||||||
MemZero(view.data, view.count * size_of(T));
|
|
||||||
view.count = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Equal checks the equality of two arrays.
|
|
||||||
Equal :: (lhs: []$T, rhs: []T) -> bool {
|
|
||||||
if lhs.count != rhs.count
|
|
||||||
{ return false; }
|
|
||||||
return MemEqual(lhs.data, rhs.data, lhs.count * size_of(T));
|
|
||||||
}
|
|
||||||
|
|
||||||
CheckBounds :: ($$index: $T, $$count: T, loc := #caller_location) #expand {
|
|
||||||
Message :: "bounds check failed!";
|
|
||||||
#if is_constant(index) && is_constant(count) {
|
|
||||||
if index < 0 || index >= count {
|
|
||||||
CompileError(Message, loc = loc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if index < 0 || index > count {
|
|
||||||
Panic(Message, loc = loc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
FindFlags :: enum_flags {
|
|
||||||
Last; // Return the last matching element.
|
|
||||||
FromEnd; // Search in reverse.
|
|
||||||
}
|
|
||||||
|
|
||||||
FindResult :: struct(T: Type) {
|
|
||||||
value: T = ---;
|
|
||||||
index: int;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find searches through an array, returning the first element
|
|
||||||
/// that matches the given value.
|
|
||||||
Find :: (view: []$T, value: T, $flags: FindFlags = 0) -> (bool, FindResult(T)) {
|
|
||||||
found: bool;
|
|
||||||
|
|
||||||
result: FindResult(T);
|
|
||||||
result.index = -1;
|
|
||||||
|
|
||||||
REVERSE :: #run (flags & .FromEnd).(bool);
|
|
||||||
for #v2 <=REVERSE view if it == value {
|
|
||||||
found = true;
|
|
||||||
result.index = it_index;
|
|
||||||
result.value = it;
|
|
||||||
#if !(flags & .Last) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return found, result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Contains checks if the given value exists in an array.
|
|
||||||
Contains :: (view: []$T, value: T) -> bool {
|
|
||||||
return Find(view, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
TrimFlags :: enum_flags {
|
|
||||||
FromStart; // Trim the start of the array.
|
|
||||||
FromEnd; // Trim the end of the array.
|
|
||||||
MatchInFull; // Only trim when the cutset matches exactly.
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trim returns a subsection of an array with all leading/trailing values
|
|
||||||
/// from cutset removed.
|
|
||||||
Trim :: (view: []$T, cutset: []T, $flags: TrimFlags = .FromStart) -> []T {
|
|
||||||
result := view;
|
|
||||||
if cutset.count == 0 || cutset.count > view.count {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if flags & .FromStart {
|
|
||||||
#if flags & .MatchInFull {
|
|
||||||
if Equal(Slice(view, 0, cutset.count), cutset) {
|
|
||||||
result = Slice(view, cutset.count, -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
while result.count > 0 {
|
|
||||||
if !Contains(cutset, result[0]) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.data += 1;
|
|
||||||
result.count -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if flags & .FromEnd {
|
|
||||||
#if flags & .MatchInFull {
|
|
||||||
if Equal(Slice(view, view.count - cutset.count), cutset) {
|
|
||||||
result.count -= cutset.count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
while result.count > 0 {
|
|
||||||
if !Contains(cutset, result[result.count - 1]) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.count -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#scope_file
|
|
||||||
|
|
||||||
basic :: #import "Basic"; // @future
|
|
||||||
|
|
||||||
#if RunTests #run,stallable {
|
|
||||||
Test("slice", t => {
|
|
||||||
a1 := int.[ 1, 2, 3, 4, 5 ];
|
|
||||||
a2 := Slice(a1, 2);
|
|
||||||
Expect(a2.count == 3);
|
|
||||||
Expect(Equal(a2, int.[ 3, 4, 5 ]));
|
|
||||||
|
|
||||||
b1 := int.[ 1, 2, 3, 4, 5 ];
|
|
||||||
b2 := Slice(b1, 2, 0);
|
|
||||||
Expect(b2.count == 0);
|
|
||||||
Expect(b2.data == b1.data + 2);
|
|
||||||
|
|
||||||
c1 := int.[ 1, 2, 3, 4, 5 ];
|
|
||||||
c2 := Slice(c1, 3, 1);
|
|
||||||
Expect(c2.count == 1);
|
|
||||||
Expect(Equal(c2, int.[ 4 ]));
|
|
||||||
|
|
||||||
d1 := int.[ 1, 2, 3 ];
|
|
||||||
d2 := Slice(d1, 2);
|
|
||||||
Expect(d2.count == 1);
|
|
||||||
Expect(Equal(d2, int.[ 3 ]));
|
|
||||||
});
|
|
||||||
|
|
||||||
Test("find", t => {
|
|
||||||
a := int.[ 1, 2, 3, 4, 5 ];
|
|
||||||
|
|
||||||
ok, res := Find(a, 3);
|
|
||||||
Expect(ok && res.index == 2);
|
|
||||||
Expect(res.value == 3);
|
|
||||||
|
|
||||||
ok, res = Find(a, -1);
|
|
||||||
Expect(!ok && res.index == -1);
|
|
||||||
|
|
||||||
b := int.[ 1, 2, 2, 3, 4, 5, 2 ];
|
|
||||||
|
|
||||||
ok, res = Find(b, 2);
|
|
||||||
Expect(ok && res.index == 1);
|
|
||||||
|
|
||||||
ok, res = Find(b, 2, .FromEnd);
|
|
||||||
Expect(ok && res.index == 6);
|
|
||||||
|
|
||||||
c := int.[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ];
|
|
||||||
|
|
||||||
ok, res = Find(c, 0, .Last);
|
|
||||||
Expect(ok && res.index == 8);
|
|
||||||
|
|
||||||
ok, res = Find(c, 0, .FromEnd | .Last);
|
|
||||||
Expect(ok && res.index == 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
Test("contains", t => {
|
|
||||||
a := int.[ 1, 2, 3, 4, 5 ];
|
|
||||||
Expect(Contains(a, 3));
|
|
||||||
Expect(!Contains(a, -1));
|
|
||||||
});
|
|
||||||
|
|
||||||
Test("trim", t => {
|
|
||||||
a1 := int.[ 0, 0, 0, 1, 2, 3 ];
|
|
||||||
a2 := Trim(a1, .[ 0 ]);
|
|
||||||
Expect(Equal(a1, .[ 0, 0, 0, 1, 2, 3 ]));
|
|
||||||
Expect(Equal(a2, .[ 1, 2, 3 ]));
|
|
||||||
|
|
||||||
b1 := int.[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ];
|
|
||||||
b2 := Trim(b1, .[ 0 ], .FromEnd);
|
|
||||||
Expect(Equal(b1, .[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ]));
|
|
||||||
Expect(Equal(b2, .[ 0, 0, 0, 1, 2, 3 ]));
|
|
||||||
|
|
||||||
c1 := int.[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ];
|
|
||||||
c2 := Trim(c1, .[ 0 ], .FromStart | .FromEnd);
|
|
||||||
Expect(Equal(c1, .[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ]));
|
|
||||||
Expect(Equal(c2, .[ 1, 2, 3 ]));
|
|
||||||
|
|
||||||
d1 := int.[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ];
|
|
||||||
d2 := Trim(d1, .[ 0, 0, 0 ], .FromStart | .MatchInFull);
|
|
||||||
d3 := Trim(d1, .[ 0, 0, 0 ], .FromEnd | .MatchInFull);
|
|
||||||
d4 := Trim(d1, .[ 0, 0, 0 ], .FromStart | .FromEnd | .MatchInFull);
|
|
||||||
Expect(Equal(d1, .[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ]));
|
|
||||||
Expect(Equal(d2, .[ 1, 2, 3, 0, 0, 0 ]));
|
|
||||||
Expect(Equal(d3, .[ 0, 0, 0, 1, 2, 3 ]));
|
|
||||||
Expect(Equal(d4, .[ 1, 2, 3 ]));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,181 +0,0 @@
|
||||||
/// JcMajor is the current major version of this library.
|
|
||||||
///
|
|
||||||
/// Major versions are guaranteed to have stable apis
|
|
||||||
/// for their duration.
|
|
||||||
JcMajor :: 0;
|
|
||||||
|
|
||||||
/// JcMinor is the current minor version of this library.
|
|
||||||
///
|
|
||||||
/// Minor versions denote bug fixes, additions, or improvements
|
|
||||||
/// that do not affect api stability.
|
|
||||||
JcMinor :: 1;
|
|
||||||
|
|
||||||
// @note(judah): we can't use range_of here because a compiler bug?
|
|
||||||
|
|
||||||
S8Min :: #run min_of(s8); /// -128 (-0x80)
|
|
||||||
S8Max :: #run max_of(s8); /// 127 (0x7f)
|
|
||||||
S16Min :: #run min_of(s16); /// -32768 (-0x8000)
|
|
||||||
S16Max :: #run max_of(s16); /// 32767 (0x7f_ff)
|
|
||||||
S32Min :: #run min_of(s32); /// -2147483648 (-0x8000_0000)
|
|
||||||
S32Max :: #run max_of(s32); /// 2147483647 (0x7fff_ffff)
|
|
||||||
S64Min :: #run min_of(s64); /// -9223372036854775808 (-0x80000000_00000000)
|
|
||||||
S64Max :: #run max_of(s64); /// 9223372036854775807 (0x7fffffff_ffffffff)
|
|
||||||
|
|
||||||
U8Min :: #run min_of(u8); /// 0 (0x00)
|
|
||||||
U8Max :: #run max_of(u8); /// 255 (0xff)
|
|
||||||
U16Min :: #run min_of(u16); /// 0 (0x00_00)
|
|
||||||
U16Max :: #run max_of(u16); /// 65535 (0xff_ff)
|
|
||||||
U32Min :: #run min_of(u32); /// 0 (0x0000_0000)
|
|
||||||
U32Max :: #run max_of(u32); /// 4294967295 (0xffff_ffff)
|
|
||||||
U64Min :: #run min_of(u64); /// 0 (0x00000000_00000000)
|
|
||||||
U64Max :: #run max_of(u64); /// 18446744073709551615 (0xffffffff_ffffffff)
|
|
||||||
|
|
||||||
Float32Min :: #run min_of(float32); /// 1.17549e-38 (0h0080_0000)
|
|
||||||
Float32Max :: #run max_of(float32); /// 3.40282e+38 (0h7f7fffff)
|
|
||||||
Float64Min :: #run min_of(float64); /// 2.22507e-308 (0h00100000_00000000)
|
|
||||||
Float64Max :: #run max_of(float64); /// 1.79769e+308 (0h7fefffff_ffffffff)
|
|
||||||
|
|
||||||
/// m0 is a 0-size marker type.
|
|
||||||
///
|
|
||||||
/// It allows specific offsets within a type to be marked which is useful for (de)serialization.
|
|
||||||
///
|
|
||||||
/// MyType :: struct {
|
|
||||||
/// do_not_serialize_1: *void;
|
|
||||||
///
|
|
||||||
/// _start: m0; // Has the same offset as serialize_1
|
|
||||||
/// serialize_1: [32]u8;
|
|
||||||
/// serialize_2: u64;
|
|
||||||
/// serialize_3: bool;
|
|
||||||
/// serialize_4: float32;
|
|
||||||
/// _end: m0; // Has the same offset as serialize_4
|
|
||||||
///
|
|
||||||
/// do_not_serialize_2: [..]int;
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// value := MyType.{};
|
|
||||||
/// start := *value + offset_of(value, #code _start);
|
|
||||||
/// end := *value + offset_of(value, #code _end);
|
|
||||||
/// WriteToDisk(data = start, count = end - start);
|
|
||||||
///
|
|
||||||
m0 :: #type void;
|
|
||||||
|
|
||||||
b8 :: enum u8 { false_ :: (0 != 0).(u8); true_ :: (0 == 0).(u8); }; /// b8 is an 8-bit boolean.
|
|
||||||
b16 :: enum u16 { false_ :: (0 != 0).(u16); true_ :: (0 == 0).(u16); }; /// b16 is a 16-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.
|
|
||||||
|
|
||||||
/// Panic displays the given message and crashes the program.
|
|
||||||
///
|
|
||||||
/// Note: Defers will not run when Panic is called.
|
|
||||||
Panic :: (message := "jc: runtime panic", loc := #caller_location) #expand #no_debug {
|
|
||||||
#if DebugBuild {
|
|
||||||
WriteStderrLocation(loc);
|
|
||||||
WriteStderrString(": ");
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteStderrString(message, "\n");
|
|
||||||
Trap();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unreachable displays the given message and causes an execution trap.
|
|
||||||
///
|
|
||||||
/// Note: Defers will not run when Unreachable is called.
|
|
||||||
Unreachable :: (message := "jc: unreachable code hit", loc := #caller_location) #expand #no_debug {
|
|
||||||
trap :: #ifx DebugBuild then DebugTrap else Trap;
|
|
||||||
|
|
||||||
#if DebugBuild {
|
|
||||||
WriteStderrLocation(loc);
|
|
||||||
WriteStderrString(": ");
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteStderrString(message, "\n");
|
|
||||||
trap();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// CompileError displays the given message and stops compilation.
|
|
||||||
///
|
|
||||||
/// Note: By default, the error is reported at the callsite.
|
|
||||||
CompileError :: (message: string, loc := #caller_location) #expand #no_debug #compile_time {
|
|
||||||
if #compile_time {
|
|
||||||
compiler_report(message, loc, .ERROR);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Panic("jc: CompileError can only be called at compile-time", loc = loc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// @todo(judah): these should be different!
|
|
||||||
|
|
||||||
/// Trap causes an execution trap.
|
|
||||||
Trap :: () #expand #no_debug {
|
|
||||||
debug_break(); // Provided by Runtime_Support
|
|
||||||
}
|
|
||||||
|
|
||||||
/// DebugTrap causes an execution trap that grabs the attention of a debugger.
|
|
||||||
DebugTrap :: () #expand #no_debug {
|
|
||||||
debug_break(); // Provided by Runtime_Support
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DebugBuild
|
|
||||||
{
|
|
||||||
/// Assert causes a debug break if the given condition is false.
|
|
||||||
Assert :: (cond: bool, message := "jc: condition was false", loc := #caller_location) #expand #no_debug {
|
|
||||||
// @note(judah): We only need to do this to route into the context's builtin assertion handling.
|
|
||||||
if cond || context.handling_assertion_failure return;
|
|
||||||
|
|
||||||
context.handling_assertion_failure = true;
|
|
||||||
should_trap := context.assertion_failed(loc, message);
|
|
||||||
context.handling_assertion_failure = false;
|
|
||||||
|
|
||||||
if should_trap {
|
|
||||||
DebugTrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// AssertCallsite works identically to Assert, except that it expects a
|
|
||||||
/// Source_Code_Location (called 'loc') to exist in the calling scope.
|
|
||||||
///
|
|
||||||
/// MyProc :: (loc := #caller_location) {
|
|
||||||
/// AssertCallsite(false); // 'loc' is passed implicitly
|
|
||||||
/// Assert(false, loc = loc); // equivalent
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
AssertCallsite :: (cond: bool, message := "jc: condition was false") #expand #no_debug {
|
|
||||||
Assert(cond, message, loc = `loc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// @note(judah): these need to be separate declarations so we can use #discard.
|
|
||||||
// otherwise, the compiler will generate instructions to setup the call when assertions are disabled.
|
|
||||||
Assert :: (#discard cond: bool, #discard message := "", #discard loc := #caller_location) #expand #no_debug {}
|
|
||||||
AssertCallsite :: (#discard cond: bool, #discard message := "") #expand #no_debug {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#scope_module
|
|
||||||
|
|
||||||
WriteString :: write_strings; @jc.nodocs // Provided by Runtime_Support
|
|
||||||
WriteNumber :: write_number; @jc.nodocs // Provided by Runtime_Support
|
|
||||||
|
|
||||||
// @note(judah): This is a direct copy of Runtime_Support's write_loc since it's not exported
|
|
||||||
WriteStderrLocation :: (loc: Source_Code_Location) {
|
|
||||||
WriteStderrString(loc.fully_pathed_filename, ":");
|
|
||||||
WriteStderrNumber(loc.line_number);
|
|
||||||
WriteStderrString(",");
|
|
||||||
WriteStderrNumber(loc.character_number);
|
|
||||||
} @jc.nodocs
|
|
||||||
|
|
||||||
WriteStderrString :: #bake_arguments write_strings(to_standard_error = true); // Provided by Runtime_Support
|
|
||||||
WriteStderrNumber :: #bake_arguments write_number(to_standard_error = true); // Provided by Runtime_Support
|
|
||||||
|
|
||||||
|
|
||||||
#scope_file
|
|
||||||
|
|
||||||
DebugBuild :: #run -> bool {
|
|
||||||
// @note(judah): there's not really a good way to detect opt level/build type,
|
|
||||||
// so just check if debug info is being emitted.
|
|
||||||
#import "Compiler";
|
|
||||||
opts := get_build_options();
|
|
||||||
return opts.emit_debug_info != .NONE;
|
|
||||||
};
|
|
||||||
|
|
@ -1,278 +0,0 @@
|
||||||
/// offset_of returns the byte offset of a field within the type T.
|
|
||||||
///
|
|
||||||
/// Note: T must be a struct type.
|
|
||||||
///
|
|
||||||
/// MyType :: struct { x: int; y: int; z: int; };
|
|
||||||
/// offset_of(MyType, #code y); // 8
|
|
||||||
///
|
|
||||||
offset_of :: ($T: Type, ident: Code, loc := #caller_location) -> int #expand {
|
|
||||||
#run (loc: Source_Code_Location) {
|
|
||||||
info := type_info(T);
|
|
||||||
if info.type != .STRUCT {
|
|
||||||
CompileError("jc: offset_of can only be used on struct types", loc = loc);
|
|
||||||
}
|
|
||||||
}(loc);
|
|
||||||
|
|
||||||
return #run -> int {
|
|
||||||
t: T = ---;
|
|
||||||
return (*t.#insert ident).(*void) - (*t).(*void);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// offset_of returns the byte offset of a field within the type of value.
|
|
||||||
///
|
|
||||||
/// Note: If offset_of is given a pointer value, it will use the type pointed to.
|
|
||||||
///
|
|
||||||
/// value := struct{ x: int; y: int; z: int; }.{};
|
|
||||||
/// offset_of(value, #code y); // 8
|
|
||||||
///
|
|
||||||
offset_of :: (#discard value: $T, ident: Code, loc := #caller_location) -> int #expand {
|
|
||||||
type :: #run -> Type {
|
|
||||||
info := T.(*Type_Info);
|
|
||||||
if info.type == .POINTER {
|
|
||||||
info = info.(*Type_Info_Pointer).pointer_to;
|
|
||||||
// @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
|
|
||||||
// from that pointer. What would you do with a field offset from **T?
|
|
||||||
if info.type == .POINTER {
|
|
||||||
CompileError("jc: offset_of only allows one level of pointer indirection.", loc = loc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return get_type(info);
|
|
||||||
};
|
|
||||||
|
|
||||||
return offset_of(type, ident, loc = loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// align_of returns the alignment of type T.
|
|
||||||
align_of :: ($T: Type) -> int #expand {
|
|
||||||
return #run -> int {
|
|
||||||
if size_of(T) == 0
|
|
||||||
{ return 0; }
|
|
||||||
|
|
||||||
info := type_info(struct{ p: u8; t: T; });
|
|
||||||
return info.members[1].offset_in_bytes.(int);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// default_of returns a value of type T as if it was just instantiated.
|
|
||||||
///
|
|
||||||
/// Note: default_of will call the initializer for aggregate types, so you
|
|
||||||
/// may want zero_of instead.
|
|
||||||
default_of :: ($T: Type) -> T #expand {
|
|
||||||
default: T;
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// undefined_of returns a value of type T that has not been initialized.
|
|
||||||
undefined_of :: ($T: Type) -> T #expand {
|
|
||||||
uninit: T = ---;
|
|
||||||
return uninit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// zero_of returns a value of type T that has been zero-initialized.
|
|
||||||
///
|
|
||||||
/// Note: zero_of will not call the initializer for aggregate types, so you
|
|
||||||
/// may want default_of instead.
|
|
||||||
zero_of :: ($T: Type) -> T #expand {
|
|
||||||
zero := undefined_of(T);
|
|
||||||
MemZero(*zero);
|
|
||||||
return zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// min_of returns the minimum value T can represent.
|
|
||||||
///
|
|
||||||
/// Note: T must be an integer, float, or enum type.
|
|
||||||
min_of :: ($T: Type, loc := #caller_location) -> T #expand {
|
|
||||||
return #run -> T {
|
|
||||||
info := T.(*Type_Info);
|
|
||||||
if info.type == {
|
|
||||||
case .INTEGER;
|
|
||||||
i := info.(*Type_Info_Integer);
|
|
||||||
if i.runtime_size == {
|
|
||||||
case 1; return (ifx i.signed then -0x80 else 0).(T, no_check);
|
|
||||||
case 2; return (ifx i.signed then -0x8000 else 0).(T, no_check);
|
|
||||||
case 4; return (ifx i.signed then -0x8000_0000 else 0).(T, no_check);
|
|
||||||
case 8; return (ifx i.signed then -0x8000_0000_0000_0000 else 0).(T, no_check);
|
|
||||||
case ; CompileError("jc: unknown integer size", loc = loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
case .FLOAT;
|
|
||||||
if info.runtime_size == {
|
|
||||||
case 4; return (0h0080_0000).(T, no_check);
|
|
||||||
case 8; return (0h00100000_00000000).(T, no_check);
|
|
||||||
case ; CompileError("jc: unknown float size", loc = loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
case .ENUM;
|
|
||||||
i := info.(*Type_Info_Enum);
|
|
||||||
if i.values.count == 0 {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
min: T = i.values[0].(T, no_check);
|
|
||||||
if i.internal_type.signed {
|
|
||||||
for i.values if it.(T) < min {
|
|
||||||
min = it.(T);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
for i.values if it.(T) < min {
|
|
||||||
min = it.(T);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return min;
|
|
||||||
|
|
||||||
case;
|
|
||||||
CompileError("jc: min_of requires an enum, integer, or float type", loc = loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// max_of returns the maximum value T can represent.
|
|
||||||
///
|
|
||||||
/// Note: T must be an integer, float, or enum type.
|
|
||||||
max_of :: ($T: Type, loc := #caller_location) -> T #expand {
|
|
||||||
return #run -> T {
|
|
||||||
info := T.(*Type_Info);
|
|
||||||
if info.type == {
|
|
||||||
case .INTEGER;
|
|
||||||
i := info.(*Type_Info_Integer);
|
|
||||||
if i.runtime_size == {
|
|
||||||
case 1; return (ifx i.signed then 0x7f else 0xff).(T, no_check);
|
|
||||||
case 2; return (ifx i.signed then 0x7fff else 0xffff).(T, no_check);
|
|
||||||
case 4; return (ifx i.signed then 0x7fff_ffff else 0xffff_ffff).(T, no_check);
|
|
||||||
case 8; return (ifx i.signed then 0x7fff_ffff_ffff_ffff else 0xffff_ffff_ffff_ffff).(T, no_check);
|
|
||||||
case ; CompileError("jc: unknown integer size", loc = loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
case .FLOAT;
|
|
||||||
if info.runtime_size == {
|
|
||||||
case 4; return (0h7F7FFFFF).(T, no_check);
|
|
||||||
case 8; return (0h7FEFFFFF_FFFFFFFF).(T, no_check);
|
|
||||||
case ; CompileError("jc: unknown float size", loc = loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
case .ENUM;
|
|
||||||
i := info.(*Type_Info_Enum);
|
|
||||||
if i.values.count == 0 {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
max := i.values[0].(T, no_check);
|
|
||||||
if i.internal_type.signed {
|
|
||||||
for i.values if xx it > max {
|
|
||||||
max = xx it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
for i.values if xx it > max {
|
|
||||||
max = xx it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return max;
|
|
||||||
|
|
||||||
case;
|
|
||||||
CompileError("jc: max_of requires an enum, integer, or float type", loc = loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// range_of returns the minimum and maximum values T can represent.
|
|
||||||
///
|
|
||||||
/// Note: T must be an integer, float, or enum type.
|
|
||||||
range_of :: ($T: Type, loc := #caller_location) -> (T, T) #expand {
|
|
||||||
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
|
|
||||||
|
|
||||||
Sector :: struct(Name: string = "early") {}
|
|
||||||
|
|
||||||
basic :: #import "Basic"; // @future
|
|
||||||
|
|
||||||
#if RunTests #run {
|
|
||||||
Test("min_of/max_of:enums", t => {
|
|
||||||
U8Enum :: enum u8 { lo :: -1; hi :: +1; }
|
|
||||||
S8Enum :: enum s8 { lo :: -2; hi :: -1; }
|
|
||||||
{
|
|
||||||
Expect(min_of(U8Enum) == U8Enum.lo);
|
|
||||||
Expect(min_of(S8Enum) == S8Enum.lo);
|
|
||||||
Expect(max_of(U8Enum) == U8Enum.hi);
|
|
||||||
Expect(max_of(S8Enum) == S8Enum.hi);
|
|
||||||
}
|
|
||||||
|
|
||||||
U16Enum :: enum u16 { lo :: -1; hi :: +1; }
|
|
||||||
S16Enum :: enum s16 { lo :: -2; hi :: -1; }
|
|
||||||
{
|
|
||||||
Expect(min_of(U16Enum) == U16Enum.lo);
|
|
||||||
Expect(min_of(S16Enum) == S16Enum.lo);
|
|
||||||
Expect(max_of(U16Enum) == U16Enum.hi);
|
|
||||||
Expect(max_of(S16Enum) == S16Enum.hi);
|
|
||||||
}
|
|
||||||
|
|
||||||
U32Enum :: enum u32 { lo :: -1; hi :: +1; }
|
|
||||||
S32Enum :: enum s32 { lo :: -2; hi :: -1; }
|
|
||||||
{
|
|
||||||
Expect(min_of(U32Enum) == U32Enum.lo);
|
|
||||||
Expect(min_of(S32Enum) == S32Enum.lo);
|
|
||||||
Expect(max_of(U32Enum) == U32Enum.hi);
|
|
||||||
Expect(max_of(S32Enum) == S32Enum.hi);
|
|
||||||
}
|
|
||||||
|
|
||||||
U64Enum :: enum u64 { lo :: -1; hi :: +1; }
|
|
||||||
S64Enum :: enum s64 { lo :: -2; hi :: -1; }
|
|
||||||
{
|
|
||||||
Expect(min_of(U64Enum) == U64Enum.lo);
|
|
||||||
Expect(min_of(S64Enum) == S64Enum.lo);
|
|
||||||
Expect(max_of(U64Enum) == U64Enum.hi);
|
|
||||||
Expect(max_of(S64Enum) == S64Enum.hi);
|
|
||||||
}
|
|
||||||
|
|
||||||
// @note(judah): just making sure this compiles
|
|
||||||
lo, hi := range_of(U64Enum);
|
|
||||||
Expect(lo == U64Enum.lo);
|
|
||||||
Expect(hi == U64Enum.hi);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
181
+internal/kv.jai
181
+internal/kv.jai
|
|
@ -1,181 +0,0 @@
|
||||||
// Dead simple key-value pair type (aka. hash table or hash map)
|
|
||||||
Record :: struct(Key: Type, Value: Type) {
|
|
||||||
allocator: Allocator;
|
|
||||||
slots: [..]Slot;
|
|
||||||
free_slots: [..]int;
|
|
||||||
count: int;
|
|
||||||
|
|
||||||
Slot :: struct {
|
|
||||||
hash: u32 = InvalidHash;
|
|
||||||
key: Key = ---;
|
|
||||||
value: Value = ---;
|
|
||||||
}
|
|
||||||
|
|
||||||
HashProc :: Murmur32;
|
|
||||||
InvalidHash :: (0x8000_dead).(u32); // @note(judah): I'm curious what values would hit this hash on accident
|
|
||||||
AllocatedItemsAtStart :: 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
// @note(judah): Not sure if I like these names, but I want to give them a try
|
|
||||||
Fetch :: Get;
|
|
||||||
Update :: Set;
|
|
||||||
Exists :: Has;
|
|
||||||
|
|
||||||
Get :: (r: *Record, key: r.Key) -> r.Value, bool {
|
|
||||||
slot, ok := FindSlot(r, GetHash(r, key));
|
|
||||||
if !ok
|
|
||||||
{ return zero_of(r.Value), false; }
|
|
||||||
|
|
||||||
return slot.value, true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Set :: (r: *Record, key: r.Key, value: r.Value) {
|
|
||||||
hash := GetHash(r, key);
|
|
||||||
slot, exists := FindSlot(r, hash);
|
|
||||||
if !exists {
|
|
||||||
slot = CreateOrReuseSlot(r);
|
|
||||||
slot.hash = hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
slot.key = key;
|
|
||||||
slot.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
Has :: (r: *Record, key: r.Key) -> bool {
|
|
||||||
_, exists := FindSlot(r, GetHash(r, key));
|
|
||||||
return exists;
|
|
||||||
}
|
|
||||||
|
|
||||||
Remove :: (r: *Record, key: r.Key) -> bool, r.Value {
|
|
||||||
slot, ok, idx := FindSlot(r, GetHash(r, key));
|
|
||||||
if !ok
|
|
||||||
{ return false, zero_of(r.Value); }
|
|
||||||
|
|
||||||
last_value := slot.value;
|
|
||||||
MarkSlotForReuse(r, idx);
|
|
||||||
|
|
||||||
return true, last_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
Reset :: (t: *Record) {
|
|
||||||
t.count = 0;
|
|
||||||
t.slots.count = 0;
|
|
||||||
t.free_slots.count = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
for_expansion :: (r: *Record, body: Code, flags: For_Flags) #expand {
|
|
||||||
#assert (flags & .POINTER == 0) "cannot iterate by pointer";
|
|
||||||
for <=(flags & .REVERSE == .REVERSE) slot: r.slots if slot.hash != r.InvalidHash {
|
|
||||||
`it := slot.value;
|
|
||||||
`it_index := slot.key;
|
|
||||||
#insert,scope(body)(break = break slot) body;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#scope_file;
|
|
||||||
|
|
||||||
GetHash :: inline (r: *Record, key: r.Key) -> u32 {
|
|
||||||
hash := r.HashProc(key);
|
|
||||||
Assert(hash != r.InvalidHash, "key collided with invalid hash");
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
FindSlot :: (r: *Record, hash: u32) -> *r.Slot, bool, int {
|
|
||||||
for * r.slots if it.hash == hash {
|
|
||||||
return it, true, it_index;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null, false, -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateOrReuseSlot :: (r: *Record) -> *r.Slot {
|
|
||||||
inline LazyInit(r);
|
|
||||||
|
|
||||||
if r.free_slots.count > 0 {
|
|
||||||
slot_idx := r.free_slots[r.free_slots.count - 1];
|
|
||||||
r.free_slots.count -= 1;
|
|
||||||
return *r.slots[slot_idx];
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.slots.allocated == 0 {
|
|
||||||
Resize(*r.slots, r.AllocatedItemsAtStart);
|
|
||||||
}
|
|
||||||
else if r.slots.count >= r.slots.allocated {
|
|
||||||
Resize(*r.slots, NextPowerOfTwo(r.slots.allocated));
|
|
||||||
}
|
|
||||||
|
|
||||||
slot := Append(*r.slots);
|
|
||||||
r.count = r.slots.count;
|
|
||||||
return slot;
|
|
||||||
}
|
|
||||||
|
|
||||||
MarkSlotForReuse :: (t: *Record, index: int) {
|
|
||||||
inline LazyInit(t);
|
|
||||||
|
|
||||||
t.count -= 1;
|
|
||||||
t.slots[index] = .{ hash = t.InvalidHash };
|
|
||||||
|
|
||||||
Append(*t.free_slots, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
LazyInit :: inline (t: *Record) {
|
|
||||||
TrySetAllocator(t);
|
|
||||||
TrySetAllocator(*t.slots);
|
|
||||||
TrySetAllocator(*t.free_slots);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#if RunTests #run {
|
|
||||||
Test("kv:basic operations", t => {
|
|
||||||
ITERATIONS :: 64;
|
|
||||||
|
|
||||||
values: Record(int, int);
|
|
||||||
for 0..ITERATIONS {
|
|
||||||
Set(*values, it, it * it);
|
|
||||||
}
|
|
||||||
|
|
||||||
for 0..ITERATIONS {
|
|
||||||
v, ok := Get(*values, it);
|
|
||||||
Expect(v == it * it);
|
|
||||||
}
|
|
||||||
|
|
||||||
for 0..ITERATIONS if it % 2 == 0 {
|
|
||||||
ok := Remove(*values, it);
|
|
||||||
Expect(ok);
|
|
||||||
}
|
|
||||||
|
|
||||||
for 0..ITERATIONS if it % 2 == 0 {
|
|
||||||
_, ok := Get(*values, it);
|
|
||||||
Expect(!ok);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Test("kv:free slots", t => {
|
|
||||||
values: Record(int, int);
|
|
||||||
|
|
||||||
Set(*values, 1, 100);
|
|
||||||
Set(*values, 2, 200);
|
|
||||||
Set(*values, 3, 300);
|
|
||||||
Expect(values.count == 3);
|
|
||||||
Expect(values.slots.allocated == values.AllocatedItemsAtStart);
|
|
||||||
|
|
||||||
// deleting something that doesn't exist should do nothing
|
|
||||||
ok := Remove(*values, 0);
|
|
||||||
Expect(!ok);
|
|
||||||
Expect(values.count == 3);
|
|
||||||
|
|
||||||
Remove(*values, 2);
|
|
||||||
Expect(values.count == 2);
|
|
||||||
});
|
|
||||||
|
|
||||||
Test("kv:iteration", t => {
|
|
||||||
values: Record(int, int);
|
|
||||||
|
|
||||||
for 0..10 Set(*values, it, it * it);
|
|
||||||
Expect(values.count == 11);
|
|
||||||
|
|
||||||
for v, k: values Expect(v == k * k);
|
|
||||||
for < v, k: values Expect(v == k * k);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
Kilobyte :: 1024;
|
|
||||||
Megabyte :: 1024 * Kilobyte;
|
|
||||||
Gigabyte :: 1024 * Megabyte;
|
|
||||||
|
|
||||||
DefaultAlign :: #run 2 * align_of(*void);
|
|
||||||
|
|
||||||
/// MemEqual checks the equality of two pieces of memory.
|
|
||||||
///
|
|
||||||
/// Note: MemEqual will panic if size_in_bytes is negative.
|
|
||||||
MemEqual :: (p1: *void, p2: *void, size_in_bytes: int) -> bool {
|
|
||||||
if size_in_bytes < 0
|
|
||||||
{ Panic("jc: size_in_bytes cannot be negative"); }
|
|
||||||
return memcmp(p1, p2, size_in_bytes) == 0; // Provided by Preload
|
|
||||||
}
|
|
||||||
|
|
||||||
/// MemCopy copies the memory of src to dst.
|
|
||||||
///
|
|
||||||
/// Note: MemCopy will panic if size_in_bytes is negative.
|
|
||||||
MemCopy :: (dst: *void, src: *void, size_in_bytes: int) {
|
|
||||||
if size_in_bytes < 0
|
|
||||||
{ Panic("jc: size_in_bytes cannot be negative"); }
|
|
||||||
memcpy(dst, src, size_in_bytes); // Provided by Preload
|
|
||||||
}
|
|
||||||
|
|
||||||
/// MemOverwrite overwites the memory of p with value.
|
|
||||||
///
|
|
||||||
/// Note: MemOverwrite will panic if size_in_bytes is negative.
|
|
||||||
MemOverwrite :: (p: *void, size_in_bytes: int, value: u8 = 0) {
|
|
||||||
if size_in_bytes < 0
|
|
||||||
{ Panic("jc: size_in_bytes cannot be negative"); }
|
|
||||||
memset(p, value, size_in_bytes); // Provided by preload
|
|
||||||
}
|
|
||||||
|
|
||||||
/// MemZero zeroes the memory of p.
|
|
||||||
///
|
|
||||||
/// Note: MemZero will panic if size_in_bytes is negative.
|
|
||||||
MemZero :: (p: *void, size_in_bytes: int) {
|
|
||||||
MemOverwrite(p, size_in_bytes, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// MemZero zeroes the memory of p.
|
|
||||||
///
|
|
||||||
/// Note: MemZero will not call the initializer for aggregate types,
|
|
||||||
/// so you may want MemReset instead.
|
|
||||||
MemZero :: (p: *$T) {
|
|
||||||
MemOverwrite(p, size_of(T), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// MemReset resets the memory of p, as if it was just instantiated.
|
|
||||||
///
|
|
||||||
/// Note: MemReset will call the initializer for aggregate types, so you
|
|
||||||
/// may want MemZero instead.
|
|
||||||
MemReset :: (p: *$T) {
|
|
||||||
initializer :: initializer_of(T);
|
|
||||||
#if initializer {
|
|
||||||
inline initializer(p);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
inline MemZero(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AlignUpwards :: (ptr: int, align: int = DefaultAlign) -> int {
|
|
||||||
Assert(PowerOfTwo(align), "alignment must be a power of two");
|
|
||||||
|
|
||||||
p := ptr;
|
|
||||||
mod := p & (align - 1);
|
|
||||||
if mod != 0 then p += align - mod;
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
PowerOfTwo :: (x: int) -> bool {
|
|
||||||
if x == 0 return false;
|
|
||||||
return x & (x - 1) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
NextPowerOfTwo :: (x: int) -> int #no_aoc {
|
|
||||||
Assert(PowerOfTwo(x), "value must be a power of two");
|
|
||||||
|
|
||||||
// Bit twiddling hacks next power of two
|
|
||||||
x |= x >> 1;
|
|
||||||
x |= x >> 2;
|
|
||||||
x |= x >> 4;
|
|
||||||
x |= x >> 8;
|
|
||||||
x |= x >> 16;
|
|
||||||
x |= x >> 32;
|
|
||||||
|
|
||||||
return x + 1;
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
#assert false "This module (jc/internal) is not expected to be imported directly. Import 'jc' instead.";
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
// Usage:
|
|
||||||
#if 0 {
|
|
||||||
#run,stallable {
|
|
||||||
Test("thing", t => {
|
|
||||||
Expect(some_condition, "error message: %", value);
|
|
||||||
});
|
|
||||||
|
|
||||||
Test("other thing", t => {
|
|
||||||
Expect(other_condition, "error message: %", value);
|
|
||||||
});
|
|
||||||
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test defines a new suite to be executed by the test runner.
|
|
||||||
///
|
|
||||||
/// See: Expect for more information.
|
|
||||||
///
|
|
||||||
/// Test("my_proc does what it should", t => {
|
|
||||||
/// value1 := my_proc(/* ... */);
|
|
||||||
/// Expect(value1 != 0, "my_proc returned zero!");
|
|
||||||
///
|
|
||||||
/// value2 := my_proc(/* .... */);
|
|
||||||
/// Expect(value2 > 0, "my_proc returned a negative number!");
|
|
||||||
/// });
|
|
||||||
///
|
|
||||||
Test :: (name: string, proc: (t: *void) -> (), loc := #caller_location) {
|
|
||||||
// @note(judah): incredibly dumb way to get nicer test runs
|
|
||||||
path := 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);
|
|
||||||
WriteString(": ", name, "... ");
|
|
||||||
|
|
||||||
t: TestRun;
|
|
||||||
proc(*t);
|
|
||||||
|
|
||||||
if t.failed {
|
|
||||||
WriteString("failed");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
WriteString("ok");
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteString(" (");
|
|
||||||
WriteNumber(t.total_ok);
|
|
||||||
WriteString("/");
|
|
||||||
WriteNumber(t.total_expects);
|
|
||||||
WriteString(")\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expect checks the given condition, failing the current test if it is false.
|
|
||||||
///
|
|
||||||
/// Note: Expect must be called within a test. Additionally, it expects the test
|
|
||||||
/// parameter to be called 't'.
|
|
||||||
Expect :: (cond: bool, message := "", args: ..Any, loc := #caller_location) #expand {
|
|
||||||
run := `t.(*TestRun);
|
|
||||||
run.total_expects += 1;
|
|
||||||
if cond {
|
|
||||||
run.total_ok += 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := "expectation failed";
|
|
||||||
if message.count != 0 {
|
|
||||||
msg = basic.tprint(message, ..args);
|
|
||||||
}
|
|
||||||
|
|
||||||
run.failed = true;
|
|
||||||
if #compile_time {
|
|
||||||
CompileError(msg, loc = loc);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
WriteStderrLocation(loc);
|
|
||||||
WriteStderrString(": ", msg, "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#scope_file
|
|
||||||
|
|
||||||
TestRun :: struct {
|
|
||||||
location: Source_Code_Location;
|
|
||||||
total_expects: s64;
|
|
||||||
total_ok: s64;
|
|
||||||
failed: bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
basic :: #import "Basic"; // @future
|
|
||||||
strings :: #import "String"; // @future
|
|
||||||
compiler :: #import "Compiler"; // @future
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
// @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 {
|
|
||||||
#if is_constant(T) {
|
|
||||||
info :: type_info(T);
|
|
||||||
if info.type == tag return true, info;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
info := T.(*Type_Info);
|
|
||||||
if info.type == tag return true, info;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, null;
|
|
||||||
}
|
|
||||||
|
|
||||||
type_is_integer :: ($$T: Type) -> bool, *Type_Info_Integer {
|
|
||||||
ok, info := check_type_tag(T, .INTEGER);
|
|
||||||
return ok, info.(*Type_Info_Integer);
|
|
||||||
}
|
|
||||||
|
|
||||||
type_is_float :: ($$T: Type) -> bool, *Type_Info_Float {
|
|
||||||
ok, info := check_type_tag(T, .FLOAT);
|
|
||||||
return ok, info.(*Type_Info_Float);
|
|
||||||
}
|
|
||||||
|
|
||||||
type_is_scalar :: (t: Type) -> bool {
|
|
||||||
return type_is_integer(t) || type_is_float(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
type_is_array :: ($$T: Type) -> bool, *Type_Info_Array {
|
|
||||||
ok, info := check_type_tag(T, .ARRAY);
|
|
||||||
return ok, info.(*Type_Info_Array);
|
|
||||||
}
|
|
||||||
|
|
||||||
type_is_struct :: ($$T: Type) -> bool, *Type_Info_Struct {
|
|
||||||
ok, info := check_type_tag(T, .STRUCT);
|
|
||||||
return ok, info.(*Type_Info_Struct);
|
|
||||||
}
|
|
||||||
|
|
||||||
type_is_enum :: ($$T: Type) -> bool, *Type_Info_Enum {
|
|
||||||
ok, info := check_type_tag(T, .ENUM);
|
|
||||||
return ok, info.(*Type_Info_Enum);
|
|
||||||
}
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,4 +1,3 @@
|
||||||
.build/
|
.build/
|
||||||
**.dSYM
|
**.dSYM
|
||||||
.DS_Store
|
.DS_Store
|
||||||
docs/
|
|
||||||
|
|
|
||||||
30
INBOX
30
INBOX
|
|
@ -2,6 +2,24 @@
|
||||||
Put any questions for me in here!
|
Put any questions for me in here!
|
||||||
|
|
||||||
[Jesse]
|
[Jesse]
|
||||||
|
05.22.25 Judah
|
||||||
|
Instead of going with a separate 'arch' module, I
|
||||||
|
think we can roll that into the future 'platform'
|
||||||
|
module instead. There's enough overlap that I
|
||||||
|
think it makes sense. Let me know what you think,
|
||||||
|
thanks!
|
||||||
|
05.23.25 Judah
|
||||||
|
I went ahead and created the platform module and
|
||||||
|
added arch-specific extension checking. Sadly
|
||||||
|
these happen at runtime because we're manually
|
||||||
|
checking isa extension flags. It's probably worth
|
||||||
|
it to add a 'SIMD' constant that we set for
|
||||||
|
targets that will almost always support SIMD
|
||||||
|
instructions; unsure for now.
|
||||||
|
05.31.25 Jesse
|
||||||
|
platform/arch merge sounds good, we can keep that going. No thoughts so far on that.
|
||||||
|
The caching is a good idea, I wonder if we should make them enum_flags.
|
||||||
|
I don't know if it will go over a u64 though.
|
||||||
08.21.25 Judah
|
08.21.25 Judah
|
||||||
I'm going to do a pretty big reorg of the repo. I'll be working in a separate branch for the time being so it shouldn't have an effect on you. When I merge this back into master I'll move your changes/modules over to the new structure.
|
I'm going to do a pretty big reorg of the repo. I'll be working in a separate branch for the time being so it shouldn't have an effect on you. When I merge this back into master I'll move your changes/modules over to the new structure.
|
||||||
|
|
||||||
|
|
@ -14,15 +32,3 @@
|
||||||
- base module (jc) imports most things you'd always want (memory allocation, strings, arrays, hash tables, macros, etc.)
|
- base module (jc) imports most things you'd always want (memory allocation, strings, arrays, hash tables, macros, etc.)
|
||||||
- x, ext, and math stay the same
|
- x, ext, and math stay the same
|
||||||
- other freestanding modules (say: encoding, math) are expected to be imported directly. None of this math/linalg or encoding/json nonsense
|
- other freestanding modules (say: encoding, math) are expected to be imported directly. None of this math/linalg or encoding/json nonsense
|
||||||
05.31.25 Jesse
|
|
||||||
platform/arch merge sounds good, we can keep that going. No thoughts so far on that.
|
|
||||||
The caching is a good idea, I wonder if we should make them enum_flags.
|
|
||||||
I don't know if it will go over a u64 though.
|
|
||||||
05.23.25 Judah
|
|
||||||
I went ahead and created the platform module and
|
|
||||||
added arch-specific extension checking. Sadly
|
|
||||||
these happen at runtime because we're manually
|
|
||||||
checking isa extension flags. It's probably worth
|
|
||||||
it to add a 'SIMD' constant that we set for
|
|
||||||
targets that will almost always support SIMD
|
|
||||||
instructions; unsure for now.
|
|
||||||
|
|
|
||||||
41
PLAN.Judah
41
PLAN.Judah
|
|
@ -1,41 +0,0 @@
|
||||||
Plan:
|
|
||||||
|
|
||||||
This is what I'm doing day-to-day.
|
|
||||||
|
|
||||||
/ day marker
|
|
||||||
+ completed that day
|
|
||||||
> completed on a later day
|
|
||||||
< decided against on a later day
|
|
||||||
: notes, todos, etc.
|
|
||||||
|
|
||||||
|
|
||||||
/ 09.06.25
|
|
||||||
|
|
||||||
+ fixed doc generator
|
|
||||||
+ encoding is now fmt
|
|
||||||
+ math tests pass after reorg
|
|
||||||
+ docs look nicer
|
|
||||||
|
|
||||||
: need to reimplement dynamic arrays
|
|
||||||
: stable arrays too?
|
|
||||||
|
|
||||||
|
|
||||||
/ 09.05.25
|
|
||||||
|
|
||||||
> cleaned up repo and finally merged everything in, sorry for breaking everything Jesse
|
|
||||||
|
|
||||||
+ got rid of jc/meta
|
|
||||||
+ had to change how tests work... again
|
|
||||||
+ updated encoding/*.jai
|
|
||||||
+ clearer Expect docs
|
|
||||||
+ cleaned up documentation
|
|
||||||
+ added temp_allocator to context when jc is imported. assumes all modules will import jc which should be a safe bet
|
|
||||||
+ internal/allocators.jai with PanicAllocator
|
|
||||||
+ updated my compiler version to 0.2.017, no changes needed
|
|
||||||
+ 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.
|
|
||||||
|
|
||||||
: 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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
51
README
51
README
|
|
@ -1,15 +1,46 @@
|
||||||
--------------
|
--
|
||||||
jc Jai Library
|
jc
|
||||||
--------------
|
--
|
||||||
|
|
||||||
For installation instructions and documentation, see:
|
# Direct installation
|
||||||
https://judahcaruso.com/jc
|
cd [jai install dir]/modules
|
||||||
|
git clone https://git.brut.systems/judah/jc.git
|
||||||
|
|
||||||
For local documentation, run 'jai _generate_docs.jai' then
|
# Indirect installation
|
||||||
navigate to the 'docs' subdirectory.
|
git clone https://git.brut.systems/judah/jc.git
|
||||||
|
|
||||||
|
ln -s "/path/to/jc" [jai install dir]/modules/jc # POSIX install
|
||||||
|
mklink /D [jai install dir]\modules\jc "C:\path\to\jc" # Windows install
|
||||||
|
|
||||||
License
|
#import "jc";
|
||||||
-------
|
#import "jc/[module]";
|
||||||
|
|
||||||
|
What
|
||||||
|
----
|
||||||
|
|
||||||
|
A set of modules for things I usually want in Jai, namely,
|
||||||
|
utilities, bindings, and experiments.
|
||||||
|
|
||||||
|
How
|
||||||
|
---
|
||||||
|
|
||||||
|
If you'd like to learn more about *what* a specific module
|
||||||
|
does, take a look at its 'module.jai' file.
|
||||||
|
|
||||||
|
If you'd like to contribute, read STYLEGUIDE.
|
||||||
|
|
||||||
|
Why
|
||||||
|
---
|
||||||
|
|
||||||
|
Because Jai is still in closed beta (as of May 15, 2025),
|
||||||
|
updates to the compiler and "standard library" will break
|
||||||
|
projects of mine; sometimes in a very annoying way.
|
||||||
|
|
||||||
|
jc was made to 1) give myself an escape hatch/skin-suit to
|
||||||
|
cause fewer breaking changes when updating the compiler,
|
||||||
|
and 2) put all of my non-project code in a single place
|
||||||
|
that's easier to manage.
|
||||||
|
|
||||||
|
While I do use many of the modules shipped with the
|
||||||
|
compiler, my goal is to eventually replace them.
|
||||||
|
|
||||||
Public Domain
|
|
||||||
|
|
|
||||||
105
STYLEGUIDE
105
STYLEGUIDE
|
|
@ -9,10 +9,9 @@ Getting Paid
|
||||||
------------
|
------------
|
||||||
|
|
||||||
To pick up tasks, find something in TODO and message me
|
To pick up tasks, find something in TODO and message me
|
||||||
your rate (bonus points if your initials are "JC"). If
|
your rate. If accepted, create a single commit moving it
|
||||||
accepted, create a single commit moving it from the 'UP
|
from the 'UP NEXT' section to your 'IN PROGRESS' section;
|
||||||
NEXT' section to your 'IN PROGRESS' section; the commit
|
the commit message should only say 'start [id]'.
|
||||||
message should only say 'start [id]'.
|
|
||||||
|
|
||||||
Once the work is done (use as many commits as you'd like),
|
Once the work is done (use as many commits as you'd like),
|
||||||
create another commit moving the task from your 'IN
|
create another commit moving the task from your 'IN
|
||||||
|
|
@ -42,8 +41,8 @@ Communication
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
If direction is needed, create a single commit with an
|
If direction is needed, create a single commit with an
|
||||||
entry in 'INBOX' at the top of that person's section; the
|
entry in 'INBOX' under that person's section; the commit
|
||||||
commit message should roughly say 'message for [name]'.
|
message should roughly say 'message for [name]'.
|
||||||
|
|
||||||
If an immediate response is needed, opt for direct
|
If an immediate response is needed, opt for direct
|
||||||
messages instead.
|
messages instead.
|
||||||
|
|
@ -51,14 +50,14 @@ messages instead.
|
||||||
Use this as a base plate to write new messages:
|
Use this as a base plate to write new messages:
|
||||||
|
|
||||||
[ToName]
|
[ToName]
|
||||||
05.25.25 ToName // This is a response
|
|
||||||
Excepteur sint occaecat cupidatat non proident,
|
|
||||||
sunt in culpa qui officia deserunt mollit anim
|
|
||||||
id est laborum.
|
|
||||||
05.22.25 FromName // This is a question
|
05.22.25 FromName // This is a question
|
||||||
Lorem ipsum dolor sit amet, consectetur
|
Lorem ipsum dolor sit amet, consectetur
|
||||||
adipisicing elit, sed do eiusmod tempor
|
adipisicing elit, sed do eiusmod tempor
|
||||||
incididunt ut labore et dolore magna aliqua.
|
incididunt ut labore et dolore magna aliqua.
|
||||||
|
05.22.25 ToName // This is a response
|
||||||
|
Excepteur sint occaecat cupidatat non proident,
|
||||||
|
sunt in culpa qui officia deserunt mollit anim
|
||||||
|
id est laborum.
|
||||||
|
|
||||||
You should check your inbox *every time* you pull.
|
You should check your inbox *every time* you pull.
|
||||||
|
|
||||||
|
|
@ -79,21 +78,14 @@ Things I actually care about:
|
||||||
premise that code is self-documenting. Take a step
|
premise that code is self-documenting. Take a step
|
||||||
back and write a comment (with your name) that
|
back and write a comment (with your name) that
|
||||||
explains *why* you're doing *what* you're doing.
|
explains *why* you're doing *what* you're doing.
|
||||||
Sticking to this format makes my life easier:
|
Please try to stick to this format:
|
||||||
@note(name), @todo, @temp, etc.
|
@note, @todo, @temp, @allocates, @leak
|
||||||
|
|
||||||
- Descriptive names. In general, it's fine to use short
|
- Descriptive names. In general, it's fine to use short
|
||||||
names for local variables. However, you should
|
names for local variables. However, you should
|
||||||
almost always opt for longer, more descriptive names
|
almost always opt for longer, more descriptive names
|
||||||
in every other context.
|
in every other context.
|
||||||
|
|
||||||
- Documentation will get auto generated if the comments
|
|
||||||
start with '///'. General format is: '/// Foo ...'
|
|
||||||
where 'Foo' is a procedure, type, whatever. To get
|
|
||||||
examples to show up, indent them more than the other
|
|
||||||
text. To prevent something from being documented,
|
|
||||||
attach '@jc.nodocs' to it.
|
|
||||||
|
|
||||||
|
|
||||||
Imports
|
Imports
|
||||||
-------
|
-------
|
||||||
|
|
@ -103,24 +95,48 @@ namespace them. The eventual goal of this repo is to be a
|
||||||
standalone library that doesn't rely on compiler-shipped
|
standalone library that doesn't rely on compiler-shipped
|
||||||
modules (unless absolutely required).
|
modules (unless absolutely required).
|
||||||
|
|
||||||
basic :: #import "Basic"; // @future
|
basic :: #import "Basic";
|
||||||
|
|
||||||
When importing 'jc' modules, ALWAYS namespace them and use
|
When importing 'jc' modules, ALWAYS namespace them and use
|
||||||
absolute import paths. '#import,file' does not function
|
absolute import paths. '#import,file' does not function
|
||||||
the same as '#import' and causes issues when mixed
|
the same as '#import' and causes issues when mixed
|
||||||
with '#load'.
|
with '#load'. '_run_all_tests.jai' is an exception to
|
||||||
|
this rule.
|
||||||
|
|
||||||
#import "jc"; // the base module can be unnamed
|
|
||||||
math :: #import "jc/math";
|
math :: #import "jc/math";
|
||||||
|
|
||||||
|
|
||||||
|
Modules
|
||||||
|
-------
|
||||||
|
|
||||||
|
Modules should *generally* have a small scope and not be
|
||||||
|
nested unless it allows better organization. Files within
|
||||||
|
modules are not intended to be imported directly; it is
|
||||||
|
the job of 'module.jai' to '#load' and '#scope_export'
|
||||||
|
module files.
|
||||||
|
|
||||||
|
When authoring a new module, use this as a base plate:
|
||||||
|
|
||||||
|
// Within module_name/module.jai
|
||||||
|
#module_parameters(RUN_TESTS := false); // Should be the last parameter if others are required
|
||||||
|
|
||||||
|
#load "file_a.jai";
|
||||||
|
#load "file_b.jai";
|
||||||
|
|
||||||
|
#scope_file;
|
||||||
|
|
||||||
|
#if RUN_TESTS {
|
||||||
|
test :: #import "jc/test";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Memory Management
|
Memory Management
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
If a type needs dynamically allocated memory to function,
|
If a custom type needs dynamically allocated memory to
|
||||||
it should always assume the memory came from an arena
|
function, it should always assume the memory came from an
|
||||||
allocator. This means it is the responsibility of the
|
arena allocator. This means it is the responsibility of
|
||||||
arena to free the memory, not the data type.
|
the arena to free the memory, not the custom data type.
|
||||||
|
|
||||||
In other words:
|
In other words:
|
||||||
|
|
||||||
|
|
@ -128,7 +144,39 @@ Do *not* add procedures that 'free' or 'delete' the memory
|
||||||
allocated by the data type.
|
allocated by the data type.
|
||||||
|
|
||||||
Instead, add procedures that 'reset' the data type,
|
Instead, add procedures that 'reset' the data type,
|
||||||
allowing its memory to be reused.
|
allowing its memory to be reused. For examples, see
|
||||||
|
the 'reset' procedures in 'jc/kv' or 'jc/array'.
|
||||||
|
|
||||||
|
|
||||||
|
OS-Specific Code
|
||||||
|
----------------
|
||||||
|
|
||||||
|
When writing code for a specific operating system, use a
|
||||||
|
switch statement over multiple files.
|
||||||
|
|
||||||
|
// Top-level scope of file:
|
||||||
|
#if OS == {
|
||||||
|
case .WINDOWS;
|
||||||
|
case .MACOS;
|
||||||
|
case .LINUX;
|
||||||
|
case; #assert false, "unimplemented platform";
|
||||||
|
}
|
||||||
|
|
||||||
|
If multiple files are required, use these file suffixes
|
||||||
|
and conditionally '#load' them based on 'OS'.
|
||||||
|
|
||||||
|
Windows: '_win.jai'
|
||||||
|
Mac: '_mac.jai'
|
||||||
|
Linux: '_linux.jai'
|
||||||
|
WASM: '_wasm.jai'
|
||||||
|
|
||||||
|
|
||||||
|
Architecture-Specific Code
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
When writing code for a specific architecture, use
|
||||||
|
the 'jc/arch' module. NEVER create a new file unless
|
||||||
|
absolutely needed.
|
||||||
|
|
||||||
|
|
||||||
Bindings
|
Bindings
|
||||||
|
|
@ -138,10 +186,11 @@ Binding modules should default to static linking and take
|
||||||
an optional '#module_parameter' to link dynamically.
|
an optional '#module_parameter' to link dynamically.
|
||||||
Libraries should be pinned to a specific version, and all
|
Libraries should be pinned to a specific version, and all
|
||||||
binaries (.dll, .dylib, etc.) *must* be checked into
|
binaries (.dll, .dylib, etc.) *must* be checked into
|
||||||
source control. If possible, use a release build of the
|
source control. If possible, use the release build of the
|
||||||
library that includes debug information.
|
library that includes debug information.
|
||||||
|
|
||||||
Bindings should stay as close as possible to the original
|
Bindings should stay as close as possible to the original
|
||||||
library. To jai-ify the bindings, create a submodule
|
library. To jai-ify the bindings, create a submodule
|
||||||
called 'wrapper' that import and wraps the api.
|
called 'wrapper' that import and wraps the api.
|
||||||
|
|
||||||
|
See: 'thirdparty/raylib' for example bindings.
|
||||||
|
|
|
||||||
1382
_generate_docs.jai
1382
_generate_docs.jai
File diff suppressed because it is too large
Load diff
85
_make_module.jai
Normal file
85
_make_module.jai
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
#run {
|
||||||
|
set_build_options_dc(.{ do_output = false });
|
||||||
|
args := get_build_options().compile_time_command_line;
|
||||||
|
|
||||||
|
usage :: () {
|
||||||
|
print("creates the template for a module or submodule\n\n");
|
||||||
|
print("usage: jai _make_module.jai - (module|submodule) [path]\n\n");
|
||||||
|
print("options:\n");
|
||||||
|
print("\tpath: a simple module path without an extension (example: 'foo' or 'foo/bar')\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.count < 2 {
|
||||||
|
usage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
kind := trim(args[0]);
|
||||||
|
if kind.count == 0 {
|
||||||
|
usage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
module_path := trim(args[1]);
|
||||||
|
if module_path.count == 0 {
|
||||||
|
usage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if kind == {
|
||||||
|
case "module";
|
||||||
|
assert(make_directory_if_it_does_not_exist(module_path), "could not create module directory: '%'", module_path);
|
||||||
|
entry_file := tprint("%/module.jai", module_path);
|
||||||
|
assert(write_entire_file(entry_file, tprint(MODULE_STRING, module_path)), "could not create %", entry_file);
|
||||||
|
|
||||||
|
case "submodule";
|
||||||
|
entry_file := tprint("%.jai", module_path);
|
||||||
|
assert(write_entire_file(entry_file, tprint(SUBMODULE_STRING, module_path)), "could not create %", entry_file);
|
||||||
|
|
||||||
|
case;
|
||||||
|
usage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MODULE_STRING :: #string END
|
||||||
|
// %1 is a module that does things.
|
||||||
|
//
|
||||||
|
#module_parameters(RUN_TESTS := false, IMPORTED_INTERNALLY := false);
|
||||||
|
|
||||||
|
#scope_export;
|
||||||
|
|
||||||
|
#if !IMPORTED_INTERNALLY {
|
||||||
|
// #import "jc/%1/submodule"(RUN_TESTS);
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
END;
|
||||||
|
|
||||||
|
SUBMODULE_STRING :: #string END
|
||||||
|
// %1 is a module that does stuff.
|
||||||
|
//
|
||||||
|
#module_parameters(RUN_TESTS := false);
|
||||||
|
|
||||||
|
#scope_export;
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
#scope_file;
|
||||||
|
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
// TESTS
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
|
||||||
|
#if RUN_TESTS #run {
|
||||||
|
test :: #import "jc/meta/test";
|
||||||
|
|
||||||
|
test.run("%1:works", t => {
|
||||||
|
test.expect(t, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
END;
|
||||||
|
|
||||||
|
#import "File";
|
||||||
|
#import "Basic";
|
||||||
|
#import "String";
|
||||||
|
#import "Compiler";
|
||||||
|
|
@ -2,9 +2,14 @@
|
||||||
compiler :: #import "Compiler";
|
compiler :: #import "Compiler";
|
||||||
compiler.set_build_options_dc(.{ do_output = false });
|
compiler.set_build_options_dc(.{ do_output = false });
|
||||||
|
|
||||||
_ :: #import "jc"(true);
|
#import "jc/array"(RUN_TESTS = true);
|
||||||
_ :: #import "jc/math"(RUN_TESTS = true);
|
#import "jc/encoding"(RUN_TESTS = true);
|
||||||
_ :: #import "jc/fmt/base64"(true);
|
#import "jc/hash"(RUN_TESTS = true);
|
||||||
|
#import "jc/memory"(RUN_TESTS = true);
|
||||||
|
#import "jc/meta"(RUN_TESTS = true);
|
||||||
|
#import "jc/platform"(RUN_TESTS = true);
|
||||||
|
|
||||||
_ :: #import "jc/x/json"(true);
|
rmath :: #import "jc/math"(.radians, RUN_TESTS = true);
|
||||||
|
dmath :: #import "jc/math"(.degrees, RUN_TESTS = true);
|
||||||
|
tmath :: #import "jc/math"(.turns, RUN_TESTS = true);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
58
array/bytes.jai
Normal file
58
array/bytes.jai
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
#module_parameters(RUN_TESTS := false);
|
||||||
|
|
||||||
|
to_string :: (c: cstring, count := 1) -> string #expand {
|
||||||
|
return string.{ data = c, count = count };
|
||||||
|
}
|
||||||
|
|
||||||
|
Index_Flag :: enum_flags {
|
||||||
|
from_end;
|
||||||
|
index_plus_one;
|
||||||
|
count_on_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
find_index :: (b: []byte, c: byte, $mode: Index_Flag) -> bool, int {
|
||||||
|
count := 0;
|
||||||
|
#if mode & .from_end {
|
||||||
|
i := b.count - 1;
|
||||||
|
while i >= 0 {
|
||||||
|
if b[i] == c {
|
||||||
|
return true, #ifx mode & .index_plus_one i + 1 else i;
|
||||||
|
}
|
||||||
|
|
||||||
|
i -= 1;
|
||||||
|
|
||||||
|
#if mode & .count_on_fail {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for b if it == c {
|
||||||
|
return true, #ifx mode & .index_plus_one then it_index + 1 else it_index;
|
||||||
|
}
|
||||||
|
else #if mode & .count_on_fail {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, #ifx mode & .count_on_fail then count else -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
find_index :: inline (s: string, c: byte, $mode: Index_Flag) -> bool, int {
|
||||||
|
ok, idx := find_index(s.([]byte), c, mode);
|
||||||
|
return ok, idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
#scope_file;
|
||||||
|
|
||||||
|
#import "jc";
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
// TESTS
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
|
||||||
|
#if RUN_TESTS #run {
|
||||||
|
test :: #import "jc/meta/test";
|
||||||
|
}
|
||||||
|
|
||||||
116
array/dynamic.jai
Normal file
116
array/dynamic.jai
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
#module_parameters(RUN_TESTS := false);
|
||||||
|
|
||||||
|
// @todo(judah): replace array_add
|
||||||
|
|
||||||
|
append :: inline (arr: *[..]$T, value: T) -> *T {
|
||||||
|
mem.lazy_set_allocator(arr);
|
||||||
|
|
||||||
|
ptr := basic.array_add(arr,, allocator = arr.allocator);
|
||||||
|
ptr.* = value;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
append :: inline (arr: *[..]$T, values: ..T) -> *T {
|
||||||
|
mem.lazy_set_allocator(arr);
|
||||||
|
|
||||||
|
count := arr.count;
|
||||||
|
basic.array_add(arr, ..values,, allocator = arr.allocator);
|
||||||
|
return *arr.data[count];
|
||||||
|
}
|
||||||
|
|
||||||
|
append :: inline (arr: *[..]$T) -> *T {
|
||||||
|
mem.lazy_set_allocator(arr);
|
||||||
|
|
||||||
|
return basic.array_add(arr,, allocator = arr.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
resize :: inline (arr: *[..]$T, new_count: int) {
|
||||||
|
mem.lazy_set_allocator(arr);
|
||||||
|
|
||||||
|
if new_count <= arr.allocated return;
|
||||||
|
basic.array_reserve(arr, new_count,, allocator = arr.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_ordered :: inline (arr: *[..]$T, index: int, loc := #caller_location) #no_abc {
|
||||||
|
meta.check_bounds(index, arr.count, loc = loc);
|
||||||
|
memcpy(arr.data + index, arr.data + index + 1, (arr.count - index - 1) * size_of(T));
|
||||||
|
arr.count -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_unordered :: inline (arr: *[..]$T, index: int, loc := #caller_location) #no_abc {
|
||||||
|
meta.check_bounds(index, arr.count, loc = loc);
|
||||||
|
arr.data[index] = arr.data[arr.count - 1];
|
||||||
|
arr.count -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset :: inline (arr: *[..]$T) {
|
||||||
|
arr.count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
find :: (a: [..]$T, $predicate: (T) -> bool) -> T, bool, int {
|
||||||
|
for a if inline predicate(it) return it, true, it_index;
|
||||||
|
return mem.undefined_of(T), false, -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
find_pointer :: (a: *[..]$T, $predicate: (T) -> bool) -> *T, bool, int {
|
||||||
|
for * a if inline predicate(it.*) return it, true, it_index;
|
||||||
|
return null, false, -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#scope_file;
|
||||||
|
|
||||||
|
mem :: #import "jc/memory";
|
||||||
|
meta :: #import "jc/meta";
|
||||||
|
|
||||||
|
basic :: #import "Basic"; // @future
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
// TESTS
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
|
||||||
|
#if RUN_TESTS #run {
|
||||||
|
test :: #import "jc/meta/test";
|
||||||
|
|
||||||
|
test.run("remove_ordered", t => {
|
||||||
|
a: [..]int;
|
||||||
|
append(*a, 10, 20, 30);
|
||||||
|
|
||||||
|
remove_ordered(*a, 1);
|
||||||
|
test.expect(t, a.count == 2);
|
||||||
|
test.expect(t, a.data[0] == 10);
|
||||||
|
test.expect(t, a.data[1] == 30);
|
||||||
|
|
||||||
|
remove_ordered(*a, 0);
|
||||||
|
test.expect(t, a.count == 1);
|
||||||
|
test.expect(t, a.data[0] == 30);
|
||||||
|
|
||||||
|
remove_ordered(*a, 0);
|
||||||
|
test.expect(t, a.count == 0);
|
||||||
|
|
||||||
|
append(*a, 10);
|
||||||
|
test.expect(t, a.count == 1);
|
||||||
|
test.expect(t, a.data[0] == 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.run("remove_unordered", t => {
|
||||||
|
a: [..]int;
|
||||||
|
append(*a, 10, 20, 30);
|
||||||
|
|
||||||
|
remove_unordered(*a, 1);
|
||||||
|
test.expect(t, a.count == 2);
|
||||||
|
test.expect(t, a.data[0] == 10);
|
||||||
|
test.expect(t, a.data[1] == 30);
|
||||||
|
|
||||||
|
remove_unordered(*a, 0);
|
||||||
|
test.expect(t, a.count == 1);
|
||||||
|
test.expect(t, a.data[0] == 30);
|
||||||
|
|
||||||
|
remove_unordered(*a, 0);
|
||||||
|
test.expect(t, a.count == 0);
|
||||||
|
|
||||||
|
append(*a, 10);
|
||||||
|
test.expect(t, a.count == 1);
|
||||||
|
test.expect(t, a.data[0] == 10);
|
||||||
|
});
|
||||||
|
}
|
||||||
13
array/module.jai
Normal file
13
array/module.jai
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#module_parameters(RUN_TESTS := false, WITH_SUBMODULES := true);
|
||||||
|
|
||||||
|
#scope_export;
|
||||||
|
|
||||||
|
#if WITH_SUBMODULES {
|
||||||
|
using #import "jc/array/bytes"(RUN_TESTS);
|
||||||
|
using #import "jc/array/dynamic"(RUN_TESTS);
|
||||||
|
using #import "jc/array/stable"(RUN_TESTS);
|
||||||
|
using #import "jc/array/static"(RUN_TESTS);
|
||||||
|
using #import "jc/array/xar"(RUN_TESTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
#scope_module;
|
||||||
194
array/stable.jai
Normal file
194
array/stable.jai
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
#module_parameters(RUN_TESTS := false);
|
||||||
|
|
||||||
|
// A dynamic array whose values will never move in memory.
|
||||||
|
//
|
||||||
|
// This means it is safe to take a pointer to a value within the array
|
||||||
|
// while continuing to append to it.
|
||||||
|
Stable_Array :: struct(T: Type, items_per_chunk := 32) {
|
||||||
|
allocator: Allocator;
|
||||||
|
chunks: [..]*Chunk;
|
||||||
|
count: int;
|
||||||
|
|
||||||
|
Chunk :: Static_Array(items_per_chunk, T);
|
||||||
|
}
|
||||||
|
|
||||||
|
append :: (a: *Stable_Array) -> *a.T {
|
||||||
|
chunk := find_or_create_chunk(a, 1);
|
||||||
|
a.count += 1;
|
||||||
|
return append(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
append :: (a: *Stable_Array, value: a.T) -> *a.T {
|
||||||
|
chunk := find_or_create_chunk(a, 1);
|
||||||
|
item := append(chunk, value);
|
||||||
|
a.count += 1;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
append :: (a: *Stable_Array, values: ..a.T) -> *a.T {
|
||||||
|
// @todo(judah): this should look for chunks where can just copy values directly
|
||||||
|
// rather than calling append for each one.
|
||||||
|
|
||||||
|
first: *a.T;
|
||||||
|
for values {
|
||||||
|
if first == null {
|
||||||
|
first = inline append(a, it);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
inline append(a, it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset :: (a: *Stable_Array) {
|
||||||
|
for a.chunks it.count = 0;
|
||||||
|
a.count = 0;
|
||||||
|
a.chunks.count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
find :: (a: Stable_Array, $predicate: (a.T) -> bool) -> a.T, bool, int {
|
||||||
|
for a if inline predicate(it) return it, true, it_index;
|
||||||
|
return mem.undefined_of(a.T), false, -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
find_pointer :: (a: *Stable_Array, $predicate: (a.T) -> bool) -> *a.T, bool, int {
|
||||||
|
for * a if inline predicate(it.*) return it, true, it_index;
|
||||||
|
return null, false, -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator [] :: (a: Stable_Array, index: int, loc := #caller_location) -> a.T #no_abc {
|
||||||
|
cidx := index / a.items_per_chunk;
|
||||||
|
iidx := index % a.items_per_chunk;
|
||||||
|
meta.check_bounds(cidx, a.chunks.count, loc = loc);
|
||||||
|
meta.check_bounds(iidx, a.chunks[cidx].count, loc = loc);
|
||||||
|
return a.chunks[cidx].items[iidx];
|
||||||
|
}
|
||||||
|
|
||||||
|
operator *[] :: (a: *Stable_Array, index: int, loc := #caller_location) -> *a.T #no_abc {
|
||||||
|
cidx := index / a.items_per_chunk;
|
||||||
|
iidx := index % a.items_per_chunk;
|
||||||
|
meta.check_bounds(cidx, a.chunks.count, loc = loc);
|
||||||
|
meta.check_bounds(iidx, a.chunks[cidx].count, loc = loc);
|
||||||
|
return *a.chunks[cidx].items[iidx];
|
||||||
|
}
|
||||||
|
|
||||||
|
operator []= :: (a: *Stable_Array, index: int, value: a.T, loc := #caller_location) #no_abc {
|
||||||
|
cidx := index / a.items_per_chunk;
|
||||||
|
iidx := index % a.items_per_chunk;
|
||||||
|
meta.check_bounds(cidx, a.chunks.count, loc = loc);
|
||||||
|
meta.check_bounds(iidx, a.chunks[cidx].count, loc = loc);
|
||||||
|
a.chunks[cidx].items[iidx] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
for_expansion :: (a: Stable_Array, body: Code, flags: For_Flags) #expand {
|
||||||
|
for #v2 <=(flags & .REVERSE == .REVERSE) i: 0..a.count - 1 {
|
||||||
|
`it_index := i;
|
||||||
|
#if flags & .POINTER == .POINTER {
|
||||||
|
`it := *a[i];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
`it := a[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
#insert,scope(body) body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#scope_file;
|
||||||
|
|
||||||
|
find_or_create_chunk :: (a: *Stable_Array, amount: int) -> *a.Chunk {
|
||||||
|
if a.chunks.count == 0 {
|
||||||
|
return create_chunk(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
last := a.chunks[a.chunks.count - 1];
|
||||||
|
if amount > a.items_per_chunk - last.count {
|
||||||
|
last = create_chunk(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
|
||||||
|
create_chunk :: (a: *Stable_Array) -> *a.Chunk {
|
||||||
|
mem.lazy_set_allocator(a);
|
||||||
|
mem.lazy_set_allocator(*a.chunks);
|
||||||
|
|
||||||
|
chunk := mem.request_memory(a.Chunk,, allocator = a.allocator);
|
||||||
|
append(*a.chunks, chunk);
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
#import "jc/array";
|
||||||
|
|
||||||
|
mem :: #import "jc/memory";
|
||||||
|
meta :: #import "jc/meta";
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
// TESTS
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
|
||||||
|
#if RUN_TESTS #run {
|
||||||
|
test :: #import "jc/meta/test";
|
||||||
|
|
||||||
|
test.run("basic operations", t => {
|
||||||
|
a: Stable_Array(int, 4);
|
||||||
|
|
||||||
|
append(*a, 10, 20, 30, 40);
|
||||||
|
test.expect(t, a.count == 4);
|
||||||
|
test.expect(t, a.chunks.count == 1, "chunk count was %", a.chunks.count);
|
||||||
|
|
||||||
|
append(*a, 50);
|
||||||
|
test.expect(t, a.count == 5);
|
||||||
|
test.expect(t, a.chunks.count == 2, "chunk count was %", a.chunks.count);
|
||||||
|
|
||||||
|
append(*a, 60, 70, 80, 90, 100, 110, 120);
|
||||||
|
test.expect(t, a.count == 12);
|
||||||
|
test.expect(t, a.chunks.count == 3, "chunk count was %", a.chunks.count);
|
||||||
|
|
||||||
|
for a {
|
||||||
|
test.expect(t, it == (it_index + 1) * 10, "% was %", it, (it_index + 1) * 10);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test.run("iteration", t => {
|
||||||
|
a: Stable_Array(int);
|
||||||
|
append(*a, 10, 20, 30, 40);
|
||||||
|
|
||||||
|
last := 999;
|
||||||
|
for < a {
|
||||||
|
test.expect(t, it == (it_index + 1) * 10);
|
||||||
|
test.expect(t, it < last);
|
||||||
|
|
||||||
|
last = it;
|
||||||
|
}
|
||||||
|
|
||||||
|
for * a it.* = 1;
|
||||||
|
for a test.expect(t, it == 1);
|
||||||
|
|
||||||
|
ptr, ok, idx := find_pointer(*a, v => v == 1);
|
||||||
|
test.expect(t, ok);
|
||||||
|
test.expect(t, idx == 0);
|
||||||
|
test.expect(t, ptr == *a[0]);
|
||||||
|
|
||||||
|
a[a.count - 1] = -1;
|
||||||
|
|
||||||
|
_, ok, idx = find(a, v => v == -1);
|
||||||
|
test.expect(t, ok);
|
||||||
|
test.expect(t, idx == a.count - 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.run("stability", t => {
|
||||||
|
a: Stable_Array(int, 1);
|
||||||
|
|
||||||
|
first := append(*a, 10);
|
||||||
|
addr := first.(u64);
|
||||||
|
for 0..10 append(*a, it * 10);
|
||||||
|
|
||||||
|
test.expect(t, first.(u64) == addr);
|
||||||
|
test.expect(t, first.* == 10);
|
||||||
|
});
|
||||||
|
}
|
||||||
171
array/static.jai
Normal file
171
array/static.jai
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
#module_parameters(RUN_TESTS := false);
|
||||||
|
|
||||||
|
Static_Array :: struct(capacity: int, T: Type) {
|
||||||
|
items: [capacity]T;
|
||||||
|
count: int;
|
||||||
|
|
||||||
|
Default :: #run mem.default_of(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
append :: inline (a: *Static_Array, item: a.T) -> *a.T #no_abc {
|
||||||
|
ensure_array_has_room(a, 1);
|
||||||
|
ptr := *a.items[a.count];
|
||||||
|
ptr.* = item;
|
||||||
|
a.count += 1;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
append :: inline (a: *Static_Array) -> *a.T #no_abc {
|
||||||
|
ensure_array_has_room(a, 1);
|
||||||
|
ptr := *a.items[a.count];
|
||||||
|
a.count += 1;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
append :: inline (a: *Static_Array, items: ..a.T) -> *a.T #no_abc {
|
||||||
|
ensure_array_has_room(a, items.count);
|
||||||
|
first := *a.items[a.count];
|
||||||
|
memcpy(a.items.data + a.count, items.data, items.count * size_of(a.T));
|
||||||
|
a.count += items.count;
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_ordered :: inline (a: *Static_Array, index: int, loc := #caller_location) #no_abc {
|
||||||
|
meta.check_bounds(index, a.count, loc = loc);
|
||||||
|
memcpy(a.items.data + index, a.items.data + index + 1, (a.count - index - 1) * size_of(a.T));
|
||||||
|
a.count -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_unordered :: inline (a: *Static_Array, index: int, loc := #caller_location) #no_abc {
|
||||||
|
meta.check_bounds(index, a.count, loc = loc);
|
||||||
|
a.items[index] = a.items[a.count - 1];
|
||||||
|
a.count -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset :: inline (a: *Static_Array) #no_abc {
|
||||||
|
for 0..a.count - 1 a.items[it] = a.Default;
|
||||||
|
a.count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
find :: (a: Static_Array, $predicate: (a.T) -> bool) -> a.T, bool, int {
|
||||||
|
for a if inline predicate(it) return it, true, it_index;
|
||||||
|
return mem.undefined_of(a.T), false, -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
find_pointer :: (a: *Static_Array, $predicate: (a.T) -> bool) -> *a.T, bool, int {
|
||||||
|
for * a if inline predicate(it.*) return it, true, it_index;
|
||||||
|
return null, false, -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator [] :: inline (a: Static_Array, $$index: int, loc := #caller_location) -> a.T #no_abc {
|
||||||
|
meta.check_bounds(index, a.count, loc = loc);
|
||||||
|
return a.items[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
operator *[] :: inline (a: *Static_Array, $$index: int, loc := #caller_location) -> *a.T #no_abc {
|
||||||
|
meta.check_bounds(index, a.count, loc = loc);
|
||||||
|
return *a.items[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
operator []= :: inline (a: *Static_Array, $$index: int, value: a.T, loc := #caller_location) #no_abc {
|
||||||
|
meta.check_bounds(index, a.count, loc = loc);
|
||||||
|
a.items[index] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
for_expansion :: (a: *Static_Array, body: Code, flags: For_Flags) #expand {
|
||||||
|
view := make_view(a);
|
||||||
|
for *=(flags & .POINTER == .POINTER) <=(flags & .REVERSE == .REVERSE) `it, `it_index: view {
|
||||||
|
#insert,scope(body)(break = break it) body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
make_view :: (a: Static_Array) -> []a.T {
|
||||||
|
view := a.items.([]a.T);
|
||||||
|
view.count = a.count;
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
make_dynamic :: (a: *Static_Array) -> [..]a.T {
|
||||||
|
res: [..]a.T;
|
||||||
|
res.count = a.count;
|
||||||
|
res.allocated = a.count;
|
||||||
|
res.data = basic.alloc(a.count * size_of(a.T));
|
||||||
|
memcpy(res.data, a.items.data, a.count * size_of(a.T));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#scope_file;
|
||||||
|
|
||||||
|
ensure_array_has_room :: (array: *Static_Array, count: int, loc := #caller_location) #expand {
|
||||||
|
basic.assert(array.count + count <= array.capacity, "attempt to add too many elements! want: %, max: %", array.count + count, array.capacity, loc = loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
mem :: #import "jc/memory";
|
||||||
|
meta :: #import "jc/meta";
|
||||||
|
|
||||||
|
basic :: #import "Basic"; // @future
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
// TESTS
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
|
||||||
|
#if RUN_TESTS #run {
|
||||||
|
test :: #import "jc/meta/test";
|
||||||
|
|
||||||
|
test.run("basic operations", (t) => {
|
||||||
|
a: Static_Array(10, int);
|
||||||
|
|
||||||
|
test.expect(t, a.count == 0);
|
||||||
|
test.expect(t, a.capacity == 10);
|
||||||
|
|
||||||
|
append(*a, 10, 20, 30);
|
||||||
|
test.expect(t, a.count == 3, "count: %", a.count);
|
||||||
|
|
||||||
|
_, ok := find(a, v => v == 10);
|
||||||
|
test.expect(t, ok);
|
||||||
|
|
||||||
|
_, ok = find_pointer(*a, v => v == 20);
|
||||||
|
test.expect(t, ok);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.run("remove_ordered", (t) => {
|
||||||
|
a: Static_Array(10, int);
|
||||||
|
append(*a, 10, 20, 30);
|
||||||
|
|
||||||
|
remove_ordered(*a, 1);
|
||||||
|
test.expect(t, a.count == 2);
|
||||||
|
test.expect(t, a.items[0] == 10);
|
||||||
|
|
||||||
|
remove_ordered(*a, 0);
|
||||||
|
test.expect(t, a.count == 1);
|
||||||
|
test.expect(t, a.items[0] == 30);
|
||||||
|
|
||||||
|
remove_ordered(*a, 0);
|
||||||
|
test.expect(t, a.count == 0);
|
||||||
|
|
||||||
|
append(*a, 10);
|
||||||
|
test.expect(t, a.count == 1);
|
||||||
|
test.expect(t, a.items[0] == 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.run("remove_unordered", (t) => {
|
||||||
|
a: Static_Array(10, int);
|
||||||
|
append(*a, 10, 20, 30);
|
||||||
|
|
||||||
|
remove_unordered(*a, 1);
|
||||||
|
test.expect(t, a.count == 2);
|
||||||
|
test.expect(t, a.items[1] == 30);
|
||||||
|
|
||||||
|
remove_unordered(*a, 0);
|
||||||
|
test.expect(t, a.count == 1);
|
||||||
|
test.expect(t, a.items[0] == 30);
|
||||||
|
|
||||||
|
remove_unordered(*a, 0);
|
||||||
|
test.expect(t, a.count == 0);
|
||||||
|
|
||||||
|
append(*a, 10);
|
||||||
|
test.expect(t, a.count == 1);
|
||||||
|
test.expect(t, a.items[0] == 10);
|
||||||
|
});
|
||||||
|
}
|
||||||
11
array/xar.jai
Normal file
11
array/xar.jai
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
#module_parameters(RUN_TESTS := false);
|
||||||
|
|
||||||
|
#scope_file;
|
||||||
|
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
// TESTS
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
|
||||||
|
#if RUN_TESTS #run {
|
||||||
|
test :: #import "jc/meta/test";
|
||||||
|
}
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
#module_parameters(RunTests := false);
|
#module_parameters(RUN_TESTS := false);
|
||||||
|
|
||||||
Base64Encode :: (str: string, $for_url := false) -> string, bool {
|
base64_encode :: (str: string, $for_url := false) -> string, bool {
|
||||||
enc, ok := Base64Encode(str.([]u8), for_url);
|
enc, ok := base64_encode(str.([]u8), for_url);
|
||||||
return enc.(string), ok;
|
return enc.(string), ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
Base64Decode :: (str: string) -> string, bool {
|
base64_decode :: (str: string) -> string, bool {
|
||||||
enc, ok := Base64Decode(str.([]u8));
|
enc, ok := base64_decode(str.([]u8));
|
||||||
return enc.(string), ok;
|
return enc.(string), ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
Base64Encode :: ($$data: []u8, $for_url := false) -> []u8, bool {
|
base64_encode :: ($$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 {
|
||||||
|
|
@ -21,7 +21,7 @@ Base64Encode :: ($$data: []u8, $for_url := false) -> []u8, bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
padded := strings.contains(data.(string), Padding_Character);
|
padded := strings.contains(data.(string), Padding_Character);
|
||||||
encoded := basic.NewArray(EncodedLength(data.count, padded), u8, true);
|
encoded := basic.NewArray(encoded_length(data.count, padded), u8, true);
|
||||||
|
|
||||||
src_idx := 0;
|
src_idx := 0;
|
||||||
dst_idx := 0;
|
dst_idx := 0;
|
||||||
|
|
@ -70,7 +70,7 @@ Base64Encode :: ($$data: []u8, $for_url := false) -> []u8, bool {
|
||||||
return encoded, true;
|
return encoded, true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Base64Decode :: (data: []u8) -> []u8, bool {
|
base64_decode :: (data: []u8) -> []u8, bool {
|
||||||
if !data.count return .[], false;
|
if !data.count return .[], false;
|
||||||
|
|
||||||
lookup :: (c: u8) -> u8 #expand {
|
lookup :: (c: u8) -> u8 #expand {
|
||||||
|
|
@ -144,20 +144,17 @@ Base64Decode :: (data: []u8) -> []u8, bool {
|
||||||
return decoded, true;
|
return decoded, true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#scope_file;
|
||||||
#scope_file
|
|
||||||
|
|
||||||
Padding_Character :: #char "=";
|
Padding_Character :: #char "=";
|
||||||
Encoding_Url :: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
Encoding_Url :: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||||
Encoding_Standard :: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
Encoding_Standard :: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
|
||||||
EncodedLength :: (count: int, with_padding := false) -> int {
|
encoded_length :: (count: int, with_padding := false) -> int {
|
||||||
if with_padding then return (count + 2) / 3 * 4;
|
if with_padding then return (count + 2) / 3 * 4;
|
||||||
return (count * 8 + 5) / 6;
|
return (count * 8 + 5) / 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
#import "jc";
|
|
||||||
|
|
||||||
basic :: #import "Basic"; // @future
|
basic :: #import "Basic"; // @future
|
||||||
strings :: #import "String"; // @future
|
strings :: #import "String"; // @future
|
||||||
|
|
||||||
|
|
@ -166,45 +163,47 @@ strings :: #import "String"; // @future
|
||||||
// TESTS
|
// TESTS
|
||||||
// ----------------------------------------------------------
|
// ----------------------------------------------------------
|
||||||
|
|
||||||
#if RunTests #run {
|
#if RUN_TESTS #run {
|
||||||
Test("encodes", t => {
|
test :: #import "jc/meta/test";
|
||||||
|
|
||||||
|
test.run("encodes", (t) => {
|
||||||
str :: "Hello, World";
|
str :: "Hello, World";
|
||||||
|
|
||||||
encoded, ok := Base64Encode(str);
|
encoded, ok := base64_encode(str);
|
||||||
Expect(ok, "'%' did not properly encode!", str);
|
test.expect(t, ok, "'%' did not properly encode!", str);
|
||||||
|
|
||||||
expected :: "SGVsbG8sIFdvcmxk";
|
expected :: "SGVsbG8sIFdvcmxk";
|
||||||
Expect(encoded == expected, "wanted: '%', received: '%'", expected, encoded);
|
test.expect(t, encoded == expected, "wanted: '%', received: '%'", expected, encoded);
|
||||||
});
|
});
|
||||||
|
|
||||||
Test("decodes", (t) => {
|
test.run("decodes", (t) => {
|
||||||
encoded_str :: "SGVsbG8sIFdvcmxk";
|
encoded_str :: "SGVsbG8sIFdvcmxk";
|
||||||
|
|
||||||
decoded, ok := Base64Decode(encoded_str);
|
decoded, ok := base64_decode(encoded_str);
|
||||||
Expect(ok, "'%' did not properly decode!", encoded_str);
|
test.expect(t, ok, "'%' did not properly decode!", encoded_str);
|
||||||
|
|
||||||
expected :: "Hello, World";
|
expected :: "Hello, World";
|
||||||
Expect(decoded == expected, "wanted: '%', received: '%'", expected, decoded);
|
test.expect(t, decoded == expected, "wanted: '%', received: '%'", expected, decoded);
|
||||||
});
|
});
|
||||||
|
|
||||||
Test("encodes/decodes padding", (t) => {
|
test.run("encodes/decodes padding", (t) => {
|
||||||
str :: "VkdocGN5QnBjeUJ6YjIxbGRHaHBibWNnYlc5eVpTQmpiMjF3YkdWNExDQnBkQ0J6YUc5MWJHUWdhR0YyWlNCd1lXUmthVzVuUHc9PQ==";
|
str :: "VkdocGN5QnBjeUJ6YjIxbGRHaHBibWNnYlc5eVpTQmpiMjF3YkdWNExDQnBkQ0J6YUc5MWJHUWdhR0YyWlNCd1lXUmthVzVuUHc9PQ==";
|
||||||
|
|
||||||
first_decode, f_ok := Base64Decode(str);
|
first_decode, f_ok := base64_decode(str);
|
||||||
Expect(f_ok, "first decode failed!");
|
test.expect(t, f_ok, "first decode failed!");
|
||||||
|
|
||||||
re_encode, r_ok := Base64Encode(first_decode);
|
re_encode, r_ok := base64_encode(first_decode);
|
||||||
Expect(r_ok, "re-encode failed!");
|
test.expect(t, r_ok, "re-encode failed!");
|
||||||
|
|
||||||
second_decode, s_ok := Base64Decode(re_encode);
|
second_decode, s_ok := base64_decode(re_encode);
|
||||||
Expect(s_ok, "second decode failed!");
|
test.expect(t, s_ok, "second decode failed!");
|
||||||
|
|
||||||
for 0..first_decode.count - 1 {
|
for 0..first_decode.count - 1 {
|
||||||
Expect(first_decode[it] == second_decode[it], "Decoded byte '%' did not match expected output", it);
|
test.expect(t, first_decode[it] == second_decode[it], "Decoded byte '%' did not match expected output", it);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Test("encodes/decodes struct", (t) => {
|
test.run("encodes/decodes struct", (t) => {
|
||||||
Foo :: struct {
|
Foo :: struct {
|
||||||
first: u64;
|
first: u64;
|
||||||
second: u64;
|
second: u64;
|
||||||
|
|
@ -221,14 +220,14 @@ strings :: #import "String"; // @future
|
||||||
enc_builder: basic.String_Builder;
|
enc_builder: basic.String_Builder;
|
||||||
basic.print_to_builder(*enc_builder, "%,%,%,%", foo.first, foo.second, foo.third, foo.forth);
|
basic.print_to_builder(*enc_builder, "%,%,%,%", foo.first, foo.second, foo.third, foo.forth);
|
||||||
|
|
||||||
encoded, e_ok := Base64Encode(basic.builder_to_string(*enc_builder));
|
encoded, e_ok := base64_encode(basic.builder_to_string(*enc_builder));
|
||||||
Expect(e_ok, "Encode of structure failed!");
|
test.expect(t, e_ok, "Encode of structure failed!");
|
||||||
|
|
||||||
decoded, d_ok := Base64Decode(encoded);
|
decoded, d_ok := base64_decode(encoded);
|
||||||
Expect(d_ok, "Decode of encoded structure failed!");
|
test.expect(t, d_ok, "Decode of encoded structure failed!");
|
||||||
|
|
||||||
parts := strings.split(decoded, ",");
|
parts := strings.split(decoded, ",");
|
||||||
Expect(parts.count == 4, "Invalid number of parts after decode: %", parts.count);
|
test.expect(t, parts.count == 4, "Invalid number of parts after decode: %", parts.count);
|
||||||
|
|
||||||
bar: Foo = ---;
|
bar: Foo = ---;
|
||||||
|
|
||||||
|
|
@ -238,17 +237,17 @@ strings :: #import "String"; // @future
|
||||||
|
|
||||||
for info.members {
|
for info.members {
|
||||||
value, ok := strings.parse_int(*parts[it_index], u64);
|
value, ok := strings.parse_int(*parts[it_index], u64);
|
||||||
Expect(ok, "Integer parse of value % ('%') failed!", it_index, parts[it_index]);
|
test.expect(t, ok, "Integer parse of value % ('%') failed!", it_index, parts[it_index]);
|
||||||
|
|
||||||
offset := (ptr + it.offset_in_bytes).(*u64);
|
offset := (ptr + it.offset_in_bytes).(*u64);
|
||||||
offset.*= value;
|
offset.*= value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Expect(foo.first == bar.first, "Foo.first (%, %) didn't match between encoding/decoding!", foo.first, bar.first);
|
test.expect(t, foo.first == bar.first, "Foo.first (%, %) didn't match between encoding/decoding!", foo.first, bar.first);
|
||||||
Expect(foo.second == bar.second, "Foo.second (%, %) didn't match between encoding/decoding!", foo.second, bar.second);
|
test.expect(t, foo.second == bar.second, "Foo.second (%, %) didn't match between encoding/decoding!", foo.second, bar.second);
|
||||||
Expect(foo.third == bar.third, "Foo.third (%, %) didn't match between encoding/decoding!", foo.third, bar.third);
|
test.expect(t, foo.third == bar.third, "Foo.third (%, %) didn't match between encoding/decoding!", foo.third, bar.third);
|
||||||
Expect(foo.forth == bar.forth, "Foo.forth (%, %) didn't match between encoding/decoding!", foo.forth, bar.forth);
|
test.expect(t, foo.forth == bar.forth, "Foo.forth (%, %) didn't match between encoding/decoding!", foo.forth, bar.forth);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#module_parameters(RunTests := false);
|
#module_parameters(RUN_TESTS := false);
|
||||||
|
|
||||||
Json_Value :: struct {
|
Json_Value :: struct {
|
||||||
kind: enum {
|
kind: enum {
|
||||||
|
|
@ -327,7 +327,7 @@ to_string :: (value: Json_Value, indent := 0) -> string {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#scope_file
|
#scope_file;
|
||||||
|
|
||||||
get_json_member_name :: (member: Type_Info_Struct_Member) -> string {
|
get_json_member_name :: (member: Type_Info_Struct_Member) -> string {
|
||||||
name := member.name;
|
name := member.name;
|
||||||
|
|
@ -609,3 +609,4 @@ at_end :: inline (p: *Parser) -> bool {
|
||||||
basic :: #import "Basic"; // @future
|
basic :: #import "Basic"; // @future
|
||||||
strings :: #import "String"; // @future
|
strings :: #import "String"; // @future
|
||||||
|
|
||||||
|
|
||||||
10
encoding/module.jai
Normal file
10
encoding/module.jai
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
#module_parameters(RUN_TESTS := false, WITH_SUBMODULES := true);
|
||||||
|
|
||||||
|
#scope_export;
|
||||||
|
|
||||||
|
#if WITH_SUBMODULES {
|
||||||
|
using #import "jc/encoding/base64"(RUN_TESTS);
|
||||||
|
using #import "jc/encoding/json"(RUN_TESTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
#scope_module;
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
NSObject :: struct {};
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
NSString :: struct {
|
|
||||||
// #as super: NSObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
init :: (path: ) -> NSString {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sel: struct {
|
|
||||||
init_withBytes_length_encoding: objc.Sel;
|
|
||||||
init_contentsOfFile_encoding: objc.Sel;
|
|
||||||
UTF8String: objc.Sel;
|
|
||||||
};
|
|
||||||
|
|
||||||
init_everything :: () {
|
|
||||||
sel.init_withBytes_length_encoding = objc.sel_register_name("init:withBytes:length:encoding");
|
|
||||||
sel.init_contentsOfFile_encoding = objc.sel_register_name("init:contentsOfFile:encoding");
|
|
||||||
sel.UTF8String = objc.sel_register_name("UTF8String");
|
|
||||||
}
|
|
||||||
|
|
||||||
// #import "jc/meta/init"(init_everything);
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
NSObject :: struct {
|
|
||||||
id: u64;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString :: struct {
|
|
||||||
#as using isa: NSObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
|
|
||||||
NSNumber :: struct {
|
|
||||||
// #as using isa: NSObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
#scope_file;
|
|
||||||
|
|
||||||
Sel :: uptr;
|
|
||||||
|
|
||||||
sel: struct {
|
|
||||||
boolValue: Sel; @boolValue
|
|
||||||
intValue: Sel; @intValue
|
|
||||||
unsignedIntValue: Sel; @unsignedIntValue
|
|
||||||
floatValue: Sel; @floatValue
|
|
||||||
doubleValue: Sel; @doubleValue
|
|
||||||
}
|
|
||||||
|
|
||||||
#add_context InitFoundation :: () {
|
|
||||||
#import "Basic";
|
|
||||||
|
|
||||||
tmp: [512]u8;
|
|
||||||
|
|
||||||
base := (*sel).(*u8);
|
|
||||||
info := type_info(type_of(sel));
|
|
||||||
for info.members if it.notes.count != 0 {
|
|
||||||
name := it.notes[0];
|
|
||||||
memcpy(tmp.data, name.data, name.count);
|
|
||||||
tmp.data[name.count] = 0;
|
|
||||||
|
|
||||||
ptr := base + it.offset_in_bytes;
|
|
||||||
ptr.* = sel_register_name(tmp.data);
|
|
||||||
assert(ptr.* != 0, "failed to register selector: %", name);
|
|
||||||
}
|
|
||||||
|
|
||||||
print("%\n", info.*);
|
|
||||||
}
|
|
||||||
|
|
||||||
Foundation :: #library,system,no_dll,link_always "Foundation";
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
#scope_export;
|
|
||||||
|
|
||||||
UInt :: u64;
|
|
||||||
|
|
||||||
Id :: *object;
|
|
||||||
Class :: *class;
|
|
||||||
Sel :: *selector;
|
|
||||||
|
|
||||||
Bool :: u8;
|
|
||||||
|
|
||||||
True : Bool : 1;
|
|
||||||
False : Bool : 0;
|
|
||||||
|
|
||||||
msg_send :: () #foreign objc "objc_msgSend";
|
|
||||||
msg_send_super :: () #foreign objc "objc_msgSend_super";
|
|
||||||
msg_send_fpret :: () #foreign objc "objc_msgSend_fpret";
|
|
||||||
msg_send_stret :: () #foreign objc "objc_msgSend_stret";
|
|
||||||
|
|
||||||
get_class :: (name: *u8) -> Class #foreign objc "objc_getClass";
|
|
||||||
|
|
||||||
sel_get_name :: (sel: Sel) -> *u8 #foreign objc "sel_getName";
|
|
||||||
sel_register_name :: (str: *u8) -> Sel #foreign objc "sel_registerName";
|
|
||||||
sel_get_uid :: (str: *u8) -> Sel #foreign objc "sel_getUid";
|
|
||||||
|
|
||||||
obj_get_class :: (obj: Id) -> Class #foreign objc "object_getClass";
|
|
||||||
obj_set_class :: (obj: Id, cls: Class) -> Class #foreign objc "object_setClass";
|
|
||||||
obj_is_class :: (obj: Id) -> Bool #foreign objc "object_isClass";
|
|
||||||
obj_get_class_name :: (obj: Id) -> *u8 #foreign objc "object_getClassName";
|
|
||||||
obj_copy :: (obj: Id, size: u64) -> Id #foreign objc "object_copy";
|
|
||||||
obj_dispose :: (obj: Id) -> Id #foreign objc "object_dispose";
|
|
||||||
|
|
||||||
class_get_name :: (cls: Class) -> *u8 #foreign objc "class_getName";
|
|
||||||
class_get_super :: (cls: Class) -> Class #foreign objc "class_getSuperclass";
|
|
||||||
|
|
||||||
#scope_module;
|
|
||||||
|
|
||||||
class :: struct {};
|
|
||||||
object :: struct {};
|
|
||||||
method :: struct {};
|
|
||||||
ivar :: struct {};
|
|
||||||
category :: struct {};
|
|
||||||
protocol :: struct {};
|
|
||||||
selector :: struct {};
|
|
||||||
|
|
||||||
objc :: #library,system,link_always,no_dll "libobjc";
|
|
||||||
|
|
||||||
#import "jc";
|
|
||||||
46
ext/hmm/README
Normal file
46
ext/hmm/README
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
-------------
|
||||||
|
Handmade Math
|
||||||
|
-------------
|
||||||
|
|
||||||
|
jai ./generate.jai # generate the bindings (not required)
|
||||||
|
|
||||||
|
#import "jc/hmm"(
|
||||||
|
STATIC = true, # if HMM should be linked statically (default: true)
|
||||||
|
SIMD = true, # if SIMD should be used (default: true)
|
||||||
|
UNITS = .radians, # angle units to use [radians, degrees, turns] (default: radians)
|
||||||
|
);
|
||||||
|
|
||||||
|
What
|
||||||
|
----
|
||||||
|
|
||||||
|
Configurable, auto-generated bindings for Handmade Math
|
||||||
|
|
||||||
|
How
|
||||||
|
---
|
||||||
|
|
||||||
|
These are generated from HandmadeMath.h using Jai's
|
||||||
|
Bindings_Generator module. Because HandmadeMath is a
|
||||||
|
header-only library, we need to compile it into a
|
||||||
|
static/dynamic library that can be used with Jai's FFI
|
||||||
|
system. 'generate.jai' creates both static and dynamic
|
||||||
|
libraries for each angle unit in HandmadeMath
|
||||||
|
(radians, degrees, turns) +- SIMD support. These are
|
||||||
|
placed in the corresponding 'win', 'mac', or 'linux'
|
||||||
|
directories.
|
||||||
|
|
||||||
|
'module.jai' conditionally links one of these libraries
|
||||||
|
based on the module parameters set.
|
||||||
|
|
||||||
|
A few liberties were taken during the binding process to
|
||||||
|
either fix issues with automatic binding generation, or
|
||||||
|
improve the usability of these bindings.
|
||||||
|
|
||||||
|
Here are the main changes:
|
||||||
|
|
||||||
|
- Converted procedure argument names from PascalCase
|
||||||
|
to snake_case
|
||||||
|
|
||||||
|
- Converted struct field names from PascalCase to
|
||||||
|
snake_case
|
||||||
|
|
||||||
|
- Procedure names still use PascalCase
|
||||||
|
|
@ -1,29 +1,7 @@
|
||||||
/*
|
|
||||||
Module hmm provides bindings for the HandmadeMath C
|
|
||||||
library.
|
|
||||||
|
|
||||||
hmm conditionally links to a specific version of
|
|
||||||
HandmadeMath based on the module parameters passed
|
|
||||||
when importing.
|
|
||||||
|
|
||||||
Additionally, a few liberties were taken during the
|
|
||||||
binding process to either fix issues with automatic
|
|
||||||
binding generation, or improve the usability of these
|
|
||||||
bindings.
|
|
||||||
|
|
||||||
Here are the main changes:
|
|
||||||
<pre>
|
|
||||||
- Converted procedure argument names from PascalCase to snake_case
|
|
||||||
- Converted struct field names from PascalCase to snake_case
|
|
||||||
</pre>
|
|
||||||
*/
|
|
||||||
#module_parameters(
|
#module_parameters(
|
||||||
/// Statically link to HandmadeMath.
|
|
||||||
STATIC := true,
|
STATIC := true,
|
||||||
/// Enable SIMD support.
|
|
||||||
SIMD := true,
|
SIMD := true,
|
||||||
/// Angle units to use.
|
UNITS := (enum { radians; degrees; turns; }).radians
|
||||||
UNITS : enum { radians; degrees; turns; } = .radians
|
|
||||||
);
|
);
|
||||||
|
|
||||||
#scope_export;
|
#scope_export;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
/*
|
|
||||||
Module luajit provides bindings for the LuaJIT C
|
|
||||||
library (2.1.1744318430)
|
|
||||||
*/
|
|
||||||
#module_parameters(STATIC := true);
|
#module_parameters(STATIC := true);
|
||||||
|
|
||||||
#if STATIC {
|
#if STATIC {
|
||||||
|
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
#scope_export;
|
|
||||||
|
|
||||||
UInt :: u64;
|
|
||||||
|
|
||||||
Id :: *object;
|
|
||||||
Class :: *class;
|
|
||||||
Sel :: *selector;
|
|
||||||
|
|
||||||
Bool :: u8;
|
|
||||||
|
|
||||||
True : Bool : 1;
|
|
||||||
False : Bool : 0;
|
|
||||||
|
|
||||||
msg_send :: () #foreign objc "objc_msgSend";
|
|
||||||
msg_send_super :: () #foreign objc "objc_msgSend_super";
|
|
||||||
msg_send_fpret :: () #foreign objc "objc_msgSend_fpret";
|
|
||||||
msg_send_stret :: () #foreign objc "objc_msgSend_stret";
|
|
||||||
|
|
||||||
get_class :: (name: *u8) -> Class #foreign objc "objc_getClass";
|
|
||||||
|
|
||||||
sel_get_name :: (sel: Sel) -> *u8 #foreign objc "sel_getName";
|
|
||||||
sel_register_name :: (str: *u8) -> Sel #foreign objc "sel_registerName";
|
|
||||||
sel_get_uid :: (str: *u8) -> Sel #foreign objc "sel_getUid";
|
|
||||||
|
|
||||||
obj_get_class :: (obj: Id) -> Class #foreign objc "object_getClass";
|
|
||||||
obj_set_class :: (obj: Id, cls: Class) -> Class #foreign objc "object_setClass";
|
|
||||||
obj_is_class :: (obj: Id) -> Bool #foreign objc "object_isClass";
|
|
||||||
obj_get_class_name :: (obj: Id) -> *u8 #foreign objc "object_getClassName";
|
|
||||||
obj_copy :: (obj: Id, size: u64) -> Id #foreign objc "object_copy";
|
|
||||||
obj_dispose :: (obj: Id) -> Id #foreign objc "object_dispose";
|
|
||||||
|
|
||||||
class_get_name :: (cls: Class) -> *u8 #foreign objc "class_getName";
|
|
||||||
class_get_super :: (cls: Class) -> Class #foreign objc "class_getSuperclass";
|
|
||||||
|
|
||||||
#scope_module;
|
|
||||||
|
|
||||||
class :: struct {};
|
|
||||||
object :: struct {};
|
|
||||||
method :: struct {};
|
|
||||||
ivar :: struct {};
|
|
||||||
category :: struct {};
|
|
||||||
protocol :: struct {};
|
|
||||||
selector :: struct {};
|
|
||||||
|
|
||||||
objc :: #library,system,link_always,no_dll "libobjc";
|
|
||||||
|
|
||||||
#import "jc";
|
|
||||||
|
|
@ -1,10 +1,3 @@
|
||||||
/*
|
|
||||||
Module raylib provides bindings for the raylib C
|
|
||||||
library (v5.5).
|
|
||||||
|
|
||||||
Supported platforms: Windows, Mac, Linux
|
|
||||||
*/
|
|
||||||
|
|
||||||
#module_parameters(STATIC := true);
|
#module_parameters(STATIC := true);
|
||||||
|
|
||||||
#scope_export
|
#scope_export
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
/*
|
|
||||||
Module remotery provides bindings for the Remotery
|
|
||||||
CPU/GPU profiling library.
|
|
||||||
*/
|
|
||||||
#module_parameters(STATIC := true);
|
#module_parameters(STATIC := true);
|
||||||
|
|
||||||
#if STATIC {
|
#if STATIC {
|
||||||
|
|
|
||||||
10
hash/module.jai
Normal file
10
hash/module.jai
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
#module_parameters(RUN_TESTS := false, WITH_SUBMODULES := false);
|
||||||
|
|
||||||
|
#scope_export;
|
||||||
|
|
||||||
|
#if WITH_SUBMODULES {
|
||||||
|
using #import "jc/hash/murmur"(RUN_TESTS);
|
||||||
|
using #import "jc/hash/xxhash"(RUN_TESTS);
|
||||||
|
using #import "jc/hash/table"(RUN_TESTS);
|
||||||
|
}
|
||||||
|
|
||||||
58
hash/murmur.jai
Normal file
58
hash/murmur.jai
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
#module_parameters(RUN_TESTS := false);
|
||||||
|
|
||||||
|
// Implementation: Demetri Spanos (github.com/demetri/scribbles)
|
||||||
|
// Jai Port: Jesse Coyle (github.com/Zilarrezko)
|
||||||
|
|
||||||
|
MurMur_Seed : u32 : 0xa3c91521;
|
||||||
|
|
||||||
|
murmur32 :: inline (s: string, seed: u32 = MurMur_Seed) -> u32 {
|
||||||
|
return murmur32(s.data, s.count, seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
murmur32 :: inline (x: $T, seed: u32 = MurMur_Seed) -> u32 {
|
||||||
|
return murmur32(*x, size_of(T), seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
murmur32 :: (key: *void, len: int, seed: u32 = MurMur_Seed) -> u32 {
|
||||||
|
scrambler :: (k: u32) -> u32 #expand {
|
||||||
|
c1: u32 : 0xcc9e2d51;
|
||||||
|
c2: u32 : 0x1b873593;
|
||||||
|
r1: int : 15;
|
||||||
|
k = k*c1;
|
||||||
|
k = k <<< r1;
|
||||||
|
k = k*c2;
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
|
||||||
|
h: u32 = seed;
|
||||||
|
tail: *u8 = cast(*u8)key + (len/4)*4;
|
||||||
|
p: *u32 = cast(*u32)key;
|
||||||
|
|
||||||
|
while cast(*u8)p < tail {
|
||||||
|
k: u32 = <<p;
|
||||||
|
k = scrambler(k);
|
||||||
|
h = h^k;
|
||||||
|
h = h <<< 13;
|
||||||
|
h = h*5 + 0xe6546b64;
|
||||||
|
p += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
t: u32;
|
||||||
|
if len & 3 == {
|
||||||
|
case 3;
|
||||||
|
t ^= cast(u32)(tail[2]) << 16;
|
||||||
|
#through;
|
||||||
|
case 2;
|
||||||
|
t ^= cast(u32)(tail[1]) << 8;
|
||||||
|
#through;
|
||||||
|
case 1;
|
||||||
|
t ^= cast(u32)(tail[0]);
|
||||||
|
t = scrambler(t);
|
||||||
|
h = h^t;
|
||||||
|
}
|
||||||
|
h ^= cast,trunc(u32)len;
|
||||||
|
h ^= h >> 16; h *= 0x85ebca6b;
|
||||||
|
h ^= h >> 13; h *= 0xc2b2ae35;
|
||||||
|
h ^= h >> 16;
|
||||||
|
return h;
|
||||||
|
}
|
||||||
191
hash/table.jai
Normal file
191
hash/table.jai
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
#module_parameters(RUN_TESTS := false);
|
||||||
|
|
||||||
|
// Dead simple key-value pair type (aka. hash table or hash map)
|
||||||
|
Table :: struct(Key: Type, Value: Type) {
|
||||||
|
allocator: Allocator;
|
||||||
|
slots: [..]Slot;
|
||||||
|
free_slots: [..]int;
|
||||||
|
count: int;
|
||||||
|
|
||||||
|
Slot :: struct {
|
||||||
|
hash: u32 = invalid_hash;
|
||||||
|
key: Key = ---;
|
||||||
|
value: Value = ---;
|
||||||
|
}
|
||||||
|
|
||||||
|
hash_proc :: hash.murmur32;
|
||||||
|
invalid_hash :: (0x8000_dead).(u32); // @note(judah): I'm curious what values would hit this hash on accident
|
||||||
|
number_of_items_to_allocate_initially :: 16; // @note(judah): must be a power of two
|
||||||
|
}
|
||||||
|
|
||||||
|
get :: (t: *Table, key: t.Key) -> t.Value, bool {
|
||||||
|
slot, ok := find_slot(t, get_hash(t, key));
|
||||||
|
if !ok {
|
||||||
|
return mem.zero_of(t.Value), false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return slot.value, true;
|
||||||
|
}
|
||||||
|
|
||||||
|
set :: (t: *Table, key: t.Key, value: t.Value) {
|
||||||
|
hash := get_hash(t, key);
|
||||||
|
slot, exists := find_slot(t, hash);
|
||||||
|
if !exists {
|
||||||
|
slot = create_or_reuse_slot(t);
|
||||||
|
slot.hash = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
slot.key = key;
|
||||||
|
slot.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
exists :: (t: *Table, key: t.Key) -> bool {
|
||||||
|
_, exists := find_slot(t, get_hash(t, key));
|
||||||
|
return exists;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @note(judah): we use 'delete' instead of 'remove' because it's a keyword...
|
||||||
|
delete :: (t: *Table, key: t.Key) -> t.Value, bool {
|
||||||
|
slot, ok, idx := find_slot(t, get_hash(t, key));
|
||||||
|
if !ok return mem.zero_of(t.Value), false;
|
||||||
|
|
||||||
|
last_value := slot.value;
|
||||||
|
mark_slot_for_reuse(t, idx);
|
||||||
|
|
||||||
|
return last_value, true;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset :: (t: *Table) {
|
||||||
|
t.count = 0;
|
||||||
|
t.slots.count = 0;
|
||||||
|
t.free_slots.count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for_expansion :: (t: *Table, body: Code, flags: For_Flags) #expand {
|
||||||
|
#assert (flags & .POINTER == 0) "cannot iterate by pointer";
|
||||||
|
for <=(flags & .REVERSE == .REVERSE) slot: t.slots if slot.hash != t.invalid_hash {
|
||||||
|
`it := slot.value;
|
||||||
|
`it_index := slot.key;
|
||||||
|
#insert,scope(body)(break = break slot) body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#scope_file;
|
||||||
|
|
||||||
|
get_hash :: inline (t: *Table, key: t.Key) -> u32 {
|
||||||
|
hash := t.hash_proc(key);
|
||||||
|
basic.assert(hash != t.invalid_hash, "key % collided with invalid hash marker (%)", key, t.invalid_hash);
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
find_slot :: (t: *Table, hash: u32) -> *t.Slot, bool, int {
|
||||||
|
for * t.slots if it.hash == hash {
|
||||||
|
return it, true, it_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null, false, -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
create_or_reuse_slot :: (t: *Table) -> *t.Slot {
|
||||||
|
inline try_lazy_init(t);
|
||||||
|
|
||||||
|
if t.free_slots.count > 0 {
|
||||||
|
slot_idx := t.free_slots[t.free_slots.count - 1];
|
||||||
|
t.free_slots.count -= 1;
|
||||||
|
return *t.slots[slot_idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.slots.allocated == 0 {
|
||||||
|
array.resize(*t.slots, t.number_of_items_to_allocate_initially);
|
||||||
|
}
|
||||||
|
else if t.slots.count >= t.slots.allocated {
|
||||||
|
array.resize(*t.slots, mem.next_power_of_two(t.slots.allocated));
|
||||||
|
}
|
||||||
|
|
||||||
|
slot := array.append(*t.slots);
|
||||||
|
t.count = t.slots.count;
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
mark_slot_for_reuse :: (t: *Table, index: int) {
|
||||||
|
inline try_lazy_init(t);
|
||||||
|
|
||||||
|
t.count -= 1;
|
||||||
|
t.slots[index] = .{ hash = t.invalid_hash };
|
||||||
|
|
||||||
|
array.append(*t.free_slots, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
try_lazy_init :: inline (t: *Table) {
|
||||||
|
mem.lazy_set_allocator(t);
|
||||||
|
mem.lazy_set_allocator(*t.slots);
|
||||||
|
mem.lazy_set_allocator(*t.free_slots);
|
||||||
|
}
|
||||||
|
|
||||||
|
mem :: #import "jc/memory";
|
||||||
|
array :: #import "jc/array";
|
||||||
|
hash :: #import "jc/hash";
|
||||||
|
|
||||||
|
basic :: #import "Basic"; // @future
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
// TESTS
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
|
||||||
|
#if RUN_TESTS #run {
|
||||||
|
test :: #import "jc/meta/test";
|
||||||
|
|
||||||
|
test.run("basic operations", t => {
|
||||||
|
ITERATIONS :: 64;
|
||||||
|
|
||||||
|
values: Table(int, int);
|
||||||
|
for 0..ITERATIONS {
|
||||||
|
set(*values, it, it * it);
|
||||||
|
}
|
||||||
|
|
||||||
|
for 0..ITERATIONS {
|
||||||
|
v, ok := get(*values, it);
|
||||||
|
test.expect(t, v == it * it);
|
||||||
|
}
|
||||||
|
|
||||||
|
for 0..ITERATIONS if it % 2 == 0 {
|
||||||
|
_, ok := delete(*values, it);
|
||||||
|
test.expect(t, ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
for 0..ITERATIONS if it % 2 == 0 {
|
||||||
|
_, ok := get(*values, it);
|
||||||
|
test.expect(t, !ok);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test.run("free slots", t => {
|
||||||
|
values: Table(int, int);
|
||||||
|
|
||||||
|
set(*values, 1, 100);
|
||||||
|
set(*values, 2, 200);
|
||||||
|
set(*values, 3, 300);
|
||||||
|
test.expect(t, values.count == 3);
|
||||||
|
test.expect(t, values.slots.allocated == values.number_of_items_to_allocate_initially);
|
||||||
|
|
||||||
|
// deleting something that doesn't exist should do nothing
|
||||||
|
_, ok := delete(*values, 0);
|
||||||
|
test.expect(t, !ok);
|
||||||
|
test.expect(t, values.count == 3);
|
||||||
|
|
||||||
|
delete(*values, 2);
|
||||||
|
test.expect(t, values.count == 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.run("iteration", t => {
|
||||||
|
values: Table(int, int);
|
||||||
|
|
||||||
|
for 0..10 set(*values, it, it * it);
|
||||||
|
test.expect(t, values.count == 11);
|
||||||
|
|
||||||
|
for v, k: values test.expect(t, v == k * k);
|
||||||
|
for < v, k: values test.expect(t, v == k * k);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -1,74 +1,19 @@
|
||||||
// Implementation: Demetri Spanos (github.com/demetri/scribbles)
|
#module_parameters(RUN_TESTS := false);
|
||||||
// Jai Port: Jesse Coyle (github.com/Zilarrezko)
|
|
||||||
|
|
||||||
MurmurSeed : u32 : 0xa3c91521;
|
|
||||||
|
|
||||||
Murmur32 :: inline (s: string, seed: u32 = MurmurSeed) -> u32 {
|
|
||||||
return Murmur32(s.data, s.count, seed);
|
|
||||||
}
|
|
||||||
|
|
||||||
Murmur32 :: inline (x: $T, seed: u32 = MurmurSeed) -> u32 {
|
|
||||||
return Murmur32(*x, size_of(T), seed);
|
|
||||||
}
|
|
||||||
|
|
||||||
Murmur32 :: (key: *void, len: int, seed: u32 = MurmurSeed) -> u32 {
|
|
||||||
scrambler :: (k: u32) -> u32 #expand {
|
|
||||||
c1: u32 : 0xcc9e2d51;
|
|
||||||
c2: u32 : 0x1b873593;
|
|
||||||
r1: int : 15;
|
|
||||||
k = k*c1;
|
|
||||||
k = k <<< r1;
|
|
||||||
k = k*c2;
|
|
||||||
return k;
|
|
||||||
}
|
|
||||||
|
|
||||||
h: u32 = seed;
|
|
||||||
tail: *u8 = cast(*u8)key + (len/4)*4;
|
|
||||||
p: *u32 = cast(*u32)key;
|
|
||||||
|
|
||||||
while cast(*u8)p < tail {
|
|
||||||
k: u32 = <<p;
|
|
||||||
k = scrambler(k);
|
|
||||||
h = h^k;
|
|
||||||
h = h <<< 13;
|
|
||||||
h = h*5 + 0xe6546b64;
|
|
||||||
p += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
t: u32;
|
|
||||||
if len & 3 == {
|
|
||||||
case 3;
|
|
||||||
t ^= cast(u32)(tail[2]) << 16;
|
|
||||||
#through;
|
|
||||||
case 2;
|
|
||||||
t ^= cast(u32)(tail[1]) << 8;
|
|
||||||
#through;
|
|
||||||
case 1;
|
|
||||||
t ^= cast(u32)(tail[0]);
|
|
||||||
t = scrambler(t);
|
|
||||||
h = h^t;
|
|
||||||
}
|
|
||||||
h ^= cast,trunc(u32)len;
|
|
||||||
h ^= h >> 16; h *= 0x85ebca6b;
|
|
||||||
h ^= h >> 13; h *= 0xc2b2ae35;
|
|
||||||
h ^= h >> 16;
|
|
||||||
return h;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implementation: Demetri Spanos (github.com/demetri/scribbles)
|
// Implementation: Demetri Spanos (github.com/demetri/scribbles)
|
||||||
// Jai Port: Jesse Coyle (github.com/Zilarrezko)
|
// Jai Port: Jesse Coyle (github.com/Zilarrezko)
|
||||||
|
|
||||||
XXHashSeed :: 0;
|
XXHash_Seed :: 0;
|
||||||
|
|
||||||
XXHash64 :: inline (s: string, seed: u32 = XXHashSeed) -> u64 {
|
xxhash64 :: inline (s: string, seed: u32 = XXHash_Seed) -> u64 {
|
||||||
return XXHash64(s.data, s.count, seed);
|
return xxhash64(s.data, s.count, seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
XXHash64 :: inline (x: $T, seed: u32 = XXHashSeed) -> u64 {
|
xxhash64 :: inline (x: $T, seed: u32 = XXHash_Seed) -> u64 {
|
||||||
return XXHash64(cast(*u8)*x, size_of(T), seed);
|
return xxhash64(cast(*u8)*x, size_of(T), seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
XXHash64 :: (key: *void, len: int, seed: u64 = XXHashSeed) -> u64 {
|
xxhash64 :: (key: *void, len: int, seed: u64 = XXHash_Seed) -> u64 {
|
||||||
p1: u64 : 0x9e3779b185ebca87;
|
p1: u64 : 0x9e3779b185ebca87;
|
||||||
p2: u64 : 0xc2b2ae3d27d4eb4f;
|
p2: u64 : 0xc2b2ae3d27d4eb4f;
|
||||||
p3: u64 : 0x165667b19e3779f9;
|
p3: u64 : 0x165667b19e3779f9;
|
||||||
|
|
@ -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 meta.type_is_scalar(T), "Only accepting scalar types, integer and float"; } {
|
||||||
return ifx v < 0 then -v else v;
|
return ifx v < 0 then -v else v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,6 +110,6 @@ sign :: (x: float) -> float {
|
||||||
|
|
||||||
#scope_file
|
#scope_file
|
||||||
|
|
||||||
#import "jc";
|
|
||||||
// sin, cos
|
// sin, cos
|
||||||
|
meta :: #import "jc/meta";
|
||||||
math :: #import "Math";
|
math :: #import "Math";
|
||||||
|
|
|
||||||
|
|
@ -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 meta.type_is_float(T); }
|
||||||
{
|
{
|
||||||
p := progress;
|
p := progress;
|
||||||
|
|
||||||
|
|
@ -85,6 +85,8 @@ ease :: (progress: $T, $$ease: Ease = .linear, $$transition: Transition = .in) -
|
||||||
|
|
||||||
#scope_file;
|
#scope_file;
|
||||||
|
|
||||||
#import "jc";
|
|
||||||
|
meta :: #import "jc/meta";
|
||||||
|
|
||||||
math :: #import "Math"; // @future
|
math :: #import "Math"; // @future
|
||||||
basic :: #import "Basic"; // @future
|
basic :: #import "Basic"; // @future
|
||||||
|
|
|
||||||
77
math/mat.jai
77
math/mat.jai
|
|
@ -156,32 +156,7 @@ make_look_at :: (camera: Vec3, at: Vec3, up_vector: Vec3) -> Mat4
|
||||||
}
|
}
|
||||||
|
|
||||||
operator* :: (a: Mat4, b: Mat4) -> Mat4 {
|
operator* :: (a: Mat4, b: Mat4) -> Mat4 {
|
||||||
#if CPU == .X64 {
|
|
||||||
return mul_sse(a, b);
|
return mul_sse(a, b);
|
||||||
}
|
|
||||||
else {
|
|
||||||
r: Mat4 = ---;
|
|
||||||
r._00 = a._00*b._00 + a._01*b._10 + a._02*b._20 + a._03*b._30;
|
|
||||||
r._01 = a._00*b._01 + a._01*b._11 + a._02*b._21 + a._03*b._31;
|
|
||||||
r._02 = a._00*b._02 + a._01*b._12 + a._02*b._22 + a._03*b._32;
|
|
||||||
r._03 = a._00*b._03 + a._01*b._13 + a._02*b._23 + a._03*b._33;
|
|
||||||
|
|
||||||
r._10 = a._10*b._00 + a._11*b._10 + a._12*b._20 + a._13*b._30;
|
|
||||||
r._11 = a._10*b._01 + a._11*b._11 + a._12*b._21 + a._13*b._31;
|
|
||||||
r._12 = a._10*b._02 + a._11*b._12 + a._12*b._22 + a._13*b._32;
|
|
||||||
r._13 = a._10*b._03 + a._11*b._13 + a._12*b._23 + a._13*b._33;
|
|
||||||
|
|
||||||
r._20 = a._20*b._00 + a._21*b._10 + a._22*b._20 + a._23*b._30;
|
|
||||||
r._21 = a._20*b._01 + a._21*b._11 + a._22*b._21 + a._23*b._31;
|
|
||||||
r._22 = a._20*b._02 + a._21*b._12 + a._22*b._22 + a._23*b._32;
|
|
||||||
r._23 = a._20*b._03 + a._21*b._13 + a._22*b._23 + a._23*b._33;
|
|
||||||
|
|
||||||
r._30 = a._30*b._00 + a._31*b._10 + a._32*b._20 + a._33*b._30;
|
|
||||||
r._31 = a._30*b._01 + a._31*b._11 + a._32*b._21 + a._33*b._31;
|
|
||||||
r._32 = a._30*b._02 + a._31*b._12 + a._32*b._22 + a._33*b._32;
|
|
||||||
r._33 = a._30*b._03 + a._31*b._13 + a._32*b._23 + a._33*b._33;
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note(Jesse): If you want to make it symmetric go ahead, I usually don't
|
// Note(Jesse): If you want to make it symmetric go ahead, I usually don't
|
||||||
|
|
@ -490,6 +465,25 @@ mul_sse :: inline (a: Mat4, b: Mat4) -> Mat4 {
|
||||||
movups [*r + 48], r0;
|
movups [*r + 48], r0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// r._00 = a._00*b._00 + a._01*b._10 + a._02*b._20 + a._03*b._30;
|
||||||
|
// r._01 = a._00*b._01 + a._01*b._11 + a._02*b._21 + a._03*b._31;
|
||||||
|
// r._02 = a._00*b._02 + a._01*b._12 + a._02*b._22 + a._03*b._32;
|
||||||
|
// r._03 = a._00*b._03 + a._01*b._13 + a._02*b._23 + a._03*b._33;
|
||||||
|
|
||||||
|
// r._10 = a._10*b._00 + a._11*b._10 + a._12*b._20 + a._13*b._30;
|
||||||
|
// r._11 = a._10*b._01 + a._11*b._11 + a._12*b._21 + a._13*b._31;
|
||||||
|
// r._12 = a._10*b._02 + a._11*b._12 + a._12*b._22 + a._13*b._32;
|
||||||
|
// r._13 = a._10*b._03 + a._11*b._13 + a._12*b._23 + a._13*b._33;
|
||||||
|
|
||||||
|
// r._20 = a._20*b._00 + a._21*b._10 + a._22*b._20 + a._23*b._30;
|
||||||
|
// r._21 = a._20*b._01 + a._21*b._11 + a._22*b._21 + a._23*b._31;
|
||||||
|
// r._22 = a._20*b._02 + a._21*b._12 + a._22*b._22 + a._23*b._32;
|
||||||
|
// r._23 = a._20*b._03 + a._21*b._13 + a._22*b._23 + a._23*b._33;
|
||||||
|
|
||||||
|
// r._30 = a._30*b._00 + a._31*b._10 + a._32*b._20 + a._33*b._30;
|
||||||
|
// r._31 = a._30*b._01 + a._31*b._11 + a._32*b._21 + a._33*b._31;
|
||||||
|
// r._32 = a._30*b._02 + a._31*b._12 + a._32*b._22 + a._33*b._32;
|
||||||
|
// r._33 = a._30*b._03 + a._31*b._13 + a._32*b._23 + a._33*b._33;
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
// Note(Jesse): This procedure will crash if it tries to store or access an element across a cache line
|
// Note(Jesse): This procedure will crash if it tries to store or access an element across a cache line
|
||||||
|
|
@ -574,47 +568,50 @@ mul_sse_aligned :: inline (dst: *Mat4, a: *Mat4, b: *Mat4) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#scope_file;
|
#scope_file;
|
||||||
|
|
||||||
is_aligned :: (p: *void, bound: int) -> bool {
|
is_aligned :: (p: *void, bound: int) -> bool {
|
||||||
return cast(int)p & (bound - 1) == 0;
|
return cast(int)p & (bound - 1) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if RUN_TESTS #run {
|
#if #exists(RUN_TESTS) #run {
|
||||||
Test(basic.tprint("%: Mat2", UNITS), t => {
|
test :: #import "jc/meta/test";
|
||||||
|
|
||||||
|
test.run(basic.tprint("%: Mat2", UNITS), t => {
|
||||||
{
|
{
|
||||||
identity := m2_identity;
|
identity := m2_identity;
|
||||||
a: Mat2 = Mat2.{.[1, 2, 3, 4]};
|
a: Mat2 = Mat2.{.[1, 2, 3, 4]};
|
||||||
Expect(a*identity == a);
|
test.expect(t, a*identity == a);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
rotator := rotation_mat2(from_rad(PI));
|
rotator := rotation_mat2(from_rad(PI));
|
||||||
v2 := v2f(1.0, 0.5);
|
v2 := v2f(1.0, 0.5);
|
||||||
v2 = rotator*v2;
|
v2 = rotator*v2;
|
||||||
Expect(v2_eq(v2, v2f(-1.0, -0.5)));
|
test.expect(t, v2_eq(v2, v2f(-1.0, -0.5)));
|
||||||
v2 = rotator*v2;
|
v2 = rotator*v2;
|
||||||
Expect(v2_eq(v2, v2f(1.0, 0.5)));
|
test.expect(t, v2_eq(v2, v2f(1.0, 0.5)));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
m := m2(1.0, 3.0, 2.0, 2.0);
|
m := m2(1.0, 3.0, 2.0, 2.0);
|
||||||
det := determinate(m);
|
det := determinate(m);
|
||||||
Expect(det == -4);
|
test.expect(t, det == -4);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
rotator := rotation_mat2(from_rad(PI));
|
rotator := rotation_mat2(from_rad(PI));
|
||||||
inv_rotator := inverse(rotator);
|
inv_rotator := inverse(rotator);
|
||||||
v2 := v2f(0.1, 1.0);
|
v2 := v2f(0.1, 1.0);
|
||||||
|
|
||||||
Expect(inv_rotator*rotator == m2_identity);
|
test.expect(t, inv_rotator*rotator == m2_identity);
|
||||||
Expect(inv_rotator*(rotator*v2) == v2);
|
test.expect(t, inv_rotator*(rotator*v2) == v2);
|
||||||
v2 = rotator*v2;
|
v2 = rotator*v2;
|
||||||
v2 = inv_rotator*v2;
|
v2 = inv_rotator*v2;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Test(basic.tprint("%: Mat4", UNITS), t => {
|
test.run(basic.tprint("%: Mat4", UNITS), t => {
|
||||||
{
|
{
|
||||||
identity := m4_identity;
|
identity := m4_identity;
|
||||||
a: Mat4 = Mat4.{.[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]};
|
a: Mat4 = Mat4.{.[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]};
|
||||||
Expect(a*identity == a);
|
test.expect(t, a*identity == a);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
UP_VECTOR :: Vec3.{.[0.0, 1.0, 0.0]};
|
UP_VECTOR :: Vec3.{.[0.0, 1.0, 0.0]};
|
||||||
|
|
@ -626,16 +623,16 @@ is_aligned :: (p: *void, bound: int) -> bool {
|
||||||
translator := translate(v3f(10.0, 5.0, 2.0));
|
translator := translate(v3f(10.0, 5.0, 2.0));
|
||||||
v3 := v3f(1.0, 2.0, 1.0);
|
v3 := v3f(1.0, 2.0, 1.0);
|
||||||
v4 := v4f(v3, 1.0);
|
v4 := v4f(v3, 1.0);
|
||||||
Expect(v4 == v4f(1.0, 2.0, 1.0, 1.0));
|
test.expect(t, v4 == v4f(1.0, 2.0, 1.0, 1.0));
|
||||||
Expect(translator*v4 == v4f(11.0, 7.0, 3.0, 1.0));
|
test.expect(t, translator*v4 == v4f(11.0, 7.0, 3.0, 1.0));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
rotator := rotation_mat4(v3f(0.0, 1.0, 0.0), from_rad(PI));
|
rotator := rotation_mat4(v3f(0.0, 1.0, 0.0), from_rad(PI));
|
||||||
v4 := v4f(1.0, 0.5, 0.1, 1.0);
|
v4 := v4f(1.0, 0.5, 0.1, 1.0);
|
||||||
v4 = rotator*v4;
|
v4 = rotator*v4;
|
||||||
Expect(v4_eq(v4, v4f(-1.0, 0.5, -0.1, 1.0)));
|
test.expect(t, v4_eq(v4, v4f(-1.0, 0.5, -0.1, 1.0)));
|
||||||
v4 = rotator*v4;
|
v4 = rotator*v4;
|
||||||
Expect(v4_eq(v4, v4f(1.0, 0.5, 0.1, 1.0)));
|
test.expect(t, v4_eq(v4, v4f(1.0, 0.5, 0.1, 1.0)));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
rotator_x := rotation_mat4(v3f(1.0, 0.0, 0.0), from_rad(0.4*PI));
|
rotator_x := rotation_mat4(v3f(1.0, 0.0, 0.0), from_rad(0.4*PI));
|
||||||
|
|
@ -648,7 +645,7 @@ is_aligned :: (p: *void, bound: int) -> bool {
|
||||||
inv_rotator := inverse(rotator);
|
inv_rotator := inverse(rotator);
|
||||||
v4 := v4f(0.1, 1.0, 0.0, 1.0);
|
v4 := v4f(0.1, 1.0, 0.0, 1.0);
|
||||||
|
|
||||||
Expect(inv_rotator*rotator == m4_identity);
|
test.expect(t, inv_rotator*rotator == m4_identity);
|
||||||
|
|
||||||
v4 = rotator*v4;
|
v4 = rotator*v4;
|
||||||
v4 = inv_rotator*v4;
|
v4 = inv_rotator*v4;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,22 @@
|
||||||
RUN_TESTS := false
|
RUN_TESTS := false
|
||||||
);
|
);
|
||||||
|
|
||||||
#assert type_is_scalar(RECT_TYPE);
|
#assert meta.type_is_scalar(RECT_TYPE);
|
||||||
|
|
||||||
|
// @todo(judah): dumb we can't use meta.range_for here.
|
||||||
|
|
||||||
|
U8_Min, U8_Max :: #run meta.lo_for(u8), #run meta.hi_for(u8);
|
||||||
|
U16_Min, U16_Max :: #run meta.lo_for(u16), #run meta.hi_for(u16);
|
||||||
|
U32_Min, U32_Max :: #run meta.lo_for(u32), #run meta.hi_for(u32);
|
||||||
|
U64_Min, U64_Max :: #run meta.lo_for(u64), #run meta.hi_for(u64);
|
||||||
|
|
||||||
|
S8_Min, S8_Max :: #run meta.lo_for(s8), #run meta.hi_for(s8);
|
||||||
|
S16_Min, S16_Max :: #run meta.lo_for(s16), #run meta.hi_for(s16);
|
||||||
|
S32_Min, S32_Max :: #run meta.lo_for(s32), #run meta.hi_for(s32);
|
||||||
|
S64_Min, S64_Max :: #run meta.lo_for(s64), #run meta.hi_for(s64);
|
||||||
|
|
||||||
|
F32_Min, F32_Max :: #run meta.lo_for(float32), #run meta.hi_for(float32);
|
||||||
|
F64_Min, F64_Max :: #run meta.lo_for(float64), #run meta.hi_for(float64);
|
||||||
|
|
||||||
#load "common.jai";
|
#load "common.jai";
|
||||||
#load "vec.jai";
|
#load "vec.jai";
|
||||||
|
|
@ -15,8 +30,7 @@
|
||||||
|
|
||||||
#scope_module;
|
#scope_module;
|
||||||
|
|
||||||
#import "jc";
|
meta :: #import "jc/meta";
|
||||||
|
|
||||||
math :: #import "Math"; // @future
|
math :: #import "Math"; // @future
|
||||||
basic :: #import "Basic"; // @future
|
basic :: #import "Basic"; // @future
|
||||||
|
|
||||||
|
|
|
||||||
134
math/vec.jai
134
math/vec.jai
|
|
@ -103,17 +103,17 @@ Vec :: struct(N: int, T: Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
operator [] :: (v: Vec, $$idx: int) -> v.T #no_abc {
|
operator [] :: (v: Vec, $$idx: int) -> v.T #no_abc {
|
||||||
CheckBounds(idx, v.N);
|
meta.check_bounds(idx, v.N);
|
||||||
return v.components[idx];
|
return v.components[idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
operator *[] :: (v: *Vec, $$idx: int) -> *v.T #no_abc {
|
operator *[] :: (v: *Vec, $$idx: int) -> *v.T #no_abc {
|
||||||
CheckBounds(idx, v.N);
|
meta.check_bounds(idx, v.N);
|
||||||
return *v.components[idx];
|
return *v.components[idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
operator []= :: (v: *Vec, $$idx: int, value: v.T) #no_abc {
|
operator []= :: (v: *Vec, $$idx: int, value: v.T) #no_abc {
|
||||||
CheckBounds(idx, v.N);
|
meta.check_bounds(idx, v.N);
|
||||||
v.components[idx] = value;
|
v.components[idx] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -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 meta.type_is_scalar(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 meta.type_is_scalar(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 meta.type_is_scalar(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 meta.type_is_scalar(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 meta.type_is_scalar(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 meta.type_is_float(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 meta.type_is_float(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 meta.type_is_integer(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 meta.type_is_float(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 meta.type_is_integer(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 meta.type_is_float(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 meta.type_is_integer(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 };
|
||||||
}
|
}
|
||||||
|
|
@ -661,74 +661,76 @@ same_dir :: (a: Vec3, b: Vec3) -> bool {
|
||||||
#scope_file;
|
#scope_file;
|
||||||
|
|
||||||
#if RUN_TESTS #run,stallable {
|
#if RUN_TESTS #run,stallable {
|
||||||
Test(basic.tprint("%: Vec2", UNITS), t => {
|
test :: #import "jc/meta/test";
|
||||||
|
|
||||||
|
test.run(basic.tprint("%: Vec2", UNITS), t => {
|
||||||
{
|
{
|
||||||
a: Vec2 = v2f(0.0, 1.0);
|
a: Vec2 = v2f(0.0, 1.0);
|
||||||
b: Vec2 = v2f(1.0, 2.0);
|
b: Vec2 = v2f(1.0, 2.0);
|
||||||
|
|
||||||
Expect(a + b == v2f(1.0, 3.0));
|
test.expect(t, a + b == v2f(1.0, 3.0));
|
||||||
Expect(b - a == v2f(1.0, 1.0));
|
test.expect(t, b - a == v2f(1.0, 1.0));
|
||||||
Expect(a*b == v2f(0.0, 2.0));
|
test.expect(t, a*b == v2f(0.0, 2.0));
|
||||||
Expect(a/b == v2f(0.0, 0.5));
|
test.expect(t, a/b == v2f(0.0, 0.5));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
a: Vec(2, int) = v2i(2, 1);
|
a: Vec(2, int) = v2i(2, 1);
|
||||||
b: Vec(2, int) = v2i(1, 0);
|
b: Vec(2, int) = v2i(1, 0);
|
||||||
Expect(a + b == v2i(3, 1));
|
test.expect(t, a + b == v2i(3, 1));
|
||||||
Expect(b - a == v2i(-1, -1));
|
test.expect(t, b - a == v2i(-1, -1));
|
||||||
Expect(a*b == v2i(2, 0));
|
test.expect(t, a*b == v2i(2, 0));
|
||||||
Expect(b/a == v2i(0, 0));
|
test.expect(t, b/a == v2i(0, 0));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
a: Vec2 = v2f(2.3, -4.1);
|
a: Vec2 = v2f(2.3, -4.1);
|
||||||
b: Vec2 = v2f(1.0, 3.6);
|
b: Vec2 = v2f(1.0, 3.6);
|
||||||
c := min(a, b);
|
c := min(a, b);
|
||||||
Expect(c == v2f(1.0, -4.1));
|
test.expect(t, c == v2f(1.0, -4.1));
|
||||||
c = max(a, b);
|
c = max(a, b);
|
||||||
Expect(c == v2f(2.3, 3.6));
|
test.expect(t, c == v2f(2.3, 3.6));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Test(basic.tprint("%: Vec3", UNITS), t => {
|
test.run(basic.tprint("%: Vec3", UNITS), t => {
|
||||||
{
|
{
|
||||||
a: Vec3 = v3f(0.0, 1.0, 2.0);
|
a: Vec3 = v3f(0.0, 1.0, 2.0);
|
||||||
b: Vec3 = v3f(1.0, 2.0, 3.0);
|
b: Vec3 = v3f(1.0, 2.0, 3.0);
|
||||||
Expect(a + b == v3f(1.0, 3.0, 5.0));
|
test.expect(t, a + b == v3f(1.0, 3.0, 5.0));
|
||||||
Expect(b - a == v3f(1.0, 1.0, 1.0));
|
test.expect(t, b - a == v3f(1.0, 1.0, 1.0));
|
||||||
Expect(a*b == v3f(0.0, 2.0, 6.0));
|
test.expect(t, a*b == v3f(0.0, 2.0, 6.0));
|
||||||
Expect(a/b == v3f(0.0, 0.5, 0.66666667));
|
test.expect(t, a/b == v3f(0.0, 0.5, 0.66666667));
|
||||||
|
|
||||||
a = v3f(1.0, 1.0, 0.0);
|
a = v3f(1.0, 1.0, 0.0);
|
||||||
b = v3f(1.0, 0.0, 0.0);
|
b = v3f(1.0, 0.0, 0.0);
|
||||||
Expect(reflect(a, b) == v3f(1.0, -1.0, 0.0));
|
test.expect(t, reflect(a, b) == v3f(1.0, -1.0, 0.0));
|
||||||
Expect(round(v3f(1.2, 1.7, 1.5)) == v3f(1.0, 2.0, 2.0));
|
test.expect(t, round(v3f(1.2, 1.7, 1.5)) == v3f(1.0, 2.0, 2.0));
|
||||||
Expect(round(v3f(-1.2, -1.7, -1.5)) == v3f(-1.0, -2.0, -2.0));
|
test.expect(t, round(v3f(-1.2, -1.7, -1.5)) == v3f(-1.0, -2.0, -2.0));
|
||||||
|
|
||||||
a = v3f(1.0, 0.0, 0.0);
|
a = v3f(1.0, 0.0, 0.0);
|
||||||
b = v3f(0.0, 1.0, 0.0);
|
b = v3f(0.0, 1.0, 0.0);
|
||||||
Expect(cross(a, b) == v3f(0.0, 0.0, 1.0));
|
test.expect(t, cross(a, b) == v3f(0.0, 0.0, 1.0));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
a: Vec3 = v3f(2.3, 4.1, 9.0);
|
a: Vec3 = v3f(2.3, 4.1, 9.0);
|
||||||
b: Vec3 = v3f(1.0, -3.6, 5.0);
|
b: Vec3 = v3f(1.0, -3.6, 5.0);
|
||||||
c := min(a, b);
|
c := min(a, b);
|
||||||
Expect(c == v3f(1.0, -3.6, 5.0));
|
test.expect(t, c == v3f(1.0, -3.6, 5.0));
|
||||||
c = max(a, b);
|
c = max(a, b);
|
||||||
Expect(c == v3f(2.3, 4.1, 9.0));
|
test.expect(t, c == v3f(2.3, 4.1, 9.0));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Test(basic.tprint("%: Vec4", UNITS), t => {
|
test.run(basic.tprint("%: Vec4", UNITS), t => {
|
||||||
a: Vec4 = v4f(2.25, 1.0, 2.0, 1.0);
|
a: Vec4 = v4f(2.25, 1.0, 2.0, 1.0);
|
||||||
b: Vec4 = v4f(4.0, 2.0, 3.0, 1.0);
|
b: Vec4 = v4f(4.0, 2.0, 3.0, 1.0);
|
||||||
Expect(a + b == v4f(6.25, 3.0, 5.0, 2.0));
|
test.expect(t, a + b == v4f(6.25, 3.0, 5.0, 2.0));
|
||||||
Expect(b - a == v4f(1.75, 1.0, 1.0, 0.0));
|
test.expect(t, b - a == v4f(1.75, 1.0, 1.0, 0.0));
|
||||||
Expect(a*b == v4f(9.0, 2.0, 6.0, 1.0));
|
test.expect(t, a*b == v4f(9.0, 2.0, 6.0, 1.0));
|
||||||
Expect(a/b == v4f(0.5625, 0.5, 2.0/3.0, 1.0));
|
test.expect(t, a/b == v4f(0.5625, 0.5, 2.0/3.0, 1.0));
|
||||||
});
|
});
|
||||||
|
|
||||||
Test(basic.tprint("%: VecN", UNITS), t => {
|
test.run(basic.tprint("%: VecN", UNITS), t => {
|
||||||
a: Vec(16, float);
|
a: Vec(16, float);
|
||||||
b: Vec(16, float);
|
b: Vec(16, float);
|
||||||
for *a {
|
for *a {
|
||||||
|
|
@ -737,59 +739,59 @@ same_dir :: (a: Vec3, b: Vec3) -> bool {
|
||||||
for *b {
|
for *b {
|
||||||
it.* = xx(it_index + 1);
|
it.* = xx(it_index + 1);
|
||||||
}
|
}
|
||||||
Expect(a + b == Vec(16, float).{.[1.0, 3.0, 5.0, 7.0, 9.0, 11.0, 13.0, 15.0, 17.0, 19.0, 21.0, 23.0, 25.0, 27.0, 29.0, 31.0]});
|
test.expect(t, a + b == Vec(16, float).{.[1.0, 3.0, 5.0, 7.0, 9.0, 11.0, 13.0, 15.0, 17.0, 19.0, 21.0, 23.0, 25.0, 27.0, 29.0, 31.0]});
|
||||||
Expect(b - a == Vec(16, float).{.[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]});
|
test.expect(t, b - a == Vec(16, float).{.[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]});
|
||||||
Expect(a*b == Vec(16, float).{.[0.0, 2.0, 6.0, 12.0, 20.0, 30.0, 42.0, 56.0, 72.0, 90.0, 110.0, 132.0, 156.0, 182.0, 210.0, 240.0]});
|
test.expect(t, a*b == Vec(16, float).{.[0.0, 2.0, 6.0, 12.0, 20.0, 30.0, 42.0, 56.0, 72.0, 90.0, 110.0, 132.0, 156.0, 182.0, 210.0, 240.0]});
|
||||||
|
|
||||||
Expect(min(a, b) == a);
|
test.expect(t, min(a, b) == a);
|
||||||
Expect(max(a, b) == b);
|
test.expect(t, max(a, b) == b);
|
||||||
Expect(max(a, 12) == Vec(16, float).{.[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 12.0, 12.0, 12.0]});
|
test.expect(t, max(a, 12) == Vec(16, float).{.[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 12.0, 12.0, 12.0]});
|
||||||
Expect(min(a, 13.2) == Vec(16, float).{.[13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 14.0, 15.0]});
|
test.expect(t, min(a, 13.2) == Vec(16, float).{.[13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 14.0, 15.0]});
|
||||||
Expect(clamp(a, 7.25, 12.0) == Vec(16, float).{.[7.25, 7.25, 7.25, 7.25, 7.25, 7.25, 7.25, 7.25, 8, 9, 10, 11, 12, 12, 12, 12]});
|
test.expect(t, clamp(a, 7.25, 12.0) == Vec(16, float).{.[7.25, 7.25, 7.25, 7.25, 7.25, 7.25, 7.25, 7.25, 8, 9, 10, 11, 12, 12, 12, 12]});
|
||||||
|
|
||||||
a1: Vec(16, float) = Vec(16, float).{.[1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 4.7, -1.2, -1.0, -1.5, 11.2, 14.0, 15.0, 14.0, 15.0, 65536.2]};
|
a1: Vec(16, float) = Vec(16, float).{.[1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 4.7, -1.2, -1.0, -1.5, 11.2, 14.0, 15.0, 14.0, 15.0, 65536.2]};
|
||||||
Expect(ceil(a1) == Vec(16, float).{.[2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 5.0, -1.0, -1.0, -1.0, 12.0, 14.0, 15.0, 14.0, 15.0, 65537]});
|
test.expect(t, ceil(a1) == Vec(16, float).{.[2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 5.0, -1.0, -1.0, -1.0, 12.0, 14.0, 15.0, 14.0, 15.0, 65537]});
|
||||||
Expect(floor(a1) == Vec(16, float).{.[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 4.0, -2.0, -1.0, -2.0, 11.0, 14.0, 15.0, 14.0, 15.0, 65536]});
|
test.expect(t, floor(a1) == Vec(16, float).{.[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 4.0, -2.0, -1.0, -2.0, 11.0, 14.0, 15.0, 14.0, 15.0, 65536]});
|
||||||
|
|
||||||
Expect(dot(a, b) == 1360.0);
|
test.expect(t, dot(a, b) == 1360.0);
|
||||||
Expect(abs(a) == a);
|
test.expect(t, abs(a) == a);
|
||||||
|
|
||||||
c := a;
|
c := a;
|
||||||
for *c { // Check making every other component negative
|
for *c { // Check making every other component negative
|
||||||
if it_index%2 == 0 then it.* = -it.*;
|
if it_index%2 == 0 then it.* = -it.*;
|
||||||
}
|
}
|
||||||
|
|
||||||
Expect(abs(c) == a);
|
test.expect(t, abs(c) == a);
|
||||||
Expect(float_eq(length(normalize(a)), 1));
|
test.expect(t, float_eq(length(normalize(a)), 1));
|
||||||
Expect(a + 2 == Vec(16, float).{.[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]});
|
test.expect(t, a + 2 == Vec(16, float).{.[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]});
|
||||||
Expect(a - 2 == Vec(16, float).{.[-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]});
|
test.expect(t, a - 2 == Vec(16, float).{.[-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]});
|
||||||
Expect(a*2 == Vec(16, float).{.[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]});
|
test.expect(t, a*2 == Vec(16, float).{.[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]});
|
||||||
});
|
});
|
||||||
|
|
||||||
Test(basic.tprint("%: Quat", UNITS), t => {
|
test.run(basic.tprint("%: Quat", UNITS), t => {
|
||||||
qi := quat_identity;
|
qi := quat_identity;
|
||||||
q: Quat = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0));
|
q: Quat = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0));
|
||||||
q2: Quat = rotation_quat(from_rad(PI), v3f(1.0, 0.0, 1.0));
|
q2: Quat = rotation_quat(from_rad(PI), v3f(1.0, 0.0, 1.0));
|
||||||
qc := conjugate(q);
|
qc := conjugate(q);
|
||||||
inv_q := inverse(q);
|
inv_q := inverse(q);
|
||||||
Expect(q*inv_q == qi);
|
test.expect(t, q*inv_q == qi);
|
||||||
|
|
||||||
q1 := quat(2, 0, 0, 0);
|
q1 := quat(2, 0, 0, 0);
|
||||||
q2 = quat(1, 1, -1, 0);
|
q2 = quat(1, 1, -1, 0);
|
||||||
c := q1*q2*conjugate(q1);
|
c := q1*q2*conjugate(q1);
|
||||||
Expect(float_eq(c.w, 0.0));
|
test.expect(t, float_eq(c.w, 0.0));
|
||||||
|
|
||||||
q = rotation_quat(from_rad(PI/4.0), v3f(0.0, 0.0, 1.0));
|
q = rotation_quat(from_rad(PI/4.0), v3f(0.0, 0.0, 1.0));
|
||||||
p := v3f(2.0, 0.0, 0.0);
|
p := v3f(2.0, 0.0, 0.0);
|
||||||
c1 := q*quat(p, 0)*conjugate(q);
|
c1 := q*quat(p, 0)*conjugate(q);
|
||||||
c2 := q*p;
|
c2 := q*p;
|
||||||
Expect(v3_eq(c2, v3f(math.sqrt(2.0), math.sqrt(2.0), 0.0)));
|
test.expect(t, v3_eq(c2, v3f(math.sqrt(2.0), math.sqrt(2.0), 0.0)));
|
||||||
Expect(v3_eq(c1.xyz, c2));
|
test.expect(t, v3_eq(c1.xyz, c2));
|
||||||
|
|
||||||
q = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0));
|
q = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0));
|
||||||
m := rotation_mat4(q);
|
m := rotation_mat4(q);
|
||||||
p1 := v4f(2.0, 0.0, 0.0, 1.0);
|
p1 := v4f(2.0, 0.0, 0.0, 1.0);
|
||||||
Expect(v4_eq(m*p1, v4f(-2.0, 0.0, -0.0, 1.0)));
|
test.expect(t, v4_eq(m*p1, v4f(-2.0, 0.0, -0.0, 1.0)));
|
||||||
|
|
||||||
q1 = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0));
|
q1 = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0));
|
||||||
q2 = rotation_quat(from_rad(2.0*PI), v3f(0.0, 1.0, 0.0));
|
q2 = rotation_quat(from_rad(2.0*PI), v3f(0.0, 1.0, 0.0));
|
||||||
|
|
|
||||||
139
memory/allocators.jai
Normal file
139
memory/allocators.jai
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
#module_parameters(RUN_TESTS := false);
|
||||||
|
|
||||||
|
make_crash_allocator :: () -> Allocator {
|
||||||
|
return .{ proc = crash_allocator_proc };
|
||||||
|
}
|
||||||
|
|
||||||
|
crash_allocator_proc :: (mode: Allocator_Mode, size: s64, old_size: s64, old_memory: rawptr, allocator_data: rawptr) -> rawptr {
|
||||||
|
message: string;
|
||||||
|
|
||||||
|
if mode == {
|
||||||
|
case .ALLOCATE;
|
||||||
|
message = basic.tprint("Attempt to allocate % byte(s) using the crash allocator!", size);
|
||||||
|
case .RESIZE;
|
||||||
|
message = basic.tprint("Attempt to resize (from % to % byte(s)) using the crash allocator!", old_size, size);
|
||||||
|
case .FREE;
|
||||||
|
message = basic.tprint("Attempt to free % byte(s) using the crash allocator!", size);
|
||||||
|
}
|
||||||
|
|
||||||
|
loc := meta.get_stack_trace_caller_location();
|
||||||
|
basic.assert(false, message, loc = loc);
|
||||||
|
debug_break();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Arena :: struct {
|
||||||
|
memory: rawptr;
|
||||||
|
memory_size: u64;
|
||||||
|
offset: u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
init_arena :: (a: *Arena, memory: rawptr, size: u64) {
|
||||||
|
a.memory = memory;
|
||||||
|
a.memory_size = size;
|
||||||
|
a.offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
make_arena_allocator :: (arena: *Arena) -> Allocator {
|
||||||
|
return .{
|
||||||
|
data = arena,
|
||||||
|
proc = xx arena_allocator_proc,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Extended_Allocator_Mode :: enum {
|
||||||
|
using Allocator_Mode;
|
||||||
|
|
||||||
|
request_memory :: Allocator_Mode.ALLOCATE;
|
||||||
|
resize_memory :: Allocator_Mode.RESIZE;
|
||||||
|
release_memory :: Allocator_Mode.FREE;
|
||||||
|
first_time_used :: Allocator_Mode.STARTUP;
|
||||||
|
release_everything :: Allocator_Mode.SHUTDOWN;
|
||||||
|
|
||||||
|
reset_state;
|
||||||
|
save_point; // should return *s64
|
||||||
|
restore_save_point; // 'old_size' will be the dereferenced return value of 'save_point'
|
||||||
|
}
|
||||||
|
|
||||||
|
arena_allocator_proc :: (mode: Extended_Allocator_Mode, size: s64, old_size: s64, old_memory: rawptr, allocator_data: rawptr) -> rawptr {
|
||||||
|
arena := allocator_data.(*Arena);
|
||||||
|
if mode == {
|
||||||
|
case .request_memory;
|
||||||
|
return arena_alloc(arena, size);
|
||||||
|
|
||||||
|
case .resize_memory;
|
||||||
|
if old_memory == null {
|
||||||
|
return arena_alloc(arena, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if size == 0 {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if size == old_size {
|
||||||
|
return old_memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_memory := arena_alloc(arena, size);
|
||||||
|
memcpy(new_memory, old_memory, old_size);
|
||||||
|
return new_memory;
|
||||||
|
|
||||||
|
case .save_point;
|
||||||
|
return *arena.offset;
|
||||||
|
|
||||||
|
case .restore_save_point;
|
||||||
|
arena.offset = old_size.(u64);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
arena_alloc :: (a: *Arena, count: int, alignment := mem.Default_Align, loc := #caller_location) -> rawptr {
|
||||||
|
basic.assert(a.memory != null, "arena: not initialized", loc = loc);
|
||||||
|
basic.assert(mem.power_of_two(alignment));
|
||||||
|
|
||||||
|
end := a.memory.(*u8) + a.offset;
|
||||||
|
ptr := mem.align_to(end.(int), alignment);
|
||||||
|
total_size := (count + ptr.(*u8) - end.(*u8)).(u64);
|
||||||
|
|
||||||
|
basic.assert(a.offset + total_size <= a.memory_size, "arena: out of memory", loc = loc);
|
||||||
|
a.offset += total_size;
|
||||||
|
|
||||||
|
return ptr.(rawptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#scope_file;
|
||||||
|
|
||||||
|
#import "jc";
|
||||||
|
|
||||||
|
mem :: #import "jc/memory";
|
||||||
|
meta :: #import "jc/meta";
|
||||||
|
|
||||||
|
basic :: #import "Basic"; // @future
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
// TESTS
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
|
||||||
|
#if RUN_TESTS #run {
|
||||||
|
test :: #import "jc/meta/test";
|
||||||
|
|
||||||
|
test.run("arena:basic", t => {
|
||||||
|
memory := mem.request_memory(1 * mem.Kilobyte);
|
||||||
|
defer mem.release_memory(memory);
|
||||||
|
|
||||||
|
arena: Arena;
|
||||||
|
init_arena(*arena, memory, 1 * mem.Kilobyte);
|
||||||
|
|
||||||
|
context.allocator = make_arena_allocator(*arena);
|
||||||
|
save_point := mem.allocator_save();
|
||||||
|
|
||||||
|
i := mem.request_memory(int);
|
||||||
|
basic.assert(i != null);
|
||||||
|
basic.assert(arena.offset == size_of(int));
|
||||||
|
mem.allocator_restore(save_point);
|
||||||
|
});
|
||||||
|
}
|
||||||
51
memory/buffer.jai
Normal file
51
memory/buffer.jai
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
#module_parameters(RUN_TESTS := false);
|
||||||
|
|
||||||
|
Buffer :: struct {
|
||||||
|
allocator: Allocator;
|
||||||
|
|
||||||
|
data: [..]byte;
|
||||||
|
count: int;
|
||||||
|
}
|
||||||
|
|
||||||
|
append :: inline (buf: *Buffer, ptr: rawptr, size: int) {
|
||||||
|
inline mem.lazy_set_allocator(buf);
|
||||||
|
|
||||||
|
free_space := ensure_buffer_has_room(buf, size);
|
||||||
|
memcpy(free_space, ptr, size);
|
||||||
|
buf.size += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
append :: inline (buf: *Buffer, data: []byte) {
|
||||||
|
append(buf, data.data, data.count);
|
||||||
|
}
|
||||||
|
|
||||||
|
append :: inline (buf: *Buffer, ptr: *$T) {
|
||||||
|
append(buf, ptr, size_of(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
reset :: inline (buf: *Buffer) {
|
||||||
|
buf.count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#scope_file;
|
||||||
|
|
||||||
|
#import "jc";
|
||||||
|
|
||||||
|
array :: #import "jc/array";
|
||||||
|
mem :: #import "jc/memory";
|
||||||
|
meta :: #import "jc/meta";
|
||||||
|
|
||||||
|
ensure_buffer_has_room :: (buf: *Buffer, count: int) -> *u8 {
|
||||||
|
ptr := buf.data.data + buf.count;
|
||||||
|
if buf.count + count >= buf.data.allocated {
|
||||||
|
array.resize(*buf.data, buf.data.allocated * 2);
|
||||||
|
ptr = buf.data.data + buf.count;
|
||||||
|
buf.data.count = buf.data.allocated;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if RUN_TESTS #run {
|
||||||
|
test :: #import "jc/meta/test";
|
||||||
|
}
|
||||||
223
memory/module.jai
Normal file
223
memory/module.jai
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
#module_parameters(RUN_TESTS := false, WITH_SUBMODULES := true);
|
||||||
|
|
||||||
|
#scope_export;
|
||||||
|
|
||||||
|
Kilobyte :: 1024;
|
||||||
|
Megabyte :: 1024 * Kilobyte;
|
||||||
|
Gigabyte :: 1024 * Megabyte;
|
||||||
|
|
||||||
|
Default_Align :: #run 2 * align_of(*void);
|
||||||
|
|
||||||
|
align_of :: ($T: Type) -> int #expand {
|
||||||
|
return #run -> int {
|
||||||
|
info := type_info(struct{ p: u8; t: T; });
|
||||||
|
return info.members[1].offset_in_bytes.(int);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
default_of :: ($T: Type) -> T #expand {
|
||||||
|
default: T;
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
undefined_of :: ($T: Type) -> T #expand {
|
||||||
|
uninit: T = ---;
|
||||||
|
return uninit;
|
||||||
|
}
|
||||||
|
|
||||||
|
zero_of :: ($T: Type) -> T #expand {
|
||||||
|
zero := undefined_of(T);
|
||||||
|
memset(*zero, 0, size_of(T));
|
||||||
|
return zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
bitcast :: ($T: Type, expr: Code) -> T #expand {
|
||||||
|
value := expr;
|
||||||
|
return (*value).(*T).*;
|
||||||
|
}
|
||||||
|
|
||||||
|
power_of_two :: (x: int) -> bool {
|
||||||
|
if x == 0 return false;
|
||||||
|
return x & (x - 1) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
next_power_of_two :: (x: int) -> int #no_aoc {
|
||||||
|
basic.assert(power_of_two(x), "value (%) must be a power of two", x);
|
||||||
|
|
||||||
|
// Bit twiddling hacks next power of two
|
||||||
|
x |= x >> 1;
|
||||||
|
x |= x >> 2;
|
||||||
|
x |= x >> 4;
|
||||||
|
x |= x >> 8;
|
||||||
|
x |= x >> 16;
|
||||||
|
x |= x >> 32;
|
||||||
|
|
||||||
|
return x + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
align_to :: (ptr: int, align: int = Default_Align) -> int {
|
||||||
|
basic.assert(power_of_two(align), "alignment must be a power of two");
|
||||||
|
|
||||||
|
p := ptr;
|
||||||
|
mod := p & (align - 1);
|
||||||
|
if mod != 0 then p += align - mod;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
init_or_zero :: inline (ptr: *$T, custom_init: (*T) = null) {
|
||||||
|
if custom_init != null {
|
||||||
|
custom_init(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
initializer :: initializer_of(T);
|
||||||
|
#if initializer {
|
||||||
|
inline initializer(ptr);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
memset(ptr, 0, size_of(T));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allocator_reset :: () {
|
||||||
|
allocator := context.allocator;
|
||||||
|
allocator.proc(xx Extended_Allocator_Mode.reset_state, 0, 0, null, allocator.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
allocator_save :: () -> int {
|
||||||
|
allocator := context.allocator;
|
||||||
|
return allocator.proc(xx Extended_Allocator_Mode.save_point, 0, 0, null, allocator.data).(*int).*;
|
||||||
|
}
|
||||||
|
|
||||||
|
allocator_restore :: (save_point: int) {
|
||||||
|
allocator := context.allocator;
|
||||||
|
allocator.proc(xx Extended_Allocator_Mode.restore_save_point, 0, save_point, null, allocator.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
request_memory :: (size: int, align := Default_Align) -> rawptr {
|
||||||
|
allocator := context.allocator;
|
||||||
|
aligned_size := align_to(size, align);
|
||||||
|
return allocator.proc(xx Extended_Allocator_Mode.request_memory, aligned_size.(int), 0, null, allocator.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
request_memory :: ($T: Type, reserved := 0, $init := true) -> [..]T
|
||||||
|
#modify {
|
||||||
|
ok, info := meta.type_is_array(T);
|
||||||
|
if ok && info.array_type == .RESIZABLE {
|
||||||
|
T = compiler.get_type(info.element_type);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
size := size_of(T) * basic.max(reserved, 0);
|
||||||
|
data := request_memory(size, align_of(T)).(*T);
|
||||||
|
#if init if size != 0 {
|
||||||
|
memset(data, 0, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
arr: [..]T;
|
||||||
|
arr.data = data;
|
||||||
|
arr.count = 0;
|
||||||
|
arr.allocated = size / size_of(T);
|
||||||
|
arr.allocator = context.allocator;
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
request_memory :: ($T: Type, $init := true) -> *T
|
||||||
|
#modify { return !meta.type_is_array(T); }
|
||||||
|
{
|
||||||
|
ptr := request_memory(size_of(T), align_of(T)).(*T);
|
||||||
|
#if init init_or_zero(ptr);
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
release_memory :: inline (ptr: rawptr) {
|
||||||
|
allocator := context.allocator;
|
||||||
|
allocator.proc(xx Extended_Allocator_Mode.release_memory, 0, 0, ptr, allocator.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
release_memory :: inline (arr: []$T) {
|
||||||
|
release_memory(arr.data.(*rawptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
release_memory :: inline (arr: [..]$T) {
|
||||||
|
release_memory(arr.data.(*rawptr),, allocator = arr.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
release_memory :: inline (str: string) {
|
||||||
|
release_memory(str.data.(*rawptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
// @todo(judah): why can't we use $T/struct{ allocator: Allocator }?
|
||||||
|
lazy_set_allocator :: (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 allocator field or dynamic array";
|
||||||
|
} #expand {
|
||||||
|
if thing.allocator.proc == null {
|
||||||
|
thing.allocator = allocator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_set_allocator :: (array: *[..]$T, allocator := context.allocator) #expand {
|
||||||
|
if array.allocator.proc == null {
|
||||||
|
array.allocator = allocator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if WITH_SUBMODULES {
|
||||||
|
using #import "jc/memory/allocators"(RUN_TESTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#scope_file;
|
||||||
|
|
||||||
|
#import "jc";
|
||||||
|
|
||||||
|
meta :: #import "jc/meta";
|
||||||
|
|
||||||
|
basic :: #import "Basic"; // @future
|
||||||
|
compiler :: #import "Compiler"; // @future
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
// TESTS
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
|
||||||
|
#if RUN_TESTS #run {
|
||||||
|
test :: #import "jc/meta/test";
|
||||||
|
|
||||||
|
test.run("request_memory:dynamic arrays", (t) => {
|
||||||
|
a1 := request_memory([..]int);
|
||||||
|
defer release_memory(a1);
|
||||||
|
|
||||||
|
test.expect(t, a1.count == 0);
|
||||||
|
test.expect(t, a1.allocated == 0);
|
||||||
|
|
||||||
|
basic.array_add(*a1, 10, 20, 30);
|
||||||
|
test.expect(t, a1.count == 3);
|
||||||
|
test.expect(t, a1.allocated != 0, "%", a1.allocated);
|
||||||
|
|
||||||
|
a2 := request_memory([..]int, 8);
|
||||||
|
defer release_memory(a2);
|
||||||
|
|
||||||
|
test.expect(t, a2.count == 0);
|
||||||
|
test.expect(t, a2.allocated == 8);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.run("request_memory:values", (t) => {
|
||||||
|
v1 := request_memory(int);
|
||||||
|
defer release_memory(v1);
|
||||||
|
|
||||||
|
test.expect(t, v1.* == 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
287
meta/macros.jai
Normal file
287
meta/macros.jai
Normal file
|
|
@ -0,0 +1,287 @@
|
||||||
|
#module_parameters(RUN_TESTS := false);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Allows structs to be copied and assigned inline.
|
||||||
|
|
||||||
|
For example, copying a Vector3 while changing a single value:
|
||||||
|
old := Vector3.{ 10, 20, 30 }; // 10, 20, 30
|
||||||
|
new := with(old, .{ y = -20 }); // 10, -20, 30
|
||||||
|
*/
|
||||||
|
with :: (old: $T, $fields: Code, location := #caller_location) -> T
|
||||||
|
#modify { return T.(*Type_Info).type == .STRUCT, "with can only be used on structs"; }
|
||||||
|
#expand {
|
||||||
|
#insert,scope() -> string {
|
||||||
|
using compiler;
|
||||||
|
|
||||||
|
ensure :: (cond: bool, message: string, args: ..Any, loc := location) {
|
||||||
|
if !cond compiler_report(basic.tprint(message, ..args), loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
b: basic.String_Builder;
|
||||||
|
root := compiler_get_nodes(fields).(*Code_Literal);
|
||||||
|
ensure(root.kind == .LITERAL, "argument must be a struct literal");
|
||||||
|
ensure(root.value_type == .STRUCT, "argument must be a struct literal");
|
||||||
|
|
||||||
|
t_info := T.(*Type_Info_Struct);
|
||||||
|
|
||||||
|
// Ensure the literal we were given is of type T so this operator is more predictable.
|
||||||
|
// i.e. disallowing Vector3.{} | SomeRandomType.{ x = 10 };
|
||||||
|
n_type := root.struct_literal_info.type_expression;
|
||||||
|
if n_type != null {
|
||||||
|
tmp: basic.String_Builder;
|
||||||
|
pp.print_expression(*tmp, n_type);
|
||||||
|
n_typename := basic.builder_to_string(*tmp);
|
||||||
|
ensure(n_type.result == t_info, "mismatched types, % vs. %", t_info.name, n_typename);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @note(judah): if the value we're trying to copy is a constant,
|
||||||
|
// we have to create a local so we can assign to the fields.
|
||||||
|
// Doing this allows this usecase: 'with(Default_Entity, .{ kind = .Player })'
|
||||||
|
receiver := "old";
|
||||||
|
#if is_constant(old) {
|
||||||
|
receiver = "copy";
|
||||||
|
basic.print_to_builder(*b, "% := old;\n", receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
for root.struct_literal_info.arguments {
|
||||||
|
op := it.(*Code_Binary_Operator);
|
||||||
|
ident := op.left.(*Code_Ident);
|
||||||
|
|
||||||
|
ensure(op.kind == .BINARY_OPERATOR, "copy-assign requires named fields", loc = make_location(op));
|
||||||
|
ensure(op.operator_type == #char "=", "copy-assign requires named field assignment", loc = make_location(op));
|
||||||
|
ensure(ident.kind == .IDENT, "must be an identifier", loc = make_location(op));
|
||||||
|
|
||||||
|
// Catch any incorrect field assignments before the compiler does for better error reporting
|
||||||
|
exists := false;
|
||||||
|
for t_info.members if it.name == ident.name exists = true;
|
||||||
|
ensure(exists, "field % does not exist within %", ident.name, t_info.name, loc = make_location(ident));
|
||||||
|
|
||||||
|
// receiver.field = value;
|
||||||
|
basic.append(*b, receiver);
|
||||||
|
basic.append(*b, ".");
|
||||||
|
pp.print_expression(*b, op);
|
||||||
|
basic.append(*b, ";\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
basic.print_to_builder(*b, "return %;\n", receiver);
|
||||||
|
return basic.builder_to_string(*b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @note(judah): I like doing this with an operator, but your mileage may vary
|
||||||
|
operator | :: with;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Creates a named block that can exit early (via 'break' or 'continue').
|
||||||
|
|
||||||
|
This mostly replaces the case where you'd like to jump to
|
||||||
|
the end of a scope based on some logic within. Without
|
||||||
|
gotos, this is the next best thing.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
// within a loop
|
||||||
|
for this_block() { // this is named 'block' by default
|
||||||
|
if !moving break;
|
||||||
|
// do movement here
|
||||||
|
}
|
||||||
|
for this_block("render_player") {
|
||||||
|
if invisible break render_player;
|
||||||
|
// do rendering here
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
this_block :: ($name: string = Default_Name) -> Named_Block(name) #expand { return .{}; }
|
||||||
|
|
||||||
|
/*
|
||||||
|
Drop-in loop unrolling macro.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
for unroll(5) {
|
||||||
|
// duplicates this body 5 times exactly
|
||||||
|
}
|
||||||
|
|
||||||
|
known_size: [3]float;
|
||||||
|
for unroll(known_size) {
|
||||||
|
// duplicates this body 3 times exactly
|
||||||
|
}
|
||||||
|
|
||||||
|
var_size: []float;
|
||||||
|
for unroll(var_size) {
|
||||||
|
// duplicates this body a set number of times,
|
||||||
|
// falling back to a regular for loop to handle
|
||||||
|
// the remaining iterations.
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
unroll :: ($count: int) -> Unrolled_Loop(count) { return .{}; }
|
||||||
|
unroll :: (arr: [$N]$T) -> Unrolled_Loop(N, T) { return .{ array = arr }; }
|
||||||
|
unroll :: (arr: []$T) -> Unrolled_Loop(-1, T) { return .{ array = arr }; }
|
||||||
|
|
||||||
|
// Call #c_call procedures inline with the current context: 'c_call(some_c_call_proc(10, 20))'
|
||||||
|
c_call :: (call: Code) #expand {
|
||||||
|
push_context context { #insert,scope(call) call; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call #c_call procedures inline with a custom context: 'c_call(some_c_call_proc(10, 20), c_context)'
|
||||||
|
c_call :: (call: Code, ctx: #Context) #expand {
|
||||||
|
push_context ctx { #insert,scope(call) call; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// @note(judah): for_expansions have to be exported
|
||||||
|
|
||||||
|
for_expansion :: (v: *Named_Block, code: Code, _: For_Flags) #expand {
|
||||||
|
#insert #run basic.tprint(#string END
|
||||||
|
for `%: 0..0 {
|
||||||
|
`it :: #run mem.zero_of(void);
|
||||||
|
`it_index :: #run mem.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 Default_Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
for_expansion :: (loop: *Unrolled_Loop, body: Code, flags: For_Flags, loc := #caller_location) #expand {
|
||||||
|
// runtime unroll
|
||||||
|
#if loop.N == -1 {
|
||||||
|
for #v2 <=(flags & .REVERSE == .REVERSE) i: 0..loop.array.count - 1 {
|
||||||
|
#if flags & .POINTER {
|
||||||
|
`it := *(#no_abc loop.array[i]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
`it := #no_abc loop.array[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
`it_index := i;
|
||||||
|
#insert,scope(body) body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// compile-time unroll
|
||||||
|
else {
|
||||||
|
`it_index := 0;
|
||||||
|
|
||||||
|
#insert -> string {
|
||||||
|
b: basic.String_Builder;
|
||||||
|
basic.print_to_builder(*b, "// inserted unrolled loop (N = %) at %:%\n", loop.N, loc.fully_pathed_filename, loc.line_number);
|
||||||
|
|
||||||
|
if loop.T == void {
|
||||||
|
basic.append(*b, "`it: int = ---;\n");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
basic.append(*b, "`it: loop.T = ---;\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
for #v2 <=(flags & .REVERSE == .REVERSE) 0..loop.N - 1 {
|
||||||
|
basic.append(*b, "{\n");
|
||||||
|
|
||||||
|
if loop.T == void {
|
||||||
|
basic.print_to_builder(*b, "\tit = %;\n", it);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
#if flags & .POINTER {
|
||||||
|
basic.print_to_builder(*b, "\tit = *(#no_abc loop.array[%]);\n", it);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
basic.print_to_builder(*b, "\tit = #no_abc loop.array[%];\n", it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
basic.print_to_builder(*b, "\tit_index = %;\n", it);
|
||||||
|
basic.append(*b, "\t#insert,scope(body) body;\n");
|
||||||
|
basic.append(*b, "}\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return basic.builder_to_string(*b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Default_Name :: "block";
|
||||||
|
Named_Block :: struct(NAME: string) {}
|
||||||
|
|
||||||
|
Unrolled_Loop :: struct(N: int, T: Type = void) {
|
||||||
|
// Only store arrays when we absolutely have to.
|
||||||
|
#if T != void {
|
||||||
|
// @todo(judah): because this will only be created via 'unroll',
|
||||||
|
// should these be pointers to the underlying arrays so we don't
|
||||||
|
// pay for a copy?
|
||||||
|
#if N == -1 {
|
||||||
|
array: []T = ---;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
array: [N]T = ---;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#scope_file;
|
||||||
|
|
||||||
|
mem :: #import "jc/memory";
|
||||||
|
|
||||||
|
basic :: #import "Basic"; // @future
|
||||||
|
pp :: #import "Program_Print"; // @future
|
||||||
|
compiler :: #import "Compiler"; // @future
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
// TESTS
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
|
||||||
|
#if RUN_TESTS #run {
|
||||||
|
test :: #import "jc/meta/test";
|
||||||
|
|
||||||
|
test.run("this_block", (t) => {
|
||||||
|
i := 0;
|
||||||
|
|
||||||
|
for this_block() {
|
||||||
|
i += 1;
|
||||||
|
for this_block() {
|
||||||
|
break block;
|
||||||
|
}
|
||||||
|
|
||||||
|
for this_block() {
|
||||||
|
continue block;
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == 1 {
|
||||||
|
break block;
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
j := 0;
|
||||||
|
for this_block("named") {
|
||||||
|
for 0..10 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if i != 1 {
|
||||||
|
break named;
|
||||||
|
}
|
||||||
|
|
||||||
|
j = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
test.expect(t, i == 1, "i was %", i);
|
||||||
|
test.expect(t, j == 1, "j was %", j);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.run("copy assign", (t) => {
|
||||||
|
Value :: struct {
|
||||||
|
x: float;
|
||||||
|
y: float;
|
||||||
|
z: float;
|
||||||
|
}
|
||||||
|
|
||||||
|
a := Value.{ 10, 20, 30 };
|
||||||
|
b := with(a, .{ x = 1, z = 1 });
|
||||||
|
c := b | .{ y = 500 };
|
||||||
|
|
||||||
|
test.expect(t, b.x == 1, "was %", b.x);
|
||||||
|
test.expect(t, b.z == 1, "was %", b.z);
|
||||||
|
test.expect(t, c.y == 500, "was %", c.y);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
94
meta/module.jai
Normal file
94
meta/module.jai
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
#module_parameters(RUN_TESTS := false, WITH_SUBMODULES := true);
|
||||||
|
|
||||||
|
#scope_export;
|
||||||
|
|
||||||
|
get_stack_trace_caller_location :: (loc := #caller_location) -> Source_Code_Location {
|
||||||
|
if context.stack_trace == null || context.stack_trace.info == null {
|
||||||
|
return loc;
|
||||||
|
}
|
||||||
|
|
||||||
|
cur := context.stack_trace;
|
||||||
|
while cur != null {
|
||||||
|
if cur.info == null break;
|
||||||
|
|
||||||
|
if cur.info.location.fully_pathed_filename != loc.fully_pathed_filename {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cur = cur.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cur.info.location;
|
||||||
|
}
|
||||||
|
|
||||||
|
check_bounds :: ($$index: $T, $$count: T, loc := #caller_location) #expand {
|
||||||
|
MESSAGE :: "bounds check failed! index % (max %)";
|
||||||
|
#if is_constant(index) && is_constant(count) {
|
||||||
|
if index < 0 || index >= count {
|
||||||
|
message := basic.tprint(MESSAGE, index, count - 1);
|
||||||
|
compiler.compiler_report(message, mode = .ERROR, loc = loc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
basic.assert(index >= 0 && index < count, MESSAGE, index, count - 1, loc = loc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can be passed directly to using,map
|
||||||
|
remap_snake_to_pascal :: (names: []string) {
|
||||||
|
for names {
|
||||||
|
names[it_index] = snake_to_pascal(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
snake_to_pascal :: (name: string) -> string {
|
||||||
|
b: basic.String_Builder;
|
||||||
|
|
||||||
|
upper := true;
|
||||||
|
for i: 0..name.count - 1 {
|
||||||
|
c := name[i];
|
||||||
|
if c == #char "_" {
|
||||||
|
upper = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if upper {
|
||||||
|
basic.append(*b, basic.to_upper(c));
|
||||||
|
upper = false;
|
||||||
|
} else {
|
||||||
|
basic.append(*b, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return basic.builder_to_string(*b);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if WITH_SUBMODULES {
|
||||||
|
using #import "jc/meta/macros"(RUN_TESTS);
|
||||||
|
using #import "jc/meta/type_info"(RUN_TESTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
#scope_module;
|
||||||
|
|
||||||
|
mem :: #import "jc/memory";
|
||||||
|
|
||||||
|
basic :: #import "Basic"; // @future
|
||||||
|
compiler :: #import "Compiler"; // @future
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
// TESTS
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
|
||||||
|
#if RUN_TESTS #run {
|
||||||
|
test :: #import "jc/meta/test";
|
||||||
|
|
||||||
|
test.run("snake_to_pascal", t => {
|
||||||
|
test.expect(t, snake_to_pascal("some_name") == "SomeName");
|
||||||
|
test.expect(t, snake_to_pascal("_some_name") == "SomeName");
|
||||||
|
test.expect(t, snake_to_pascal("some__name") == "SomeName");
|
||||||
|
test.expect(t, snake_to_pascal("some_name_") == "SomeName");
|
||||||
|
test.expect(t, snake_to_pascal("X_Y_Z") == "XYZ");
|
||||||
|
test.expect(t, snake_to_pascal("XY_Z") == "XYZ");
|
||||||
|
});
|
||||||
|
}
|
||||||
89
meta/test/module.jai
Normal file
89
meta/test/module.jai
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
A very simple test runner that can be used at compile-time.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
test :: #import "jx/test";
|
||||||
|
|
||||||
|
#if RUN_TESTS #run {
|
||||||
|
test.run("collection of tests", t => {
|
||||||
|
test.expect(t, some_condition, "error message: %", value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
T :: struct {
|
||||||
|
location: Source_Code_Location;
|
||||||
|
expects_run: s64;
|
||||||
|
expects_ok: s64;
|
||||||
|
failed: bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
Proc :: #type (t: *T);
|
||||||
|
|
||||||
|
expect :: (t: *T, cond: bool, message := "", args: ..Any, loc := #caller_location) {
|
||||||
|
t.expects_run += 1;
|
||||||
|
if cond {
|
||||||
|
t.expects_ok += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := "expectation failed";
|
||||||
|
if message.count != 0 {
|
||||||
|
msg = basic.tprint(message, ..args);
|
||||||
|
}
|
||||||
|
|
||||||
|
t.failed = true;
|
||||||
|
if #compile_time {
|
||||||
|
compiler.compiler_report(msg, loc = loc, mode = .ERROR_CONTINUABLE);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
basic.assert(false, msg, loc = loc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run :: (name: string, proc: Proc, loc := #caller_location) {
|
||||||
|
// @note(judah): incredibly dumb way to get nicer test runs
|
||||||
|
path := 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
basic.print("%,%: %...", path, loc.line_number, name);
|
||||||
|
|
||||||
|
t: T;
|
||||||
|
proc(*t);
|
||||||
|
|
||||||
|
if t.failed {
|
||||||
|
basic.print(" failed!\n");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
basic.print(" ok!\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#scope_file;
|
||||||
|
|
||||||
|
basic :: #import "Basic"; // @future
|
||||||
|
strings :: #import "String"; // @future
|
||||||
|
compiler :: #import "Compiler"; // @future
|
||||||
244
meta/type_info.jai
Normal file
244
meta/type_info.jai
Normal file
|
|
@ -0,0 +1,244 @@
|
||||||
|
#module_parameters(RUN_TESTS := false);
|
||||||
|
|
||||||
|
// These return the bool first so you can check in a conditional,
|
||||||
|
// rather than having to do '_, ok := ...'
|
||||||
|
|
||||||
|
check_type_tag :: ($$T: Type, tag: Type_Info_Tag) -> bool, *Type_Info {
|
||||||
|
#if is_constant(T) {
|
||||||
|
info :: type_info(T);
|
||||||
|
if info.type == tag return true, info;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
info := T.(*Type_Info);
|
||||||
|
if info.type == tag return true, info;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, null;
|
||||||
|
}
|
||||||
|
|
||||||
|
type_is_integer :: ($$T: Type) -> bool, *Type_Info_Integer {
|
||||||
|
ok, info := check_type_tag(T, .INTEGER);
|
||||||
|
return ok, info.(*Type_Info_Integer);
|
||||||
|
}
|
||||||
|
|
||||||
|
type_is_float :: ($$T: Type) -> bool, *Type_Info_Float {
|
||||||
|
ok, info := check_type_tag(T, .FLOAT);
|
||||||
|
return ok, info.(*Type_Info_Float);
|
||||||
|
}
|
||||||
|
|
||||||
|
type_is_scalar :: (t: Type) -> bool {
|
||||||
|
return type_is_integer(t) || type_is_float(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
type_is_array :: ($$T: Type) -> bool, *Type_Info_Array {
|
||||||
|
ok, info := check_type_tag(T, .ARRAY);
|
||||||
|
return ok, info.(*Type_Info_Array);
|
||||||
|
}
|
||||||
|
|
||||||
|
type_is_struct :: ($$T: Type) -> bool, *Type_Info_Struct {
|
||||||
|
ok, info := check_type_tag(T, .STRUCT);
|
||||||
|
return ok, info.(*Type_Info_Struct);
|
||||||
|
}
|
||||||
|
|
||||||
|
type_is_enum :: ($$T: Type) -> bool, *Type_Info_Enum {
|
||||||
|
ok, info := check_type_tag(T, .ENUM);
|
||||||
|
return ok, info.(*Type_Info_Enum);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the lowest and highest values T can represent.
|
||||||
|
// Note: T must be an integer, float, or enum type.
|
||||||
|
range_for :: ($T: Type, loc := #caller_location) -> (T, T) #expand {
|
||||||
|
// @note(judah): we need to runs here because jai is weird.
|
||||||
|
return #run lo_for(T, loc = loc), #run hi_for(T, loc = loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the lowest value T can represent.
|
||||||
|
// Note: T must be an integer, float, or enum type.
|
||||||
|
lo_for :: ($T: Type, loc := #caller_location) -> T #expand {
|
||||||
|
return #run -> T {
|
||||||
|
info := T.(*Type_Info);
|
||||||
|
if info.type == {
|
||||||
|
case .INTEGER;
|
||||||
|
i := info.(*Type_Info_Integer);
|
||||||
|
if i.runtime_size == {
|
||||||
|
case 1; return (ifx i.signed then -0x80 else 0).(T, no_check);
|
||||||
|
case 2; return (ifx i.signed then -0x8000 else 0).(T, no_check);
|
||||||
|
case 4; return (ifx i.signed then -0x8000_0000 else 0).(T, no_check);
|
||||||
|
case 8; return (ifx i.signed then -0x8000_0000_0000_0000 else 0).(T, no_check);
|
||||||
|
case;
|
||||||
|
compiler.compiler_report("unhandled integer type", loc = loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
case .FLOAT;
|
||||||
|
if info.runtime_size == {
|
||||||
|
case 4; return (0h00800000).(T, no_check);
|
||||||
|
case 8; return (0h00100000_00000000).(T, no_check);
|
||||||
|
case;
|
||||||
|
compiler.compiler_report("unhandled float type", loc = loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
case .ENUM;
|
||||||
|
i := info.(*Type_Info_Enum);
|
||||||
|
if i.values.count == 0 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
min: T = i.values[0].(T, no_check);
|
||||||
|
if i.internal_type.signed {
|
||||||
|
for i.values if it.(T) < min {
|
||||||
|
min = it.(T);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for i.values if it.(T) < min {
|
||||||
|
min = it.(T);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return min;
|
||||||
|
|
||||||
|
case;
|
||||||
|
compiler.compiler_report("min requires an enum, integer, or float type", loc = loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the highest value T can represent.
|
||||||
|
// Note: T must be an integer, float, or enum type.
|
||||||
|
hi_for :: ($T: Type, loc := #caller_location) -> T #expand {
|
||||||
|
return #run -> T {
|
||||||
|
info := T.(*Type_Info);
|
||||||
|
if info.type == {
|
||||||
|
case .INTEGER;
|
||||||
|
i := info.(*Type_Info_Integer);
|
||||||
|
if i.runtime_size == {
|
||||||
|
case 1; return (ifx i.signed then 0x7f else 0xff).(T, no_check);
|
||||||
|
case 2; return (ifx i.signed then 0x7fff else 0xffff).(T, no_check);
|
||||||
|
case 4; return (ifx i.signed then 0x7fff_ffff else 0xffff_ffff).(T, no_check);
|
||||||
|
case 8; return (ifx i.signed then 0x7fff_ffff_ffff_ffff else 0xffff_ffff_ffff_ffff).(T, no_check);
|
||||||
|
case;
|
||||||
|
compiler.compiler_report("unhandled integer type", loc = loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
case .FLOAT;
|
||||||
|
if info.runtime_size == {
|
||||||
|
case 4; return (0h7F7FFFFF).(T, no_check);
|
||||||
|
case 8; return (0h7FEFFFFF_FFFFFFFF).(T, no_check);
|
||||||
|
case;
|
||||||
|
compiler.compiler_report("unhandled float type", loc = loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
case .ENUM;
|
||||||
|
i := info.(*Type_Info_Enum);
|
||||||
|
if i.values.count == 0 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
max := i.values[0].(T, no_check);
|
||||||
|
if i.internal_type.signed {
|
||||||
|
for i.values if xx it > max {
|
||||||
|
max = xx it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for i.values if xx it > max {
|
||||||
|
max = xx it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return max;
|
||||||
|
|
||||||
|
case;
|
||||||
|
compiler.compiler_report("max requires an enum, integer, or float type", loc = loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#scope_file;
|
||||||
|
|
||||||
|
compiler :: #import "Compiler"; // @future
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
// TESTS
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
|
||||||
|
#if RUN_TESTS #run {
|
||||||
|
test :: #import "jc/meta/test";
|
||||||
|
|
||||||
|
test.run("lo_for:primitives", t => {
|
||||||
|
test.expect(t, lo_for(u8) == 0);
|
||||||
|
test.expect(t, lo_for(s8) == -128);
|
||||||
|
test.expect(t, lo_for(u16) == 0);
|
||||||
|
test.expect(t, lo_for(s16) == -32768);
|
||||||
|
test.expect(t, lo_for(u32) == 0);
|
||||||
|
test.expect(t, lo_for(s32) == -2147483648);
|
||||||
|
test.expect(t, lo_for(u64) == 0);
|
||||||
|
test.expect(t, lo_for(s64) == -9223372036854775808);
|
||||||
|
|
||||||
|
test.expect(t, lo_for(float32) == 0h00800000);
|
||||||
|
test.expect(t, lo_for(float64) == 0h00100000_00000000);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.run("hi_for:primitives", t => {
|
||||||
|
test.expect(t, hi_for(u8) == 255);
|
||||||
|
test.expect(t, hi_for(s8) == 127);
|
||||||
|
test.expect(t, hi_for(u16) == 65535);
|
||||||
|
test.expect(t, hi_for(s16) == 32767);
|
||||||
|
test.expect(t, hi_for(u32) == 4294967295);
|
||||||
|
test.expect(t, hi_for(s32) == 2147483647);
|
||||||
|
test.expect(t, hi_for(u64) == 18446744073709551615);
|
||||||
|
test.expect(t, hi_for(s64) == 9223372036854775807);
|
||||||
|
|
||||||
|
test.expect(t, hi_for(float32) == 340282346638528859000000000000000000000.0);
|
||||||
|
test.expect(t, hi_for(float64) == 179769313486231570900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.run("lo_for/hi_for:enums", t => {
|
||||||
|
U8_Enum :: enum u8 { lo :: -1; hi :: +1; }
|
||||||
|
S8_Enum :: enum s8 { lo :: -2; hi :: -1; }
|
||||||
|
{
|
||||||
|
test.expect(t, lo_for(U8_Enum) == U8_Enum.lo);
|
||||||
|
test.expect(t, lo_for(S8_Enum) == S8_Enum.lo);
|
||||||
|
test.expect(t, hi_for(U8_Enum) == U8_Enum.hi);
|
||||||
|
test.expect(t, hi_for(S8_Enum) == S8_Enum.hi);
|
||||||
|
}
|
||||||
|
|
||||||
|
U16_Enum :: enum u16 { lo :: -1; hi :: +1; }
|
||||||
|
S16_Enum :: enum s16 { lo :: -2; hi :: -1; }
|
||||||
|
{
|
||||||
|
test.expect(t, lo_for(U16_Enum) == U16_Enum.lo);
|
||||||
|
test.expect(t, lo_for(S16_Enum) == S16_Enum.lo);
|
||||||
|
test.expect(t, hi_for(U16_Enum) == U16_Enum.hi);
|
||||||
|
test.expect(t, hi_for(S16_Enum) == S16_Enum.hi);
|
||||||
|
}
|
||||||
|
|
||||||
|
U32_Enum :: enum u32 { lo :: -1; hi :: +1; }
|
||||||
|
S32_Enum :: enum s32 { lo :: -2; hi :: -1; }
|
||||||
|
{
|
||||||
|
test.expect(t, lo_for(U32_Enum) == U32_Enum.lo);
|
||||||
|
test.expect(t, lo_for(S32_Enum) == S32_Enum.lo);
|
||||||
|
test.expect(t, hi_for(U32_Enum) == U32_Enum.hi);
|
||||||
|
test.expect(t, hi_for(S32_Enum) == S32_Enum.hi);
|
||||||
|
}
|
||||||
|
|
||||||
|
U64_Enum :: enum u64 { lo :: -1; hi :: +1; }
|
||||||
|
S64_Enum :: enum s64 { lo :: -2; hi :: -1; }
|
||||||
|
{
|
||||||
|
test.expect(t, lo_for(U64_Enum) == U64_Enum.lo);
|
||||||
|
test.expect(t, lo_for(S64_Enum) == S64_Enum.lo);
|
||||||
|
test.expect(t, hi_for(U64_Enum) == U64_Enum.hi);
|
||||||
|
test.expect(t, hi_for(S64_Enum) == S64_Enum.hi);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @note(judah): just making sure this compiles
|
||||||
|
lo, hi := range_for(U64_Enum);
|
||||||
|
test.expect(t, lo == U64_Enum.lo);
|
||||||
|
test.expect(t, hi == U64_Enum.hi);
|
||||||
|
});
|
||||||
|
}
|
||||||
76
module.jai
76
module.jai
|
|
@ -1,21 +1,59 @@
|
||||||
/// Module jc contains procedures for working with memory,
|
#module_parameters(IMPORT_LOCATION := #caller_location);
|
||||||
/// arrays, and strings; as well as helpful macros and
|
|
||||||
/// constants.
|
|
||||||
///
|
|
||||||
/// Additionally, it provides a platform-independant
|
|
||||||
/// interface for interacting with the target operating
|
|
||||||
/// system.
|
|
||||||
#module_parameters(RunTests := false);
|
|
||||||
|
|
||||||
// @note(judah): this causes some very weird issues in tests so it's ifdef'd out
|
#scope_export;
|
||||||
#if !RunTests #add_context temp_allocator: Allocator;
|
|
||||||
|
|
||||||
#load "+internal/builtin.jai";
|
byte :: u8;
|
||||||
#load "+internal/array.jai";
|
cstring :: *byte;
|
||||||
#load "+internal/kv.jai";
|
rawptr :: *void;
|
||||||
#load "+internal/hashing.jai";
|
|
||||||
#load "+internal/memory.jai";
|
#if size_of(int) == size_of(s64) {
|
||||||
#load "+internal/allocators.jai";
|
sint :: s64;
|
||||||
#load "+internal/testing.jai";
|
uint :: u64;
|
||||||
#load "+internal/keywords.jai";
|
}
|
||||||
#load "+internal/type_info.jai";
|
else {
|
||||||
|
sint :: s32;
|
||||||
|
uint :: u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if size_of(rawptr) == size_of(u64) {
|
||||||
|
uptr :: u64;
|
||||||
|
sptr :: s64;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
uptr :: u32;
|
||||||
|
sptr :: s32;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if OS == {
|
||||||
|
case .WINDOWS;
|
||||||
|
#if CPU != .X64 #run report_error_on_import("JC is only supported x86-64 Windows systems, not %", CPU);
|
||||||
|
|
||||||
|
ARCH :: "x86-64";
|
||||||
|
PLATFORM :: "windows";
|
||||||
|
|
||||||
|
case .MACOS;
|
||||||
|
#if CPU == {
|
||||||
|
case .X64; ARCH :: "x86-64";
|
||||||
|
case .ARM64; ARCH :: "arm64";
|
||||||
|
}
|
||||||
|
|
||||||
|
PLATFORM :: "macos";
|
||||||
|
|
||||||
|
case .LINUX;
|
||||||
|
#if CPU != .X64 #run report_error_on_import("JC is only supported x86-64 Unix systems, not %", CPU);
|
||||||
|
|
||||||
|
ARCH :: "x86-64";
|
||||||
|
PLATFORM :: "unix";
|
||||||
|
|
||||||
|
case;
|
||||||
|
#run report_error_on_import("JC is not supported on % % systems", CPU, OS);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#scope_file;
|
||||||
|
|
||||||
|
report_error_on_import :: (fmt: string, args: ..Any) #compile_time {
|
||||||
|
basic :: #import "Basic"; // @future
|
||||||
|
compiler :: #import "Compiler"; // @future
|
||||||
|
compiler.compiler_report(basic.tprint(fmt, ..args), loc = IMPORT_LOCATION);
|
||||||
|
}
|
||||||
|
|
|
||||||
107
platform/arch.jai
Normal file
107
platform/arch.jai
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
#module_parameters(RUN_TESTS := false);
|
||||||
|
|
||||||
|
// All of the architecture-specific extensions we care to look for.
|
||||||
|
// Note: Checking for impossible extensions will always turn into a no-op (ex. NEON support on x64).
|
||||||
|
ISA_Extension :: enum {
|
||||||
|
// x64
|
||||||
|
sse2;
|
||||||
|
sse3;
|
||||||
|
sse41;
|
||||||
|
sse42;
|
||||||
|
ssse3;
|
||||||
|
|
||||||
|
avx;
|
||||||
|
avx2;
|
||||||
|
avx512cd;
|
||||||
|
avx512f;
|
||||||
|
avx512vnni;
|
||||||
|
|
||||||
|
// arm64
|
||||||
|
neon;
|
||||||
|
|
||||||
|
// riscv
|
||||||
|
rv64ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the extension is supported by the current architecture.
|
||||||
|
arch_supports :: inline ($ext: ISA_Extension) -> bool {
|
||||||
|
lazy_init_cpu_info();
|
||||||
|
return arch_has_extension(ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if any of the extensions are supported by the current architecture.
|
||||||
|
arch_supports_any :: inline ($extensions: ..ISA_Extension) -> bool {
|
||||||
|
lazy_init_cpu_info();
|
||||||
|
|
||||||
|
res: bool;
|
||||||
|
#insert -> string {
|
||||||
|
b: basic.String_Builder;
|
||||||
|
for extensions {
|
||||||
|
basic.print_to_builder(*b, "res ||= arch_has_extension(.%);\n", it);
|
||||||
|
}
|
||||||
|
return basic.builder_to_string(*b);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if all of the extensions are supported by the current architecture.
|
||||||
|
arch_supports_all :: inline ($extensions: ..ISA_Extension) -> bool {
|
||||||
|
lazy_init_cpu_info();
|
||||||
|
|
||||||
|
res: bool;
|
||||||
|
#insert -> string {
|
||||||
|
b: basic.String_Builder;
|
||||||
|
for extensions {
|
||||||
|
basic.print_to_builder(*b, "res &&= arch_has_extension(.%);\n", it);
|
||||||
|
}
|
||||||
|
return basic.builder_to_string(*b);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#scope_file;
|
||||||
|
|
||||||
|
// @note(judah): this assumes lazy_init_cpu_info was already called
|
||||||
|
arch_has_extension :: inline ($ext: ISA_Extension) -> bool {
|
||||||
|
#if CPU == {
|
||||||
|
case .X64; #if ext == {
|
||||||
|
case .sse2; return x64.check_feature(info.feature_leaves, .SSE2);
|
||||||
|
case .sse3; return x64.check_feature(info.feature_leaves, .SSE3);
|
||||||
|
case .sse41; return x64.check_feature(info.feature_leaves, .SSE4_1);
|
||||||
|
case .sse42; return x64.check_feature(info.feature_leaves, .SSE4_2);
|
||||||
|
case .ssse3; return x64.check_feature(info.feature_leaves, .SSSE3);
|
||||||
|
case .avx; return x64.check_feature(info.feature_leaves, .AVX);
|
||||||
|
case .avx2; return x64.check_feature(info.feature_leaves, .AVX2);
|
||||||
|
case .avx512cd; return x64.check_feature(info.feature_leaves, .AVX512CD);
|
||||||
|
case .avx512f; return x64.check_feature(info.feature_leaves, .AVX512F);
|
||||||
|
case .avx512vnni; return x64.check_feature(info.feature_leaves, .AVX512_VNNI);
|
||||||
|
}
|
||||||
|
case .ARM64; #if ext == {
|
||||||
|
case .neon;
|
||||||
|
return true; // @note(judah): it's safe to assume neon support if we're on arm (for now)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
info_initialized := false;
|
||||||
|
|
||||||
|
lazy_init_cpu_info :: () #expand {
|
||||||
|
if info_initialized return;
|
||||||
|
info_initialized = true;
|
||||||
|
|
||||||
|
#if CPU == .X64 {
|
||||||
|
info = x64.get_cpu_info();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CPU == {
|
||||||
|
case .X64;
|
||||||
|
info: x64.Cpu_X86;
|
||||||
|
x64 :: #import "Machine_X64"; // @future
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
basic :: #import "Basic"; // @future
|
||||||
9
platform/module.jai
Normal file
9
platform/module.jai
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
#module_parameters(RUN_TESTS := false, WITH_SUBMODULES := true);
|
||||||
|
|
||||||
|
#scope_export;
|
||||||
|
|
||||||
|
#if WITH_SUBMODULES {
|
||||||
|
using #import "jc/platform/arch"(RUN_TESTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
#scope_module;
|
||||||
Loading…
Reference in a new issue