Compare commits

...

2 commits

Author SHA1 Message Date
3bd9b6b3e5 update plan 2025-09-06 00:59:01 -06:00
cdfb6af00b re-org mostly done 2025-09-06 00:52:48 -06:00
48 changed files with 882 additions and 2519 deletions

76
+internal/allocators.jai Normal file
View file

@ -0,0 +1,76 @@
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

View file

@ -1,7 +1,39 @@
/// 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 returns a subsection of an array.
Slice :: (view: []$T, start_idx: int, count := -1, loc := #caller_location) -> []T { Slice :: (view: []$T, start_idx: int, count := -1, loc := #caller_location) -> []T {
AssertCallsite(start_idx >= +0 && start_idx < view.count, "incorrect slice bounds"); AssertCallsite(start_idx >= +0 && start_idx < view.count, "jc: incorrect slice bounds");
AssertCallsite(count >= -1 && count < view.count, "incorrect slice length"); AssertCallsite(count >= -1 && count < view.count, "jc: incorrect slice length");
if count == -1 if count == -1
{ count = view.count - start_idx; } { count = view.count - start_idx; }
@ -9,15 +41,14 @@ Slice :: (view: []$T, start_idx: int, count := -1, loc := #caller_location) -> [
return .{ data = view.data + start_idx, count = count }; return .{ data = view.data + start_idx, count = count };
} }
/// Reset sets an array's length to 0, allowing it to be reused /// Reset sets an array's length to 0, but leaves its memory intact.
/// without allocating new memory. /// Note: To reset the associated memory as well, see Clear.
Reset :: (view: *[]$T) { Reset :: (view: *[]$T) {
view.count = 0; view.count = 0;
} }
/// Clear zeroes the memory of an array and sets its length to 0. /// Clear zeroes an array's memory and sets its length to 0.
/// /// Note: To leave the associated memory intact, see Reset.
/// Note: Clear does not free the array's memory.
Clear :: (view: *[]$T) { Clear :: (view: *[]$T) {
MemZero(view.data, view.count * size_of(T)); MemZero(view.data, view.count * size_of(T));
view.count = 0; view.count = 0;
@ -30,6 +61,18 @@ Equal :: (lhs: []$T, rhs: []T) -> bool {
return MemEqual(lhs.data, rhs.data, lhs.count * size_of(T)); 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 { FindFlags :: enum_flags {
Last; // Return the last matching element. Last; // Return the last matching element.
@ -121,7 +164,9 @@ Trim :: (view: []$T, cutset: []T, $flags: TrimFlags = .FromStart) -> []T {
#scope_file #scope_file
#if #exists(RunTests) #run,stallable { basic :: #import "Basic"; // @future
#if RunTests #run,stallable {
Test("slice", t => { Test("slice", t => {
a1 := int.[ 1, 2, 3, 4, 5 ]; a1 := int.[ 1, 2, 3, 4, 5 ];
a2 := Slice(a1, 2); a2 := Slice(a1, 2);

View file

@ -67,7 +67,7 @@ b64 :: enum u64 { false_ :: (0 != 0).(u64); true_ :: (0 == 0).(u64); }; /// b64
/// Panic displays the given message and crashes the program. /// Panic displays the given message and crashes the program.
/// ///
/// Note: Defers will not run when Panic is called. /// Note: Defers will not run when Panic is called.
Panic :: (message := "runtime panic", loc := #caller_location) #expand #no_debug { Panic :: (message := "jc: runtime panic", loc := #caller_location) #expand #no_debug {
#if DebugBuild { #if DebugBuild {
WriteStderrLocation(loc); WriteStderrLocation(loc);
WriteStderrString(": "); WriteStderrString(": ");
@ -80,7 +80,7 @@ Panic :: (message := "runtime panic", loc := #caller_location) #expand #no_debug
/// Unreachable displays the given message and causes an execution trap. /// Unreachable displays the given message and causes an execution trap.
/// ///
/// Note: Defers will not run when Unreachable is called. /// Note: Defers will not run when Unreachable is called.
Unreachable :: (message := "unreachable code hit", loc := #caller_location) #expand #no_debug { Unreachable :: (message := "jc: unreachable code hit", loc := #caller_location) #expand #no_debug {
trap :: #ifx DebugBuild then DebugTrap else Trap; trap :: #ifx DebugBuild then DebugTrap else Trap;
#if DebugBuild { #if DebugBuild {
@ -100,7 +100,7 @@ CompileError :: (message: string, loc := #caller_location) #expand #no_debug #co
compiler_report(message, loc, .ERROR); compiler_report(message, loc, .ERROR);
} }
else { else {
Panic("CompileError can only be called at compile-time", loc = loc); Panic("jc: CompileError can only be called at compile-time", loc = loc);
} }
} }
@ -119,7 +119,7 @@ DebugTrap :: () #expand #no_debug {
#if DebugBuild #if DebugBuild
{ {
/// Assert causes a debug break if the given condition is false. /// Assert causes a debug break if the given condition is false.
Assert :: (cond: bool, message := "condition was false", loc := #caller_location) #expand #no_debug { 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. // @note(judah): We only need to do this to route into the context's builtin assertion handling.
if cond || context.handling_assertion_failure return; if cond || context.handling_assertion_failure return;
@ -140,7 +140,7 @@ DebugTrap :: () #expand #no_debug {
/// Assert(false, loc = loc); // equivalent /// Assert(false, loc = loc); // equivalent
/// } /// }
/// ///
AssertCallsite :: (cond: bool, message := "condition was false") #expand #no_debug { AssertCallsite :: (cond: bool, message := "jc: condition was false") #expand #no_debug {
Assert(cond, message, loc = `loc); Assert(cond, message, loc = `loc);
} }
} }
@ -166,6 +166,10 @@ WriteStderrLocation :: (loc: Source_Code_Location) {
WriteStderrNumber(loc.character_number); WriteStderrNumber(loc.character_number);
} @jc.nodocs } @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 #scope_file
DebugBuild :: #run -> bool { DebugBuild :: #run -> bool {
@ -175,6 +179,3 @@ DebugBuild :: #run -> bool {
opts := get_build_options(); opts := get_build_options();
return opts.emit_debug_info != .NONE; return opts.emit_debug_info != .NONE;
}; };
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

View file

@ -1,19 +1,74 @@
#module_parameters(RUN_TESTS := false); // Implementation: Demetri Spanos (github.com/demetri/scribbles)
// 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)
XXHash_Seed :: 0; XXHashSeed :: 0;
xxhash64 :: inline (s: string, seed: u32 = XXHash_Seed) -> u64 { XXHash64 :: inline (s: string, seed: u32 = XXHashSeed) -> u64 {
return xxhash64(s.data, s.count, seed); return XXHash64(s.data, s.count, seed);
} }
xxhash64 :: inline (x: $T, seed: u32 = XXHash_Seed) -> u64 { XXHash64 :: inline (x: $T, seed: u32 = XXHashSeed) -> 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 = XXHash_Seed) -> u64 { XXHash64 :: (key: *void, len: int, seed: u64 = XXHashSeed) -> u64 {
p1: u64 : 0x9e3779b185ebca87; p1: u64 : 0x9e3779b185ebca87;
p2: u64 : 0xc2b2ae3d27d4eb4f; p2: u64 : 0xc2b2ae3d27d4eb4f;
p3: u64 : 0x165667b19e3779f9; p3: u64 : 0x165667b19e3779f9;

View file

@ -9,7 +9,7 @@ offset_of :: ($T: Type, ident: Code, loc := #caller_location) -> int #expand {
#run (loc: Source_Code_Location) { #run (loc: Source_Code_Location) {
info := type_info(T); info := type_info(T);
if info.type != .STRUCT { if info.type != .STRUCT {
CompileError("offset_of can only be used on struct types", loc = loc); CompileError("jc: offset_of can only be used on struct types", loc = loc);
} }
}(loc); }(loc);
@ -35,7 +35,7 @@ offset_of :: (#discard value: $T, ident: Code, loc := #caller_location) -> int #
// I opted against because if you have a *T, you only want offset_of to get an offset // I opted against because if you have a *T, you only want offset_of to get an offset
// from that pointer. What would you do with a field offset from **T? // from that pointer. What would you do with a field offset from **T?
if info.type == .POINTER { if info.type == .POINTER {
CompileError("offset_of only allows one level of pointer indirection.", loc = loc); CompileError("jc: offset_of only allows one level of pointer indirection.", loc = loc);
} }
} }
@ -95,14 +95,14 @@ min_of :: ($T: Type, loc := #caller_location) -> T #expand {
case 2; return (ifx i.signed then -0x8000 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 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 8; return (ifx i.signed then -0x8000_0000_0000_0000 else 0).(T, no_check);
case ; CompileError("unknown integer size", loc = loc); case ; CompileError("jc: unknown integer size", loc = loc);
} }
case .FLOAT; case .FLOAT;
if info.runtime_size == { if info.runtime_size == {
case 4; return (0h0080_0000).(T, no_check); case 4; return (0h0080_0000).(T, no_check);
case 8; return (0h00100000_00000000).(T, no_check); case 8; return (0h00100000_00000000).(T, no_check);
case ; CompileError("unknown float size", loc = loc); case ; CompileError("jc: unknown float size", loc = loc);
} }
case .ENUM; case .ENUM;
@ -126,7 +126,7 @@ min_of :: ($T: Type, loc := #caller_location) -> T #expand {
return min; return min;
case; case;
CompileError("min_of requires an enum, integer, or float type", loc = loc); CompileError("jc: min_of requires an enum, integer, or float type", loc = loc);
} }
return 0; return 0;
@ -147,14 +147,14 @@ max_of :: ($T: Type, loc := #caller_location) -> T #expand {
case 2; return (ifx i.signed then 0x7fff else 0xffff).(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 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 8; return (ifx i.signed then 0x7fff_ffff_ffff_ffff else 0xffff_ffff_ffff_ffff).(T, no_check);
case ; CompileError("unknown integer size", loc = loc); case ; CompileError("jc: unknown integer size", loc = loc);
} }
case .FLOAT; case .FLOAT;
if info.runtime_size == { if info.runtime_size == {
case 4; return (0h7F7FFFFF).(T, no_check); case 4; return (0h7F7FFFFF).(T, no_check);
case 8; return (0h7FEFFFFF_FFFFFFFF).(T, no_check); case 8; return (0h7FEFFFFF_FFFFFFFF).(T, no_check);
case ; CompileError("unknown float size", loc = loc); case ; CompileError("jc: unknown float size", loc = loc);
} }
case .ENUM; case .ENUM;
@ -178,7 +178,7 @@ max_of :: ($T: Type, loc := #caller_location) -> T #expand {
return max; return max;
case; case;
CompileError("max_of requires an enum, integer, or float type", loc = loc); CompileError("jc: max_of requires an enum, integer, or float type", loc = loc);
} }
return 0; return 0;
@ -230,4 +230,49 @@ for_expansion :: (v: Sector, code: Code, _: For_Flags) #expand {
Sector :: struct(Name: string = "early") {} Sector :: struct(Name: string = "early") {}
basic :: #import "Basic"; 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 Normal file
View file

@ -0,0 +1,181 @@
// 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);
});
}

View file

@ -1,9 +1,15 @@
Kilobyte :: 1024;
Megabyte :: 1024 * Kilobyte;
Gigabyte :: 1024 * Megabyte;
DefaultAlign :: #run 2 * align_of(*void);
/// MemEqual checks the equality of two pieces of memory. /// MemEqual checks the equality of two pieces of memory.
/// ///
/// Note: MemEqual will panic if size_in_bytes is negative. /// Note: MemEqual will panic if size_in_bytes is negative.
MemEqual :: (p1: *void, p2: *void, size_in_bytes: int) -> bool { MemEqual :: (p1: *void, p2: *void, size_in_bytes: int) -> bool {
if size_in_bytes < 0 if size_in_bytes < 0
{ Panic("size_in_bytes cannot be negative"); } { Panic("jc: size_in_bytes cannot be negative"); }
return memcmp(p1, p2, size_in_bytes) == 0; // Provided by Preload return memcmp(p1, p2, size_in_bytes) == 0; // Provided by Preload
} }
@ -12,7 +18,7 @@ MemEqual :: (p1: *void, p2: *void, size_in_bytes: int) -> bool {
/// Note: MemCopy will panic if size_in_bytes is negative. /// Note: MemCopy will panic if size_in_bytes is negative.
MemCopy :: (dst: *void, src: *void, size_in_bytes: int) { MemCopy :: (dst: *void, src: *void, size_in_bytes: int) {
if size_in_bytes < 0 if size_in_bytes < 0
{ Panic("size_in_bytes cannot be negative"); } { Panic("jc: size_in_bytes cannot be negative"); }
memcpy(dst, src, size_in_bytes); // Provided by Preload memcpy(dst, src, size_in_bytes); // Provided by Preload
} }
@ -21,7 +27,7 @@ MemCopy :: (dst: *void, src: *void, size_in_bytes: int) {
/// Note: MemOverwrite will panic if size_in_bytes is negative. /// Note: MemOverwrite will panic if size_in_bytes is negative.
MemOverwrite :: (p: *void, size_in_bytes: int, value: u8 = 0) { MemOverwrite :: (p: *void, size_in_bytes: int, value: u8 = 0) {
if size_in_bytes < 0 if size_in_bytes < 0
{ Panic("size_in_bytes cannot be negative"); } { Panic("jc: size_in_bytes cannot be negative"); }
memset(p, value, size_in_bytes); // Provided by preload memset(p, value, size_in_bytes); // Provided by preload
} }
@ -53,3 +59,31 @@ MemReset :: (p: *$T) {
inline MemZero(p); 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;
}

View file

@ -25,7 +25,7 @@
/// Expect(value2 > 0, "my_proc returned a negative number!"); /// Expect(value2 > 0, "my_proc returned a negative number!");
/// }); /// });
/// ///
Test :: (name: string, proc: (*void) -> (), loc := #caller_location) { Test :: (name: string, proc: (t: *void) -> (), loc := #caller_location) {
// @note(judah): incredibly dumb way to get nicer test runs // @note(judah): incredibly dumb way to get nicer test runs
path := loc.fully_pathed_filename; path := loc.fully_pathed_filename;
@ -76,7 +76,8 @@ Test :: (name: string, proc: (*void) -> (), loc := #caller_location) {
/// Expect checks the given condition, failing the current test if it is false. /// Expect checks the given condition, failing the current test if it is false.
/// ///
/// Note: Expect must be called within a test. /// 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 { Expect :: (cond: bool, message := "", args: ..Any, loc := #caller_location) #expand {
run := `t.(*TestRun); run := `t.(*TestRun);
run.total_expects += 1; run.total_expects += 1;

43
+internal/type_info.jai Normal file
View file

@ -0,0 +1,43 @@
// @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);
}

20
INBOX
View file

@ -2,12 +2,10 @@
Put any questions for me in here! Put any questions for me in here!
[Jesse] [Jesse]
05.22.25 Judah 05.31.25 Jesse
Instead of going with a separate 'arch' module, I platform/arch merge sounds good, we can keep that going. No thoughts so far on that.
think we can roll that into the future 'platform' The caching is a good idea, I wonder if we should make them enum_flags.
module instead. There's enough overlap that I I don't know if it will go over a u64 though.
think it makes sense. Let me know what you think,
thanks!
05.23.25 Judah 05.23.25 Judah
I went ahead and created the platform module and I went ahead and created the platform module and
added arch-specific extension checking. Sadly added arch-specific extension checking. Sadly
@ -16,7 +14,9 @@
it to add a 'SIMD' constant that we set for it to add a 'SIMD' constant that we set for
targets that will almost always support SIMD targets that will almost always support SIMD
instructions; unsure for now. instructions; unsure for now.
05.31.25 Jesse 05.22.25 Judah
platform/arch merge sounds good, we can keep that going. No thoughts so far on that. Instead of going with a separate 'arch' module, I
The caching is a good idea, I wonder if we should make them enum_flags. think we can roll that into the future 'platform'
I don't know if it will go over a u64 though. module instead. There's enough overlap that I
think it makes sense. Let me know what you think,
thanks!

41
PLAN.Judah Normal file
View file

@ -0,0 +1,41 @@
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
View file

@ -1,46 +1,15 @@
-- --------------
jc jc Jai Library
-- --------------
# Direct installation For installation instructions and documentation, see:
cd [jai install dir]/modules https://judahcaruso.com/jc
git clone https://git.brut.systems/judah/jc.git
# Indirect installation For local documentation, run 'jai _generate_docs.jai' then
git clone https://git.brut.systems/judah/jc.git navigate to the 'docs' subdirectory.
ln -s "/path/to/jc" [jai install dir]/modules/jc # POSIX install
mklink /D "C:\path\to\jc" [jai install dir]\jc # Windows install
#import "jc"; License
#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

View file

@ -42,8 +42,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' under that person's section; the commit entry in 'INBOX' at the top of that person's section; the
message should roughly say 'message for [name]'. commit 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 +51,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,14 +79,21 @@ 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.
Please try to stick to this format: Sticking to this format makes my life easier:
@note, @todo, @temp, @allocates, @leak @note(name), @todo, @temp, etc.
- 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
------- -------
@ -96,48 +103,24 @@ 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"; basic :: #import "Basic"; // @future
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'. '_run_all_tests.jai' is an exception to with '#load'.
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 custom type needs dynamically allocated memory to If a type needs dynamically allocated memory to function,
function, it should always assume the memory came from an it should always assume the memory came from an arena
arena allocator. This means it is the responsibility of allocator. This means it is the responsibility of the
the arena to free the memory, not the custom data type. arena to free the memory, not the data type.
In other words: In other words:
@ -145,39 +128,7 @@ 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. For examples, see allowing its memory to be reused.
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
@ -187,11 +138,10 @@ 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 the release build of the source control. If possible, use a 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.

View file

@ -18,14 +18,19 @@ UseLocalLinks :: false; // set to false when deploying the docs
// Add freestanding modules here. // Add freestanding modules here.
// Other modules will be automatically generated if they're imported by someone else. // Other modules will be automatically generated if they're imported by someone else.
jc :: #import "jc"; _ :: #import "jc";
hmm :: #import "jc/ext/hmm"; _ :: #import "jc/math";
luajit :: #import "jc/ext/luajit"; _ :: #import "jc/fmt/base64";
raylib :: #import "jc/ext/raylib";
remotery :: #import "jc/ext/remotery";
// darwin :: #import "jc/ext/darwin"; _ :: #import "jc/x/json";
// objc :: #import "jc/ext/objc";
_ :: #import "jc/ext/hmm";
_ :: #import "jc/ext/luajit";
_ :: #import "jc/ext/raylib";
_ :: #import "jc/ext/remotery";
// _ :: #import "jc/ext/darwin";
// _ :: #import "jc/ext/objc";
// _ :: #import "jc/ext/win32";
END, ws); END, ws);
CheckImport :: (import: *Code_Directive_Import) -> bool { CheckImport :: (import: *Code_Directive_Import) -> bool {
@ -103,6 +108,12 @@ UseLocalLinks :: false; // set to false when deploying the docs
array = *mod.macros; array = *mod.macros;
} }
// there should really be a flag on the proc header...
first := to_lower(decl.name[0]);
if !(first >= #char "a" && first <= #char "z") {
decl.name = tprint("operator%", decl.name);
}
array_add(array, .{ array_add(array, .{
decl = decl, decl = decl,
node = header, node = header,
@ -114,6 +125,9 @@ UseLocalLinks :: false; // set to false when deploying the docs
if !(decl.flags & .IS_CONSTANT) if !(decl.flags & .IS_CONSTANT)
{ continue; } { continue; }
if decl.name == "RunTests" || decl.name == "RUN_TESTS"
{ continue; }
array := *mod.consts; array := *mod.consts;
// Things we don't want to be constant decls // Things we don't want to be constant decls
@ -188,12 +202,21 @@ UseLocalLinks :: false; // set to false when deploying the docs
join(..contributors, ", "), join(..contributors, ", "),
); );
sorted_names := quick_sort(names, SortAlphabeticallyDescendingLength);
append(*b, #string END append(*b, #string END
<details id='index' class='index' open='true'> <div class='index'>
END);
{
append(*b, #string END
<details open='true'>
<summary><h3>Modules</h3></summary> <summary><h3>Modules</h3></summary>
<ul> <ul>
END); END);
for quick_sort(names, SortAlphabeticallyDescendingLength) {
for sorted_names if !contains(it, "jc/ext") {
print_to_builder(*b, "<li><a href='%1'>%2</a></li>", LinkableName(it), it); print_to_builder(*b, "<li><a href='%1'>%2</a></li>", LinkableName(it), it);
} }
@ -201,6 +224,26 @@ UseLocalLinks :: false; // set to false when deploying the docs
</ul> </ul>
</details> </details>
END); END);
}
{
append(*b, #string END
<details>
<summary><h3>Bindings</h3></summary>
<ul>
END);
for sorted_names if contains(it, "jc/ext") {
print_to_builder(*b, "<li><a href='%1'>%2</a></li>", LinkableName(it), it);
}
append(*b, #string END
</ul>
</details>
END);
}
append(*b, "</div>");
append(*b, #string END append(*b, #string END
<p> <p>
@ -235,7 +278,7 @@ git clone https://git.brut.systems/judah/jc.git
# Indirect installation # Indirect installation
git clone https://git.brut.systems/judah/jc.git git clone https://git.brut.systems/judah/jc.git
ln -s "/path/to/jc" [jai install dir]/modules/jc # POSIX install ln -s "$(pwd)jc" [jai install dir]/modules/jc # POSIX install
mklink /D "C:\path\to\jc" [jai install dir]\jc # Windows install mklink /D "C:\path\to\jc" [jai install dir]\jc # Windows install
# Usage: # Usage:
@ -256,9 +299,9 @@ mklink /D "C:\path\to\jc" [jai install dir]\jc # Windows install
source_files: Table(string, []string); source_files: Table(string, []string);
GetSourceLines :: (filename: string) -> []string, bool { GetSourceLines :: (filename: string) -> []string, bool {
// old_logger := context.logger; old_logger := context.logger;
// context.logger = (message: string, data: *void, info: Log_Info) {}; // do nothing logger context.logger = (message: string, data: *void, info: Log_Info) {}; // do nothing logger
// defer context.logger = old_logger; defer context.logger = old_logger;
fok, src := table_find_new(*source_files, filename); fok, src := table_find_new(*source_files, filename);
if fok { if fok {
@ -266,12 +309,15 @@ GetSourceLines :: (filename: string) -> []string, bool {
} }
data, rok := read_entire_file(filename);
lines: []string; lines: []string;
data, rok := read_entire_file(filename);
if rok { if rok {
lines = split(data, "\n"); lines = split(data, "\n");
table_add(*source_files, filename, lines); table_add(*source_files, filename, lines);
} }
else {
print("warn: file didn't exist '%'\n", filename);
}
return lines, rok; return lines, rok;
} }
@ -374,6 +420,13 @@ ModuleToHtml :: (mod: *Module) -> string {
StartPage(*b, tprint("Module % &ndash; Jc v%.% Documentation", import_name, JcMajor, JcMinor)); StartPage(*b, tprint("Module % &ndash; Jc v%.% Documentation", import_name, JcMajor, JcMinor));
print_to_builder(*b, "<h1><a href='index.html'>[..]</a> Module &ndash; %</h1>", short_name); print_to_builder(*b, "<h1><a href='index.html'>[..]</a> Module &ndash; %</h1>", short_name);
print_to_builder(*b, "<p>%</p>", mod.main_docs); print_to_builder(*b, "<p>%</p>", mod.main_docs);
if contains(import_name, "jc/x") {
append(*b, "<span class='module-warning'>");
print_to_builder(*b, "Warning: % is experimental and has no stability guarantees or versioning; use with caution.", short_name);
append(*b, "</span>");
}
print_to_builder(*b, "<pre class='code'>#import \"%\";</pre>", import_name); print_to_builder(*b, "<pre class='code'>#import \"%\";</pre>", import_name);
PrintIndexFor :: (b: *String_Builder, name: string, decls: []Decl) { PrintIndexFor :: (b: *String_Builder, name: string, decls: []Decl) {
@ -729,6 +782,10 @@ CleanJaiSource :: (node: *Code_Node) -> string {
postfix = tprint("% #compile_time", postfix); postfix = tprint("% #compile_time", postfix);
} }
if header.procedure_flags & .SYMMETRIC {
postfix = tprint("% #symmetric", postfix);
}
return tprint("%1%2", trim(tail), postfix); return tprint("%1%2", trim(tail), postfix);
} }
// for everything else, get the source range for the node and only strip out // for everything else, get the source range for the node and only strip out
@ -791,18 +848,21 @@ CleanJaiSource :: (node: *Code_Node) -> string {
source := join(..cleaned_lines, "\n"); source := join(..cleaned_lines, "\n");
index := find_index_from_left(source, "::"); index := find_index_from_left(source, "::");
// This is like 95% a module parameter // @todo(judah): handle module parameters correctly
if index == -1 { if index == -1 {
#import "Program_Print"; #import "Program_Print";
b: String_Builder; b: String_Builder;
is_module_param := true;
if decl.expression != null { if decl.expression != null {
print_expression(*b, decl.expression); print_expression(*b, decl.expression);
} }
else if decl.type_inst { else if decl.type_inst {
print_expression(*b, decl.type_inst); print_expression(*b, decl.type_inst);
is_module_param = false;
} }
append(*b, "; // #module_parameter"); append(*b, ";");
return builder_to_string(*b); return builder_to_string(*b);
} }
@ -939,12 +999,15 @@ MainCss :: #string END
} }
body { body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6; line-height: 1.6;
color: var(--text-primary); color: var(--text-primary);
background-color: var(--bg-primary); background-color: var(--bg-primary);
margin: 0; margin: 0;
padding: 0; padding: 0;
font-feature-settings: 'kern' 1, 'liga' 1, 'calt' 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
} }
h1 { h1 {
@ -966,7 +1029,7 @@ h4 {
font-size: 1.1rem; font-size: 1.1rem;
font-weight: 600; font-weight: 600;
margin: 1.5rem 0 1rem 0; margin: 1.5rem 0 1rem 0;
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; font-family: 'Menlo', 'Monaco', 'SF Mono', 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
} }
a { a {
@ -1045,7 +1108,7 @@ h4 a:hover {
.index a { .index a {
color: var(--link-color); color: var(--link-color);
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; font-family: 'Menlo', 'Monaco', 'SF Mono', 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
} }
.index a:hover { .index a:hover {
@ -1073,9 +1136,12 @@ pre.code {
padding: 1rem; padding: 1rem;
margin: 1rem 0; margin: 1rem 0;
overflow-x: auto; overflow-x: auto;
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; font-family: 'Menlo', 'Monaco', 'SF Mono', 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
font-size: 0.9rem; font-size: 0.9rem;
line-height: 1.4; line-height: 1.4;
font-feature-settings: 'liga' 1, 'calt' 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
} }
span.ident { span.ident {
@ -1151,6 +1217,23 @@ main {
font-size: 1.6rem; font-size: 1.6rem;
margin: 1.5rem 0 0.75rem 0; margin: 1.5rem 0 0.75rem 0;
} }
h4 {
font-size: 1rem;
font-family: 'Menlo', 'Monaco', Consolas, 'Courier New', monospace;
}
.index a {
font-family: 'Menlo', 'Monaco', Consolas, 'Courier New', monospace;
}
pre.code {
font-family: 'Menlo', 'Monaco', Consolas, 'Courier New', monospace;
font-size: 0.85rem;
padding: 0.75rem;
-webkit-text-size-adjust: 100%;
text-rendering: optimizeLegibility;
}
} }
a:focus, a:focus,
@ -1196,6 +1279,17 @@ html {
outline: 2px solid var(--accent-color); outline: 2px solid var(--accent-color);
outline-offset: 2px; outline-offset: 2px;
} }
span.module-warning {
background-color: var(--bg-primary);
color: var(--accent-color);
display: block;
border-radius: 6px;
font-weight: bold;
border: 1px solid var(--accent-color);
border-radius: 6px;
padding: 0.5rem;
}
END; END;
ResetCss :: #string END ResetCss :: #string END
@ -1275,7 +1369,7 @@ textarea:not([rows]) {
END; END;
#load "./module.jai"; #import "jc";
#import "String"; #import "String";
#import "Compiler"; #import "Compiler";

View file

@ -1,85 +0,0 @@
#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";

View file

@ -1,8 +1,10 @@
RunTests :: true;
#scope_file #run { #scope_file #run {
compiler :: #import "Compiler"; compiler :: #import "Compiler";
compiler.set_build_options_dc(.{ do_output = false }); compiler.set_build_options_dc(.{ do_output = false });
#load "module.jai"; _ :: #import "jc"(true);
_ :: #import "jc/math"(RUN_TESTS = true);
_ :: #import "jc/fmt/base64"(true);
_ :: #import "jc/x/json"(true);
} }

View file

@ -1,58 +0,0 @@
#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";
}

View file

@ -1,116 +0,0 @@
#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);
});
}

