Compare commits

..

7 commits

Author SHA1 Message Date
49973e65ed Merge pull request 'Reorganize everything' (#2) from rejigger into master
Reviewed-on: #2
2025-09-06 07:06:59 +00:00
083cfdb035 fix no #asm guard for mul_sse 2025-09-06 01:06:03 -06:00
19bbf2abfd merge conflict 2025-09-06 01:03:07 -06:00
3bd9b6b3e5 update plan 2025-09-06 00:59:01 -06:00
cdfb6af00b re-org mostly done 2025-09-06 00:52:48 -06:00
5447a4ee06 spelling 2025-09-03 20:36:44 -06:00
a909496e27 reorg part 1 2025-09-03 20:27:41 -06:00
60 changed files with 3132 additions and 2589 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

250
+internal/array.jai Normal file
View file

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

181
+internal/builtin.jai Normal file
View file

@ -0,0 +1,181 @@
/// JcMajor is the current major version of this library.
///
/// Major versions are guaranteed to have stable apis
/// for their duration.
JcMajor :: 0;
/// JcMinor is the current minor version of this library.
///
/// Minor versions denote bug fixes, additions, or improvements
/// that do not affect api stability.
JcMinor :: 1;
// @note(judah): we can't use range_of here because a compiler bug?
S8Min :: #run min_of(s8); /// -128 (-0x80)
S8Max :: #run max_of(s8); /// 127 (0x7f)
S16Min :: #run min_of(s16); /// -32768 (-0x8000)
S16Max :: #run max_of(s16); /// 32767 (0x7f_ff)
S32Min :: #run min_of(s32); /// -2147483648 (-0x8000_0000)
S32Max :: #run max_of(s32); /// 2147483647 (0x7fff_ffff)
S64Min :: #run min_of(s64); /// -9223372036854775808 (-0x80000000_00000000)
S64Max :: #run max_of(s64); /// 9223372036854775807 (0x7fffffff_ffffffff)
U8Min :: #run min_of(u8); /// 0 (0x00)
U8Max :: #run max_of(u8); /// 255 (0xff)
U16Min :: #run min_of(u16); /// 0 (0x00_00)
U16Max :: #run max_of(u16); /// 65535 (0xff_ff)
U32Min :: #run min_of(u32); /// 0 (0x0000_0000)
U32Max :: #run max_of(u32); /// 4294967295 (0xffff_ffff)
U64Min :: #run min_of(u64); /// 0 (0x00000000_00000000)
U64Max :: #run max_of(u64); /// 18446744073709551615 (0xffffffff_ffffffff)
Float32Min :: #run min_of(float32); /// 1.17549e-38 (0h0080_0000)
Float32Max :: #run max_of(float32); /// 3.40282e+38 (0h7f7fffff)
Float64Min :: #run min_of(float64); /// 2.22507e-308 (0h00100000_00000000)
Float64Max :: #run max_of(float64); /// 1.79769e+308 (0h7fefffff_ffffffff)
/// m0 is a 0-size marker type.
///
/// It allows specific offsets within a type to be marked which is useful for (de)serialization.
///
/// MyType :: struct {
/// do_not_serialize_1: *void;
///
/// _start: m0; // Has the same offset as serialize_1
/// serialize_1: [32]u8;
/// serialize_2: u64;
/// serialize_3: bool;
/// serialize_4: float32;
/// _end: m0; // Has the same offset as serialize_4
///
/// do_not_serialize_2: [..]int;
/// }
///
/// value := MyType.{};
/// start := *value + offset_of(value, #code _start);
/// end := *value + offset_of(value, #code _end);
/// WriteToDisk(data = start, count = end - start);
///
m0 :: #type void;
b8 :: enum u8 { false_ :: (0 != 0).(u8); true_ :: (0 == 0).(u8); }; /// b8 is an 8-bit boolean.
b16 :: enum u16 { false_ :: (0 != 0).(u16); true_ :: (0 == 0).(u16); }; /// b16 is a 16-bit boolean.
b32 :: enum u32 { false_ :: (0 != 0).(u32); true_ :: (0 == 0).(u32); }; /// b32 is a 32-bit boolean.
b64 :: enum u64 { false_ :: (0 != 0).(u64); true_ :: (0 == 0).(u64); }; /// b64 is a 64-bit boolean.
/// Panic displays the given message and crashes the program.
///
/// Note: Defers will not run when Panic is called.
Panic :: (message := "jc: runtime panic", loc := #caller_location) #expand #no_debug {
#if DebugBuild {
WriteStderrLocation(loc);
WriteStderrString(": ");
}
WriteStderrString(message, "\n");
Trap();
}
/// Unreachable displays the given message and causes an execution trap.
///
/// Note: Defers will not run when Unreachable is called.
Unreachable :: (message := "jc: unreachable code hit", loc := #caller_location) #expand #no_debug {
trap :: #ifx DebugBuild then DebugTrap else Trap;
#if DebugBuild {
WriteStderrLocation(loc);
WriteStderrString(": ");
}
WriteStderrString(message, "\n");
trap();
}
/// CompileError displays the given message and stops compilation.
///
/// Note: By default, the error is reported at the callsite.
CompileError :: (message: string, loc := #caller_location) #expand #no_debug #compile_time {
if #compile_time {
compiler_report(message, loc, .ERROR);
}
else {
Panic("jc: CompileError can only be called at compile-time", loc = loc);
}
}
// @todo(judah): these should be different!
/// Trap causes an execution trap.
Trap :: () #expand #no_debug {
debug_break(); // Provided by Runtime_Support
}
/// DebugTrap causes an execution trap that grabs the attention of a debugger.
DebugTrap :: () #expand #no_debug {
debug_break(); // Provided by Runtime_Support
}
#if DebugBuild
{
/// Assert causes a debug break if the given condition is false.
Assert :: (cond: bool, message := "jc: condition was false", loc := #caller_location) #expand #no_debug {
// @note(judah): We only need to do this to route into the context's builtin assertion handling.
if cond || context.handling_assertion_failure return;
context.handling_assertion_failure = true;
should_trap := context.assertion_failed(loc, message);
context.handling_assertion_failure = false;
if should_trap {
DebugTrap();
}
}
/// AssertCallsite works identically to Assert, except that it expects a
/// Source_Code_Location (called 'loc') to exist in the calling scope.
///
/// MyProc :: (loc := #caller_location) {
/// AssertCallsite(false); // 'loc' is passed implicitly
/// Assert(false, loc = loc); // equivalent
/// }
///
AssertCallsite :: (cond: bool, message := "jc: condition was false") #expand #no_debug {
Assert(cond, message, loc = `loc);
}
}
else
{
// @note(judah): these need to be separate declarations so we can use #discard.
// otherwise, the compiler will generate instructions to setup the call when assertions are disabled.
Assert :: (#discard cond: bool, #discard message := "", #discard loc := #caller_location) #expand #no_debug {}
AssertCallsite :: (#discard cond: bool, #discard message := "") #expand #no_debug {}
}
#scope_module
WriteString :: write_strings; @jc.nodocs // Provided by Runtime_Support
WriteNumber :: write_number; @jc.nodocs // Provided by Runtime_Support
// @note(judah): This is a direct copy of Runtime_Support's write_loc since it's not exported
WriteStderrLocation :: (loc: Source_Code_Location) {
WriteStderrString(loc.fully_pathed_filename, ":");
WriteStderrNumber(loc.line_number);
WriteStderrString(",");
WriteStderrNumber(loc.character_number);
} @jc.nodocs
WriteStderrString :: #bake_arguments write_strings(to_standard_error = true); // Provided by Runtime_Support
WriteStderrNumber :: #bake_arguments write_number(to_standard_error = true); // Provided by Runtime_Support
#scope_file
DebugBuild :: #run -> bool {
// @note(judah): there's not really a good way to detect opt level/build type,
// so just check if debug info is being emitted.
#import "Compiler";
opts := get_build_options();
return opts.emit_debug_info != .NONE;
};

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;

278
+internal/keywords.jai Normal file
View file

@ -0,0 +1,278 @@
/// offset_of returns the byte offset of a field within the type T.
///
/// Note: T must be a struct type.
///
/// MyType :: struct { x: int; y: int; z: int; };
/// offset_of(MyType, #code y); // 8
///
offset_of :: ($T: Type, ident: Code, loc := #caller_location) -> int #expand {
#run (loc: Source_Code_Location) {
info := type_info(T);
if info.type != .STRUCT {
CompileError("jc: offset_of can only be used on struct types", loc = loc);
}
}(loc);
return #run -> int {
t: T = ---;
return (*t.#insert ident).(*void) - (*t).(*void);
};
}
/// offset_of returns the byte offset of a field within the type of value.
///
/// Note: If offset_of is given a pointer value, it will use the type pointed to.
///
/// value := struct{ x: int; y: int; z: int; }.{};
/// offset_of(value, #code y); // 8
///
offset_of :: (#discard value: $T, ident: Code, loc := #caller_location) -> int #expand {
type :: #run -> Type {
info := T.(*Type_Info);
if info.type == .POINTER {
info = info.(*Type_Info_Pointer).pointer_to;
// @question(judah): do we want it to traverse all the way up to a non-pointer type?
// I opted against because if you have a *T, you only want offset_of to get an offset
// from that pointer. What would you do with a field offset from **T?
if info.type == .POINTER {
CompileError("jc: offset_of only allows one level of pointer indirection.", loc = loc);
}
}
return get_type(info);
};
return offset_of(type, ident, loc = loc);
}
/// align_of returns the alignment of type T.
align_of :: ($T: Type) -> int #expand {
return #run -> int {
if size_of(T) == 0
{ return 0; }
info := type_info(struct{ p: u8; t: T; });
return info.members[1].offset_in_bytes.(int);
};
}
/// default_of returns a value of type T as if it was just instantiated.
///
/// Note: default_of will call the initializer for aggregate types, so you
/// may want zero_of instead.
default_of :: ($T: Type) -> T #expand {
default: T;
return default;
}
/// undefined_of returns a value of type T that has not been initialized.
undefined_of :: ($T: Type) -> T #expand {
uninit: T = ---;
return uninit;
}
/// zero_of returns a value of type T that has been zero-initialized.
///
/// Note: zero_of will not call the initializer for aggregate types, so you
/// may want default_of instead.
zero_of :: ($T: Type) -> T #expand {
zero := undefined_of(T);
MemZero(*zero);
return zero;
}
/// min_of returns the minimum value T can represent.
///
/// Note: T must be an integer, float, or enum type.
min_of :: ($T: Type, loc := #caller_location) -> T #expand {
return #run -> T {
info := T.(*Type_Info);
if info.type == {
case .INTEGER;
i := info.(*Type_Info_Integer);
if i.runtime_size == {
case 1; return (ifx i.signed then -0x80 else 0).(T, no_check);
case 2; return (ifx i.signed then -0x8000 else 0).(T, no_check);
case 4; return (ifx i.signed then -0x8000_0000 else 0).(T, no_check);
case 8; return (ifx i.signed then -0x8000_0000_0000_0000 else 0).(T, no_check);
case ; CompileError("jc: unknown integer size", loc = loc);
}
case .FLOAT;
if info.runtime_size == {
case 4; return (0h0080_0000).(T, no_check);
case 8; return (0h00100000_00000000).(T, no_check);
case ; CompileError("jc: unknown float size", loc = loc);
}
case .ENUM;
i := info.(*Type_Info_Enum);
if i.values.count == 0 {
return 0;
}
min: T = i.values[0].(T, no_check);
if i.internal_type.signed {
for i.values if it.(T) < min {
min = it.(T);
}
}
else {
for i.values if it.(T) < min {
min = it.(T);
}
}
return min;
case;
CompileError("jc: min_of requires an enum, integer, or float type", loc = loc);
}
return 0;
};
}
/// max_of returns the maximum value T can represent.
///
/// Note: T must be an integer, float, or enum type.
max_of :: ($T: Type, loc := #caller_location) -> T #expand {
return #run -> T {
info := T.(*Type_Info);
if info.type == {
case .INTEGER;
i := info.(*Type_Info_Integer);
if i.runtime_size == {
case 1; return (ifx i.signed then 0x7f else 0xff).(T, no_check);
case 2; return (ifx i.signed then 0x7fff else 0xffff).(T, no_check);
case 4; return (ifx i.signed then 0x7fff_ffff else 0xffff_ffff).(T, no_check);
case 8; return (ifx i.signed then 0x7fff_ffff_ffff_ffff else 0xffff_ffff_ffff_ffff).(T, no_check);
case ; CompileError("jc: unknown integer size", loc = loc);
}
case .FLOAT;
if info.runtime_size == {
case 4; return (0h7F7FFFFF).(T, no_check);
case 8; return (0h7FEFFFFF_FFFFFFFF).(T, no_check);
case ; CompileError("jc: unknown float size", loc = loc);
}
case .ENUM;
i := info.(*Type_Info_Enum);
if i.values.count == 0 {
return 0;
}
max := i.values[0].(T, no_check);
if i.internal_type.signed {
for i.values if xx it > max {
max = xx it;
}
}
else {
for i.values if xx it > max {
max = xx it;
}
}
return max;
case;
CompileError("jc: max_of requires an enum, integer, or float type", loc = loc);
}
return 0;
};
}
/// range_of returns the minimum and maximum values T can represent.
///
/// Note: T must be an integer, float, or enum type.
range_of :: ($T: Type, loc := #caller_location) -> (T, T) #expand {
return min_of(T, loc = loc), max_of(T, loc = loc);
}
/// sector creates a named block that can exit early via the 'break' keyword.
///
/// Note: The block created by sector is called 'early' by default.
///
/// for sector() {
/// break;
/// break early; // automatically created
/// }
///
/// for sector("render_player") {
/// break render_player;
/// }
///
sector :: ($name := Sector().Name) -> Sector(name) #expand { return .{}; }
// @note(judah): there seems to be a weird race condition in the compiler
// that causes this to hit a null reference check error if running at compile-time.
for_expansion :: (v: Sector, code: Code, _: For_Flags) #expand {
// @todo(judah): fix this case?
// 'for this: sector() { break early; break this; }'
// both names valid here!
#insert #run basic.tprint(#string END
for `%1: 0..0 {
`it :: #run zero_of(void);
`it_index :: #run zero_of(void);
#insert,scope(code) code;
}
END,
// @note(judah): guards against calling this_block with
// an empty string which results in weird error messages.
ifx v.Name.count != 0 v.Name else Sector().Name);
}
#scope_file
Sector :: struct(Name: string = "early") {}
basic :: #import "Basic"; // @future
#if RunTests #run {
Test("min_of/max_of:enums", t => {
U8Enum :: enum u8 { lo :: -1; hi :: +1; }
S8Enum :: enum s8 { lo :: -2; hi :: -1; }
{
Expect(min_of(U8Enum) == U8Enum.lo);
Expect(min_of(S8Enum) == S8Enum.lo);
Expect(max_of(U8Enum) == U8Enum.hi);
Expect(max_of(S8Enum) == S8Enum.hi);
}
U16Enum :: enum u16 { lo :: -1; hi :: +1; }
S16Enum :: enum s16 { lo :: -2; hi :: -1; }
{
Expect(min_of(U16Enum) == U16Enum.lo);
Expect(min_of(S16Enum) == S16Enum.lo);
Expect(max_of(U16Enum) == U16Enum.hi);
Expect(max_of(S16Enum) == S16Enum.hi);
}
U32Enum :: enum u32 { lo :: -1; hi :: +1; }
S32Enum :: enum s32 { lo :: -2; hi :: -1; }
{
Expect(min_of(U32Enum) == U32Enum.lo);
Expect(min_of(S32Enum) == S32Enum.lo);
Expect(max_of(U32Enum) == U32Enum.hi);
Expect(max_of(S32Enum) == S32Enum.hi);
}
U64Enum :: enum u64 { lo :: -1; hi :: +1; }
S64Enum :: enum s64 { lo :: -2; hi :: -1; }
{
Expect(min_of(U64Enum) == U64Enum.lo);
Expect(min_of(S64Enum) == S64Enum.lo);
Expect(max_of(U64Enum) == U64Enum.hi);
Expect(max_of(S64Enum) == S64Enum.hi);
}
// @note(judah): just making sure this compiles
lo, hi := range_of(U64Enum);
Expect(lo == U64Enum.lo);
Expect(hi == U64Enum.hi);
});
}

181
+internal/kv.jai 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);
});
}

89
+internal/memory.jai Normal file
View file

@ -0,0 +1,89 @@
Kilobyte :: 1024;
Megabyte :: 1024 * Kilobyte;
Gigabyte :: 1024 * Megabyte;
DefaultAlign :: #run 2 * align_of(*void);
/// MemEqual checks the equality of two pieces of memory.
///
/// Note: MemEqual will panic if size_in_bytes is negative.
MemEqual :: (p1: *void, p2: *void, size_in_bytes: int) -> bool {
if size_in_bytes < 0
{ Panic("jc: size_in_bytes cannot be negative"); }
return memcmp(p1, p2, size_in_bytes) == 0; // Provided by Preload
}
/// MemCopy copies the memory of src to dst.
///
/// Note: MemCopy will panic if size_in_bytes is negative.
MemCopy :: (dst: *void, src: *void, size_in_bytes: int) {
if size_in_bytes < 0
{ Panic("jc: size_in_bytes cannot be negative"); }
memcpy(dst, src, size_in_bytes); // Provided by Preload
}
/// MemOverwrite overwites the memory of p with value.
///
/// Note: MemOverwrite will panic if size_in_bytes is negative.
MemOverwrite :: (p: *void, size_in_bytes: int, value: u8 = 0) {
if size_in_bytes < 0
{ Panic("jc: size_in_bytes cannot be negative"); }
memset(p, value, size_in_bytes); // Provided by preload
}
/// MemZero zeroes the memory of p.
///
/// Note: MemZero will panic if size_in_bytes is negative.
MemZero :: (p: *void, size_in_bytes: int) {
MemOverwrite(p, size_in_bytes, 0);
}
/// MemZero zeroes the memory of p.
///
/// Note: MemZero will not call the initializer for aggregate types,
/// so you may want MemReset instead.
MemZero :: (p: *$T) {
MemOverwrite(p, size_of(T), 0);
}
/// MemReset resets the memory of p, as if it was just instantiated.
///
/// Note: MemReset will call the initializer for aggregate types, so you
/// may want MemZero instead.
MemReset :: (p: *$T) {
initializer :: initializer_of(T);
#if initializer {
inline initializer(p);
}
else {
inline MemZero(p);
}
}
AlignUpwards :: (ptr: int, align: int = DefaultAlign) -> int {
Assert(PowerOfTwo(align), "alignment must be a power of two");
p := ptr;
mod := p & (align - 1);
if mod != 0 then p += align - mod;
return p;
}
PowerOfTwo :: (x: int) -> bool {
if x == 0 return false;
return x & (x - 1) == 0;
}
NextPowerOfTwo :: (x: int) -> int #no_aoc {
Assert(PowerOfTwo(x), "value must be a power of two");
// Bit twiddling hacks next power of two
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x |= x >> 32;
return x + 1;
}