View file

@ -1,13 +0,0 @@
#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;

View file

@ -1,194 +0,0 @@
#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);
});
}

View file

@ -1,171 +0,0 @@
#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);
});
}

View file

@ -1,11 +0,0 @@
#module_parameters(RUN_TESTS := false);
#scope_file;
// ----------------------------------------------------------
// TESTS
// ----------------------------------------------------------
#if RUN_TESTS #run {
test :: #import "jc/meta/test";
}

View file

@ -1,10 +0,0 @@
#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;

View file

@ -1,16 +1,16 @@
#module_parameters(RUN_TESTS := false); #module_parameters(RunTests := false);
base64_encode :: (str: string, $for_url := false) -> string, bool { Base64Encode :: (str: string, $for_url := false) -> string, bool {
enc, ok := base64_encode(str.([]u8), for_url); enc, ok := Base64Encode(str.([]u8), for_url);
return enc.(string), ok; return enc.(string), ok;
} }
base64_decode :: (str: string) -> string, bool { Base64Decode :: (str: string) -> string, bool {
enc, ok := base64_decode(str.([]u8)); enc, ok := Base64Decode(str.([]u8));
return enc.(string), ok; return enc.(string), ok;
} }
base64_encode :: ($$data: []u8, $for_url := false) -> []u8, bool { Base64Encode :: ($$data: []u8, $for_url := false) -> []u8, bool {
if data.count == 0 return .[], false; if data.count == 0 return .[], false;
#if for_url { #if for_url {
@ -21,7 +21,7 @@ base64_encode :: ($$data: []u8, $for_url := false) -> []u8, bool {
} }
padded := strings.contains(data.(string), Padding_Character); padded := strings.contains(data.(string), Padding_Character);
encoded := basic.NewArray(encoded_length(data.count, padded), u8, true); encoded := basic.NewArray(EncodedLength(data.count, padded), u8, true);
src_idx := 0; src_idx := 0;
dst_idx := 0; dst_idx := 0;
@ -70,7 +70,7 @@ base64_encode :: ($$data: []u8, $for_url := false) -> []u8, bool {
return encoded, true; return encoded, true;
} }
base64_decode :: (data: []u8) -> []u8, bool { Base64Decode :: (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,17 +144,20 @@ base64_decode :: (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+/";
encoded_length :: (count: int, with_padding := false) -> int { EncodedLength :: (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
@ -163,47 +166,45 @@ strings :: #import "String"; // @future
// TESTS // TESTS
// ---------------------------------------------------------- // ----------------------------------------------------------
#if RUN_TESTS #run { #if RunTests #run {
test :: #import "jc/meta/test"; Test("encodes", t => {
test.run("encodes", (t) => {
str :: "Hello, World"; str :: "Hello, World";
encoded, ok := base64_encode(str); encoded, ok := Base64Encode(str);
test.expect(t, ok, "'%' did not properly encode!", str); Expect(ok, "'%' did not properly encode!", str);
expected :: "SGVsbG8sIFdvcmxk"; expected :: "SGVsbG8sIFdvcmxk";
test.expect(t, encoded == expected, "wanted: '%', received: '%'", expected, encoded); Expect(encoded == expected, "wanted: '%', received: '%'", expected, encoded);
}); });
test.run("decodes", (t) => { Test("decodes", (t) => {
encoded_str :: "SGVsbG8sIFdvcmxk"; encoded_str :: "SGVsbG8sIFdvcmxk";
decoded, ok := base64_decode(encoded_str); decoded, ok := Base64Decode(encoded_str);
test.expect(t, ok, "'%' did not properly decode!", encoded_str); Expect(ok, "'%' did not properly decode!", encoded_str);
expected :: "Hello, World"; expected :: "Hello, World";
test.expect(t, decoded == expected, "wanted: '%', received: '%'", expected, decoded); Expect(decoded == expected, "wanted: '%', received: '%'", expected, decoded);
}); });
test.run("encodes/decodes padding", (t) => { Test("encodes/decodes padding", (t) => {
str :: "VkdocGN5QnBjeUJ6YjIxbGRHaHBibWNnYlc5eVpTQmpiMjF3YkdWNExDQnBkQ0J6YUc5MWJHUWdhR0YyWlNCd1lXUmthVzVuUHc9PQ=="; str :: "VkdocGN5QnBjeUJ6YjIxbGRHaHBibWNnYlc5eVpTQmpiMjF3YkdWNExDQnBkQ0J6YUc5MWJHUWdhR0YyWlNCd1lXUmthVzVuUHc9PQ==";
first_decode, f_ok := base64_decode(str); first_decode, f_ok := Base64Decode(str);
test.expect(t, f_ok, "first decode failed!"); Expect(f_ok, "first decode failed!");
re_encode, r_ok := base64_encode(first_decode); re_encode, r_ok := Base64Encode(first_decode);
test.expect(t, r_ok, "re-encode failed!"); Expect(r_ok, "re-encode failed!");
second_decode, s_ok := base64_decode(re_encode); second_decode, s_ok := Base64Decode(re_encode);
test.expect(t, s_ok, "second decode failed!"); Expect(s_ok, "second decode failed!");
for 0..first_decode.count - 1 { for 0..first_decode.count - 1 {
test.expect(t, first_decode[it] == second_decode[it], "Decoded byte '%' did not match expected output", it); Expect(first_decode[it] == second_decode[it], "Decoded byte '%' did not match expected output", it);
} }
}); });
test.run("encodes/decodes struct", (t) => { Test("encodes/decodes struct", (t) => {
Foo :: struct { Foo :: struct {
first: u64; first: u64;
second: u64; second: u64;
@ -220,14 +221,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 := base64_encode(basic.builder_to_string(*enc_builder)); encoded, e_ok := Base64Encode(basic.builder_to_string(*enc_builder));
test.expect(t, e_ok, "Encode of structure failed!"); Expect(e_ok, "Encode of structure failed!");
decoded, d_ok := base64_decode(encoded); decoded, d_ok := Base64Decode(encoded);
test.expect(t, d_ok, "Decode of encoded structure failed!"); Expect(d_ok, "Decode of encoded structure failed!");
parts := strings.split(decoded, ","); parts := strings.split(decoded, ",");
test.expect(t, parts.count == 4, "Invalid number of parts after decode: %", parts.count); Expect(parts.count == 4, "Invalid number of parts after decode: %", parts.count);
bar: Foo = ---; bar: Foo = ---;
@ -237,17 +238,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);
test.expect(t, ok, "Integer parse of value % ('%') failed!", it_index, parts[it_index]); Expect(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;
} }
} }
test.expect(t, foo.first == bar.first, "Foo.first (%, %) didn't match between encoding/decoding!", foo.first, bar.first); Expect(foo.first == bar.first, "Foo.first (%, %) didn't match between encoding/decoding!", foo.first, bar.first);
test.expect(t, foo.second == bar.second, "Foo.second (%, %) didn't match between encoding/decoding!", foo.second, bar.second); Expect(foo.second == bar.second, "Foo.second (%, %) didn't match between encoding/decoding!", foo.second, bar.second);
test.expect(t, foo.third == bar.third, "Foo.third (%, %) didn't match between encoding/decoding!", foo.third, bar.third); Expect(foo.third == bar.third, "Foo.third (%, %) didn't match between encoding/decoding!", foo.third, bar.third);
test.expect(t, foo.forth == bar.forth, "Foo.forth (%, %) didn't match between encoding/decoding!", foo.forth, bar.forth); Expect(foo.forth == bar.forth, "Foo.forth (%, %) didn't match between encoding/decoding!", foo.forth, bar.forth);
}); });
} }