1
+internal/module.jai Normal file
View file

@ -0,0 +1 @@
#assert false "This module (jc/internal) is not expected to be imported directly. Import 'jc' instead.";

116
+internal/testing.jai Normal file
View file

@ -0,0 +1,116 @@
// Usage:
#if 0 {
#run,stallable {
Test("thing", t => {
Expect(some_condition, "error message: %", value);
});
Test("other thing", t => {
Expect(other_condition, "error message: %", value);
});
// ...
}
}
/// Test defines a new suite to be executed by the test runner.
///
/// See: Expect for more information.
///
/// Test("my_proc does what it should", t => {
/// value1 := my_proc(/* ... */);
/// Expect(value1 != 0, "my_proc returned zero!");
///
/// value2 := my_proc(/* .... */);
/// Expect(value2 > 0, "my_proc returned a negative number!");
/// });
///
Test :: (name: string, proc: (t: *void) -> (), loc := #caller_location) {
// @note(judah): incredibly dumb way to get nicer test runs
path := loc.fully_pathed_filename;
i := path.count - 1;
found_first_slash := false;
while i >= 0 {
if path[i] == "/" {
if found_first_slash {
i += 1;
break;
}
found_first_slash = true;
}
i -= 1;
}
if !found_first_slash {
path = strings.path_filename(loc.fully_pathed_filename);
}
else {
path.count -= i;
path.data += i;
}
WriteString(path, ",");
WriteNumber(loc.line_number);
WriteString(": ", name, "... ");
t: TestRun;
proc(*t);
if t.failed {
WriteString("failed");
}
else {
WriteString("ok");
}
WriteString(" (");
WriteNumber(t.total_ok);
WriteString("/");
WriteNumber(t.total_expects);
WriteString(")\n");
}
/// Expect checks the given condition, failing the current test if it is false.
///
/// Note: Expect must be called within a test. Additionally, it expects the test
/// parameter to be called 't'.
Expect :: (cond: bool, message := "", args: ..Any, loc := #caller_location) #expand {
run := `t.(*TestRun);
run.total_expects += 1;
if cond {
run.total_ok += 1;
return;
}
msg := "expectation failed";
if message.count != 0 {
msg = basic.tprint(message, ..args);
}
run.failed = true;
if #compile_time {
CompileError(msg, loc = loc);
}
else {
WriteStderrLocation(loc);
WriteStderrString(": ", msg, "\n");
}
}
#scope_file
TestRun :: struct {
location: Source_Code_Location;
total_expects: s64;
total_ok: s64;
failed: bool;
}
basic :: #import "Basic"; // @future
strings :: #import "String"; // @future
compiler :: #import "Compiler"; // @future

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);
}

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
.build/ .build/
**.dSYM **.dSYM
.DS_Store .DS_Store
docs/

30
INBOX
View file

@ -2,24 +2,6 @@
Put any questions for me in here! Put any questions for me in here!
[Jesse] [Jesse]
05.22.25 Judah
Instead of going with a separate 'arch' module, I
think we can roll that into the future 'platform'
module instead. There's enough overlap that I
think it makes sense. Let me know what you think,
thanks!
05.23.25 Judah
I went ahead and created the platform module and
added arch-specific extension checking. Sadly
these happen at runtime because we're manually
checking isa extension flags. It's probably worth
it to add a 'SIMD' constant that we set for
targets that will almost always support SIMD
instructions; unsure for now.
05.31.25 Jesse
platform/arch merge sounds good, we can keep that going. No thoughts so far on that.
The caching is a good idea, I wonder if we should make them enum_flags.
I don't know if it will go over a u64 though.
08.21.25 Judah 08.21.25 Judah
I'm going to do a pretty big reorg of the repo. I'll be working in a separate branch for the time being so it shouldn't have an effect on you. When I merge this back into master I'll move your changes/modules over to the new structure. I'm going to do a pretty big reorg of the repo. I'll be working in a separate branch for the time being so it shouldn't have an effect on you. When I merge this back into master I'll move your changes/modules over to the new structure.
@ -32,3 +14,15 @@
- base module (jc) imports most things you'd always want (memory allocation, strings, arrays, hash tables, macros, etc.) - base module (jc) imports most things you'd always want (memory allocation, strings, arrays, hash tables, macros, etc.)
- x, ext, and math stay the same - x, ext, and math stay the same
- other freestanding modules (say: encoding, math) are expected to be imported directly. None of this math/linalg or encoding/json nonsense - other freestanding modules (say: encoding, math) are expected to be imported directly. None of this math/linalg or encoding/json nonsense
05.31.25 Jesse
platform/arch merge sounds good, we can keep that going. No thoughts so far on that.
The caching is a good idea, I wonder if we should make them enum_flags.
I don't know if it will go over a u64 though.
05.23.25 Judah
I went ahead and created the platform module and
added arch-specific extension checking. Sadly
these happen at runtime because we're manually
checking isa extension flags. It's probably worth
it to add a 'SIMD' constant that we set for
targets that will almost always support SIMD
instructions; unsure for now.

41
PLAN.Judah 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 [jai install dir]\modules\jc "C:\path\to\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

@ -9,9 +9,10 @@ Getting Paid
------------ ------------
To pick up tasks, find something in TODO and message me To pick up tasks, find something in TODO and message me
your rate. If accepted, create a single commit moving it your rate (bonus points if your initials are "JC"). If
from the 'UP NEXT' section to your 'IN PROGRESS' section; accepted, create a single commit moving it from the 'UP
the commit message should only say 'start [id]'. NEXT' section to your 'IN PROGRESS' section; the commit
message should only say 'start [id]'.
Once the work is done (use as many commits as you'd like), Once the work is done (use as many commits as you'd like),
create another commit moving the task from your 'IN create another commit moving the task from your 'IN
@ -41,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.
@ -50,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.
@ -78,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
------- -------
@ -95,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:
@ -144,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
@ -186,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.

1382
_generate_docs.jai Normal file

File diff suppressed because it is too large Load diff

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

@ -2,14 +2,9 @@
compiler :: #import "Compiler"; compiler :: #import "Compiler";
compiler.set_build_options_dc(.{ do_output = false }); compiler.set_build_options_dc(.{ do_output = false });
#import "jc/array"(RUN_TESTS = true); _ :: #import "jc"(true);
#import "jc/encoding"(RUN_TESTS = true); _ :: #import "jc/math"(RUN_TESTS = true);
#import "jc/hash"(RUN_TESTS = true); _ :: #import "jc/fmt/base64"(true);
#import "jc/memory"(RUN_TESTS = true);
#import "jc/meta"(RUN_TESTS = true);
#import "jc/platform"(RUN_TESTS = true);
rmath :: #import "jc/math"(.radians, RUN_TESTS = true); _ :: #import "jc/x/json"(true);
dmath :: #import "jc/math"(.degrees, RUN_TESTS = true);
tmath :: #import "jc/math"(.turns, RUN_TESTS = 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

@ -0,0 +1 @@
NSObject :: struct {};

View file

@ -0,0 +1,21 @@
NSString :: struct {
// #as super: NSObject;
}
init :: (path: ) -> NSString {
}
sel: struct {
init_withBytes_length_encoding: objc.Sel;
init_contentsOfFile_encoding: objc.Sel;
UTF8String: objc.Sel;
};
init_everything :: () {
sel.init_withBytes_length_encoding = objc.sel_register_name("init:withBytes:length:encoding");
sel.init_contentsOfFile_encoding = objc.sel_register_name("init:contentsOfFile:encoding");
sel.UTF8String = objc.sel_register_name("UTF8String");
}
// #import "jc/meta/init"(init_everything);

View file

@ -0,0 +1,8 @@
NSObject :: struct {
id: u64;
}
NSString :: struct {
#as using isa: NSObject;
}

38
ext/darwin/foundation.jai Normal file
View file

@ -0,0 +1,38 @@
NSNumber :: struct {
// #as using isa: NSObject;
}
#scope_file;
Sel :: uptr;
sel: struct {
boolValue: Sel; @boolValue
intValue: Sel; @intValue
unsignedIntValue: Sel; @unsignedIntValue
floatValue: Sel; @floatValue
doubleValue: Sel; @doubleValue
}
#add_context InitFoundation :: () {
#import "Basic";
tmp: [512]u8;
base := (*sel).(*u8);
info := type_info(type_of(sel));
for info.members if it.notes.count != 0 {
name := it.notes[0];
memcpy(tmp.data, name.data, name.count);
tmp.data[name.count] = 0;
ptr := base + it.offset_in_bytes;
ptr.* = sel_register_name(tmp.data);
assert(ptr.* != 0, "failed to register selector: %", name);
}
print("%\n", info.*);
}
Foundation :: #library,system,no_dll,link_always "Foundation";

47
ext/darwin/module.jai Normal file
View file

@ -0,0 +1,47 @@
#scope_export;
UInt :: u64;
Id :: *object;
Class :: *class;
Sel :: *selector;
Bool :: u8;
True : Bool : 1;
False : Bool : 0;
msg_send :: () #foreign objc "objc_msgSend";
msg_send_super :: () #foreign objc "objc_msgSend_super";
msg_send_fpret :: () #foreign objc "objc_msgSend_fpret";
msg_send_stret :: () #foreign objc "objc_msgSend_stret";
get_class :: (name: *u8) -> Class #foreign objc "objc_getClass";
sel_get_name :: (sel: Sel) -> *u8 #foreign objc "sel_getName";
sel_register_name :: (str: *u8) -> Sel #foreign objc "sel_registerName";
sel_get_uid :: (str: *u8) -> Sel #foreign objc "sel_getUid";
obj_get_class :: (obj: Id) -> Class #foreign objc "object_getClass";
obj_set_class :: (obj: Id, cls: Class) -> Class #foreign objc "object_setClass";
obj_is_class :: (obj: Id) -> Bool #foreign objc "object_isClass";
obj_get_class_name :: (obj: Id) -> *u8 #foreign objc "object_getClassName";
obj_copy :: (obj: Id, size: u64) -> Id #foreign objc "object_copy";
obj_dispose :: (obj: Id) -> Id #foreign objc "object_dispose";
class_get_name :: (cls: Class) -> *u8 #foreign objc "class_getName";
class_get_super :: (cls: Class) -> Class #foreign objc "class_getSuperclass";
#scope_module;
class :: struct {};
object :: struct {};
method :: struct {};
ivar :: struct {};
category :: struct {};
protocol :: struct {};
selector :: struct {};
objc :: #library,system,link_always,no_dll "libobjc";
#import "jc";

View file

@ -1,46 +0,0 @@
-------------
Handmade Math
-------------
jai ./generate.jai # generate the bindings (not required)
#import "jc/hmm"(
STATIC = true, # if HMM should be linked statically (default: true)
SIMD = true, # if SIMD should be used (default: true)
UNITS = .radians, # angle units to use [radians, degrees, turns] (default: radians)
);
What
----
Configurable, auto-generated bindings for Handmade Math
How
---
These are generated from HandmadeMath.h using Jai's
Bindings_Generator module. Because HandmadeMath is a
header-only library, we need to compile it into a
static/dynamic library that can be used with Jai's FFI
system. 'generate.jai' creates both static and dynamic
libraries for each angle unit in HandmadeMath
(radians, degrees, turns) +- SIMD support. These are
placed in the corresponding 'win', 'mac', or 'linux'
directories.
'module.jai' conditionally links one of these libraries
based on the module parameters set.
A few liberties were taken during the binding process to
either fix issues with automatic binding generation, or
improve the usability of these bindings.
Here are the main changes:
- Converted procedure argument names from PascalCase
to snake_case
- Converted struct field names from PascalCase to
snake_case
- Procedure names still use PascalCase

View file

@ -1,7 +1,29 @@
/*
Module hmm provides bindings for the HandmadeMath C
library.
hmm conditionally links to a specific version of
HandmadeMath based on the module parameters passed
when importing.
Additionally, a few liberties were taken during the
binding process to either fix issues with automatic
binding generation, or improve the usability of these
bindings.
Here are the main changes:
<pre>
- Converted procedure argument names from PascalCase to snake_case
- Converted struct field names from PascalCase to snake_case
</pre>
*/
#module_parameters( #module_parameters(
/// Statically link to HandmadeMath.
STATIC := true, STATIC := true,
/// Enable SIMD support.
SIMD := true, SIMD := true,
UNITS := (enum { radians; degrees; turns; }).radians /// Angle units to use.
UNITS : enum { radians; degrees; turns; } = .radians
); );
#scope_export; #scope_export;

View file

@ -1,3 +1,7 @@
/*
Module luajit provides bindings for the LuaJIT C
library (2.1.1744318430)
*/
#module_parameters(STATIC := true); #module_parameters(STATIC := true);
#if STATIC { #if STATIC {

47
ext/objc/module.jai Normal file
View file

@ -0,0 +1,47 @@
#scope_export;
UInt :: u64;
Id :: *object;
Class :: *class;
Sel :: *selector;
Bool :: u8;
True : Bool : 1;
False : Bool : 0;
msg_send :: () #foreign objc "objc_msgSend";
msg_send_super :: () #foreign objc "objc_msgSend_super";
msg_send_fpret :: () #foreign objc "objc_msgSend_fpret";
msg_send_stret :: () #foreign objc "objc_msgSend_stret";
get_class :: (name: *u8) -> Class #foreign objc "objc_getClass";
sel_get_name :: (sel: Sel) -> *u8 #foreign objc "sel_getName";
sel_register_name :: (str: *u8) -> Sel #foreign objc "sel_registerName";
sel_get_uid :: (str: *u8) -> Sel #foreign objc "sel_getUid";
obj_get_class :: (obj: Id) -> Class #foreign objc "object_getClass";
obj_set_class :: (obj: Id, cls: Class) -> Class #foreign objc "object_setClass";
obj_is_class :: (obj: Id) -> Bool #foreign objc "object_isClass";
obj_get_class_name :: (obj: Id) -> *u8 #foreign objc "object_getClassName";
obj_copy :: (obj: Id, size: u64) -> Id #foreign objc "object_copy";
obj_dispose :: (obj: Id) -> Id #foreign objc "object_dispose";
class_get_name :: (cls: Class) -> *u8 #foreign objc "class_getName";
class_get_super :: (cls: Class) -> Class #foreign objc "class_getSuperclass";
#scope_module;
class :: struct {};
object :: struct {};
method :: struct {};
ivar :: struct {};
category :: struct {};
protocol :: struct {};
selector :: struct {};
objc :: #library,system,link_always,no_dll "libobjc";
#import "jc";

View file

@ -1,3 +1,10 @@
/*
Module raylib provides bindings for the raylib C
library (v5.5).
Supported platforms: Windows, Mac, Linux
*/
#module_parameters(STATIC := true); #module_parameters(STATIC := true);
#scope_export #scope_export

View file

@ -1,3 +1,7 @@
/*
Module remotery provides bindings for the Remotery
CPU/GPU profiling library.
*/
#module_parameters(STATIC := true); #module_parameters(STATIC := true);
#if STATIC { #if STATIC {

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,7 +88,7 @@ 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;
} }
@ -110,6 +110,6 @@ sign :: (x: float) -> float {
#scope_file #scope_file
#import "jc";
// sin, cos // sin, cos
meta :: #import "jc/meta";
math :: #import "Math"; math :: #import "Math";

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

@ -156,8 +156,33 @@ make_look_at :: (camera: Vec3, at: Vec3, up_vector: Vec3) -> Mat4
} }
operator* :: (a: Mat4, b: Mat4) -> Mat4 { operator* :: (a: Mat4, b: Mat4) -> Mat4 {
#if CPU == .X64 {
return mul_sse(a, b); return mul_sse(a, b);
} }
else {
r: Mat4 = ---;
r._00 = a._00*b._00 + a._01*b._10 + a._02*b._20 + a._03*b._30;
r._01 = a._00*b._01 + a._01*b._11 + a._02*b._21 + a._03*b._31;
r._02 = a._00*b._02 + a._01*b._12 + a._02*b._22 + a._03*b._32;
r._03 = a._00*b._03 + a._01*b._13 + a._02*b._23 + a._03*b._33;
r._10 = a._10*b._00 + a._11*b._10 + a._12*b._20 + a._13*b._30;
r._11 = a._10*b._01 + a._11*b._11 + a._12*b._21 + a._13*b._31;
r._12 = a._10*b._02 + a._11*b._12 + a._12*b._22 + a._13*b._32;
r._13 = a._10*b._03 + a._11*b._13 + a._12*b._23 + a._13*b._33;
r._20 = a._20*b._00 + a._21*b._10 + a._22*b._20 + a._23*b._30;
r._21 = a._20*b._01 + a._21*b._11 + a._22*b._21 + a._23*b._31;
r._22 = a._20*b._02 + a._21*b._12 + a._22*b._22 + a._23*b._32;
r._23 = a._20*b._03 + a._21*b._13 + a._22*b._23 + a._23*b._33;
r._30 = a._30*b._00 + a._31*b._10 + a._32*b._20 + a._33*b._30;
r._31 = a._30*b._01 + a._31*b._11 + a._32*b._21 + a._33*b._31;
r._32 = a._30*b._02 + a._31*b._12 + a._32*b._22 + a._33*b._32;
r._33 = a._30*b._03 + a._31*b._13 + a._32*b._23 + a._33*b._33;
return r;
}
}
// Note(Jesse): If you want to make it symmetric go ahead, I usually don't // Note(Jesse): If you want to make it symmetric go ahead, I usually don't
operator* :: (a: Mat4, v: Vec4) -> Vec4 { operator* :: (a: Mat4, v: Vec4) -> Vec4 {
@ -465,25 +490,6 @@ mul_sse :: inline (a: Mat4, b: Mat4) -> Mat4 {
movups [*r + 48], r0; movups [*r + 48], r0;
} }
// r._00 = a._00*b._00 + a._01*b._10 + a._02*b._20 + a._03*b._30;
// r._01 = a._00*b._01 + a._01*b._11 + a._02*b._21 + a._03*b._31;
// r._02 = a._00*b._02 + a._01*b._12 + a._02*b._22 + a._03*b._32;
// r._03 = a._00*b._03 + a._01*b._13 + a._02*b._23 + a._03*b._33;
// r._10 = a._10*b._00 + a._11*b._10 + a._12*b._20 + a._13*b._30;
// r._11 = a._10*b._01 + a._11*b._11 + a._12*b._21 + a._13*b._31;
// r._12 = a._10*b._02 + a._11*b._12 + a._12*b._22 + a._13*b._32;
// r._13 = a._10*b._03 + a._11*b._13 + a._12*b._23 + a._13*b._33;
// r._20 = a._20*b._00 + a._21*b._10 + a._22*b._20 + a._23*b._30;
// r._21 = a._20*b._01 + a._21*b._11 + a._22*b._21 + a._23*b._31;
// r._22 = a._20*b._02 + a._21*b._12 + a._22*b._22 + a._23*b._32;
// r._23 = a._20*b._03 + a._21*b._13 + a._22*b._23 + a._23*b._33;
// r._30 = a._30*b._00 + a._31*b._10 + a._32*b._20 + a._33*b._30;
// r._31 = a._30*b._01 + a._31*b._11 + a._32*b._21 + a._33*b._31;
// r._32 = a._30*b._02 + a._31*b._12 + a._32*b._22 + a._33*b._32;
// r._33 = a._30*b._03 + a._31*b._13 + a._32*b._23 + a._33*b._33;
return r; return r;
} }
// Note(Jesse): This procedure will crash if it tries to store or access an element across a cache line // Note(Jesse): This procedure will crash if it tries to store or access an element across a cache line
@ -568,50 +574,47 @@ mul_sse_aligned :: inline (dst: *Mat4, a: *Mat4, b: *Mat4) {
} }
#scope_file; #scope_file;
is_aligned :: (p: *void, bound: int) -> bool { is_aligned :: (p: *void, bound: int) -> bool {
return cast(int)p & (bound - 1) == 0; return cast(int)p & (bound - 1) == 0;
} }
#if #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]};
@ -623,16 +626,16 @@ is_aligned :: (p: *void, bound: int) -> bool {
translator := translate(v3f(10.0, 5.0, 2.0)); translator := translate(v3f(10.0, 5.0, 2.0));
v3 := v3f(1.0, 2.0, 1.0); v3 := v3f(1.0, 2.0, 1.0);
v4 := v4f(v3, 1.0); v4 := v4f(v3, 1.0);
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));
@ -645,7 +648,7 @@ is_aligned :: (p: *void, bound: int) -> bool {
inv_rotator := inverse(rotator); inv_rotator := inverse(rotator);
v4 := v4f(0.1, 1.0, 0.0, 1.0); v4 := v4f(0.1, 1.0, 0.0, 1.0);
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 };
} }
@ -661,76 +661,74 @@ same_dir :: (a: Vec3, b: Vec3) -> bool {
#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 {
@ -739,59 +737,59 @@ same_dir :: (a: Vec3, b: Vec3) -> bool {
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,59 +1,21 @@
#module_parameters(IMPORT_LOCATION := #caller_location); /// Module jc contains procedures for working with memory,
/// arrays, and strings; as well as helpful macros and
/// constants.
///
/// Additionally, it provides a platform-independant
/// interface for interacting with the target operating
/// system.
#module_parameters(RunTests := false);
#scope_export; // @note(judah): this causes some very weird issues in tests so it's ifdef'd out
#if !RunTests #add_context temp_allocator: Allocator;
byte :: u8; #load "+internal/builtin.jai";
cstring :: *byte; #load "+internal/array.jai";
rawptr :: *void; #load "+internal/kv.jai";
#load "+internal/hashing.jai";
#if size_of(int) == size_of(s64) { #load "+internal/memory.jai";
sint :: s64; #load "+internal/allocators.jai";
uint :: u64; #load "+internal/testing.jai";
} #load "+internal/keywords.jai";
else { #load "+internal/type_info.jai";
sint :: s32;
uint :: u32;
}
#if size_of(rawptr) == size_of(u64) {
uptr :: u64;
sptr :: s64;
}
else {
uptr :: u32;
sptr :: s32;
}
#if OS == {
case .WINDOWS;
#if CPU != .X64 #run report_error_on_import("JC is only supported x86-64 Windows systems, not %", CPU);
ARCH :: "x86-64";
PLATFORM :: "windows";
case .MACOS;
#if CPU == {
case .X64; ARCH :: "x86-64";
case .ARM64; ARCH :: "arm64";
}
PLATFORM :: "macos";
case .LINUX;
#if CPU != .X64 #run report_error_on_import("JC is only supported x86-64 Unix systems, not %", CPU);
ARCH :: "x86-64";
PLATFORM :: "unix";
case;
#run report_error_on_import("JC is not supported on % % systems", CPU, OS);
}
#scope_file;
report_error_on_import :: (fmt: string, args: ..Any) #compile_time {
basic :: #import "Basic"; // @future
compiler :: #import "Compiler"; // @future
compiler.compiler_report(basic.tprint(fmt, ..args), loc = IMPORT_LOCATION);
}

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