View file

@ -1,10 +0,0 @@
#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);
}

View file

@ -1,58 +0,0 @@
#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;
}

View file

@ -1,191 +0,0 @@
#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);
});
}

View file

@ -88,12 +88,12 @@ min :: (x: float, y: float) -> float {
} }
abs :: (v: $T) -> T abs :: (v: $T) -> T
#modify { return meta.type_is_scalar(T), "Only accepting scalar types, integer and float"; } { #modify { return 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;
} }
#scope_file #scope_file
#import "jc";
// sin, cos // sin, cos
meta :: #import "jc/meta";
math :: #import "Math"; math :: #import "Math";

View file

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

View file

@ -404,45 +404,43 @@ inverse :: (m: Mat4) -> Mat4 {
#scope_file; #scope_file;
#if #exists(RUN_TESTS) #run { #if RUN_TESTS #run {
test :: #import "jc/meta/test"; Test(basic.tprint("%: Mat2", UNITS), t => {
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]};
test.expect(t, a*identity == a); Expect(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;
test.expect(t, v2_eq(v2, v2f(-1.0, -0.5))); Expect(v2_eq(v2, v2f(-1.0, -0.5)));
v2 = rotator*v2; v2 = rotator*v2;
test.expect(t, v2_eq(v2, v2f(1.0, 0.5))); Expect(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);
test.expect(t, det == -4); Expect(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);
test.expect(t, inv_rotator*rotator == m2_identity); Expect(inv_rotator*rotator == m2_identity);
test.expect(t, inv_rotator*(rotator*v2) == v2); Expect(inv_rotator*(rotator*v2) == v2);
v2 = rotator*v2; v2 = rotator*v2;
v2 = inv_rotator*v2; v2 = inv_rotator*v2;
} }
}); });
test.run(basic.tprint("%: Mat4", UNITS), t => { Test(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]};
test.expect(t, a*identity == a); Expect(a*identity == a);
} }
{ {
UP_VECTOR :: Vec3.{.[0.0, 1.0, 0.0]}; UP_VECTOR :: Vec3.{.[0.0, 1.0, 0.0]};
@ -454,16 +452,16 @@ inverse :: (m: Mat4) -> Mat4 {
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);
test.expect(t, v4 == v4f(1.0, 2.0, 1.0, 1.0)); Expect(v4 == v4f(1.0, 2.0, 1.0, 1.0));
test.expect(t, translator*v4 == v4f(11.0, 7.0, 3.0, 1.0)); Expect(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;
test.expect(t, v4_eq(v4, v4f(-1.0, 0.5, -0.1, 1.0))); Expect(v4_eq(v4, v4f(-1.0, 0.5, -0.1, 1.0)));
v4 = rotator*v4; v4 = rotator*v4;
test.expect(t, v4_eq(v4, v4f(1.0, 0.5, 0.1, 1.0))); Expect(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));
@ -476,7 +474,7 @@ inverse :: (m: Mat4) -> Mat4 {
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);
test.expect(t, inv_rotator*rotator == m4_identity); Expect(inv_rotator*rotator == m4_identity);
v4 = rotator*v4; v4 = rotator*v4;
v4 = inv_rotator*v4; v4 = inv_rotator*v4;

View file

@ -5,22 +5,7 @@
RUN_TESTS := false RUN_TESTS := false
); );
#assert meta.type_is_scalar(RECT_TYPE); #assert 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";
@ -30,7 +15,8 @@ F64_Min, F64_Max :: #run meta.lo_for(float64), #run meta.hi_for(float64);
#scope_module; #scope_module;
meta :: #import "jc/meta"; #import "jc";
math :: #import "Math"; // @future math :: #import "Math"; // @future
basic :: #import "Basic"; // @future basic :: #import "Basic"; // @future

View file

@ -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 {
meta.check_bounds(idx, v.N); CheckBounds(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 {
meta.check_bounds(idx, v.N); CheckBounds(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 {
meta.check_bounds(idx, v.N); CheckBounds(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 meta.type_is_scalar(R), "type is not integer or float"; } { #modify { return 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 meta.type_is_scalar(R), "type is not integer or float"; } { #modify { return 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 meta.type_is_scalar(R), "type is not integer or float"; } { #modify { return 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 meta.type_is_scalar(R), "type is not integer or float"; } { #modify { return 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 meta.type_is_scalar(R), "type is not integer or float"; } { #modify { return 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 meta.type_is_float(T), "Used non-float vector on round"; } { #modify { return 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 meta.type_is_float(T), "use v2i for integer arguments"; } #modify { return 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 meta.type_is_integer(T), "use v2f for float arguments"; } #modify { return 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 meta.type_is_float(T), "use v3i for integer arguments"; } #modify { return 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 meta.type_is_integer(T), "use v3f for float arguments"; } #modify { return 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 meta.type_is_float(T), "use v4i for integer arguments"; } #modify { return 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 meta.type_is_integer(T), "use v4f for float arguments"; } #modify { return 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 };
} }
@ -654,76 +654,74 @@ cross :: (a: Vec3, b: Vec3) -> Vec3 {
#scope_file; #scope_file;
#if RUN_TESTS #run,stallable { #if RUN_TESTS #run,stallable {
test :: #import "jc/meta/test"; Test(basic.tprint("%: Vec2", UNITS), t => {
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);
test.expect(t, a + b == v2f(1.0, 3.0)); Expect(a + b == v2f(1.0, 3.0));
test.expect(t, b - a == v2f(1.0, 1.0)); Expect(b - a == v2f(1.0, 1.0));
test.expect(t, a*b == v2f(0.0, 2.0)); Expect(a*b == v2f(0.0, 2.0));
test.expect(t, a/b == v2f(0.0, 0.5)); Expect(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);
test.expect(t, a + b == v2i(3, 1)); Expect(a + b == v2i(3, 1));
test.expect(t, b - a == v2i(-1, -1)); Expect(b - a == v2i(-1, -1));
test.expect(t, a*b == v2i(2, 0)); Expect(a*b == v2i(2, 0));
test.expect(t, b/a == v2i(0, 0)); Expect(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);
test.expect(t, c == v2f(1.0, -4.1)); Expect(c == v2f(1.0, -4.1));
c = max(a, b); c = max(a, b);
test.expect(t, c == v2f(2.3, 3.6)); Expect(c == v2f(2.3, 3.6));
} }
}); });
test.run(basic.tprint("%: Vec3", UNITS), t => { Test(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);
test.expect(t, a + b == v3f(1.0, 3.0, 5.0)); Expect(a + b == v3f(1.0, 3.0, 5.0));
test.expect(t, b - a == v3f(1.0, 1.0, 1.0)); Expect(b - a == v3f(1.0, 1.0, 1.0));
test.expect(t, a*b == v3f(0.0, 2.0, 6.0)); Expect(a*b == v3f(0.0, 2.0, 6.0));
test.expect(t, a/b == v3f(0.0, 0.5, 0.66666667)); Expect(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);
test.expect(t, reflect(a, b) == v3f(1.0, -1.0, 0.0)); Expect(reflect(a, b) == v3f(1.0, -1.0, 0.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)); Expect(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);
test.expect(t, cross(a, b) == v3f(0.0, 0.0, 1.0)); Expect(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);
test.expect(t, c == v3f(1.0, -3.6, 5.0)); Expect(c == v3f(1.0, -3.6, 5.0));
c = max(a, b); c = max(a, b);
test.expect(t, c == v3f(2.3, 4.1, 9.0)); Expect(c == v3f(2.3, 4.1, 9.0));
} }
}); });
test.run(basic.tprint("%: Vec4", UNITS), t => { Test(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);
test.expect(t, a + b == v4f(6.25, 3.0, 5.0, 2.0)); Expect(a + b == v4f(6.25, 3.0, 5.0, 2.0));
test.expect(t, b - a == v4f(1.75, 1.0, 1.0, 0.0)); Expect(b - a == v4f(1.75, 1.0, 1.0, 0.0));
test.expect(t, a*b == v4f(9.0, 2.0, 6.0, 1.0)); Expect(a*b == v4f(9.0, 2.0, 6.0, 1.0));
test.expect(t, a/b == v4f(0.5625, 0.5, 2.0/3.0, 1.0)); Expect(a/b == v4f(0.5625, 0.5, 2.0/3.0, 1.0));
}); });
test.run(basic.tprint("%: VecN", UNITS), t => { Test(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 {
@ -732,59 +730,59 @@ cross :: (a: Vec3, b: Vec3) -> Vec3 {
for *b { for *b {
it.* = xx(it_index + 1); it.* = xx(it_index + 1);
} }
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(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, 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(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, 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(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, min(a, b) == a); Expect(min(a, b) == a);
test.expect(t, max(a, b) == b); Expect(max(a, b) == b);
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(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, 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(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, 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]}); 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]});
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]};
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(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, 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(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, dot(a, b) == 1360.0); Expect(dot(a, b) == 1360.0);
test.expect(t, abs(a) == a); Expect(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.*;
} }
test.expect(t, abs(c) == a); Expect(abs(c) == a);
test.expect(t, float_eq(length(normalize(a)), 1)); Expect(float_eq(length(normalize(a)), 1));
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, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]});
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).{.[-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]});
test.expect(t, a*2 == Vec(16, float).{.[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]}); Expect(a*2 == Vec(16, float).{.[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]});
}); });
test.run(basic.tprint("%: Quat", UNITS), t => { Test(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);
test.expect(t, q*inv_q == qi); Expect(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);
test.expect(t, float_eq(c.w, 0.0)); Expect(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;
test.expect(t, v3_eq(c2, v3f(math.sqrt(2.0), math.sqrt(2.0), 0.0))); Expect(v3_eq(c2, v3f(math.sqrt(2.0), math.sqrt(2.0), 0.0)));
test.expect(t, v3_eq(c1.xyz, c2)); Expect(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);
test.expect(t, v4_eq(m*p1, v4f(-2.0, 0.0, -0.0, 1.0))); Expect(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));

View file

@ -1,139 +0,0 @@
#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);
});
}

View file

@ -1,51 +0,0 @@
#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";
}

View file

@ -1,223 +0,0 @@
#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);
});
}

View file

@ -1,287 +0,0 @@
#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);
});
}

View file

@ -1,94 +0,0 @@
#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");
});
}

View file

@ -1,89 +0,0 @@
/*
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

View file

@ -1,244 +0,0 @@
#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);
});
}

View file

@ -1,15 +1,21 @@
/* /// Module jc contains procedures for working with memory,
Module jc contains procedures for working with memory, /// arrays, and strings; as well as helpful macros and
arrays, and strings; as well as helpful macros and /// constants.
constants. ///
/// Additionally, it provides a platform-independant
/// interface for interacting with the target operating
/// system.
#module_parameters(RunTests := false);
Additionally, it provides a platform-independant // @note(judah): this causes some very weird issues in tests so it's ifdef'd out
interface for interacting with the target operating #if !RunTests #add_context temp_allocator: Allocator;
system.
*/
#load "internal/builtin.jai"; #load "+internal/builtin.jai";
#load "internal/array.jai"; #load "+internal/array.jai";
#load "internal/memory.jai"; #load "+internal/kv.jai";
#load "internal/testing.jai"; #load "+internal/hashing.jai";
#load "internal/keywords.jai"; #load "+internal/memory.jai";
#load "+internal/allocators.jai";
#load "+internal/testing.jai";
#load "+internal/keywords.jai";
#load "+internal/type_info.jai";

View file

@ -1,107 +0,0 @@
#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

View file

@ -1,9 +0,0 @@
#module_parameters(RUN_TESTS := false, WITH_SUBMODULES := true);
#scope_export;
#if WITH_SUBMODULES {
using #import "jc/platform/arch"(RUN_TESTS);
}
#scope_module;

View file

@ -1,4 +1,4 @@
#module_parameters(RUN_TESTS := false); #module_parameters(RunTests := 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,4 +609,3 @@ at_end :: inline (p: *Parser) -> bool {
basic :: #import "Basic"; // @future basic :: #import "Basic"; // @future
strings :: #import "String"; // @future strings :: #import "String"; // @future