From cdfb6af00b0aa6f64bc2ccebc68b3e36b829cd50 Mon Sep 17 00:00:00 2001 From: Judah Caruso Date: Sat, 6 Sep 2025 00:52:48 -0600 Subject: [PATCH] re-org mostly done --- +internal/allocators.jai | 76 +++++ {internal => +internal}/array.jai | 61 +++- {internal => +internal}/builtin.jai | 17 +- hash/xxhash.jai => +internal/hashing.jai | 69 ++++- {internal => +internal}/keywords.jai | 63 +++- +internal/kv.jai | 181 +++++++++++ {internal => +internal}/memory.jai | 40 ++- {internal => +internal}/module.jai | 0 {internal => +internal}/testing.jai | 5 +- +internal/type_info.jai | 43 +++ INBOX | 20 +- PLAN.Judah | 29 ++ README | 51 +--- STYLEGUIDE | 98 ++---- _generate_docs.jai | 154 ++++++++-- _make_module.jai | 85 ------ _run_all_tests.jai | 8 +- array/bytes.jai | 58 ---- array/dynamic.jai | 116 ------- array/module.jai | 13 - array/stable.jai | 194 ------------ array/static.jai | 171 ----------- array/xar.jai | 11 - encoding/module.jai | 10 - {encoding => fmt}/base64.jai | 81 ++--- hash/module.jai | 10 - hash/murmur.jai | 58 ---- hash/table.jai | 191 ------------ math/common.jai | 4 +- math/ease.jai | 6 +- math/mat.jai | 32 +- math/module.jai | 20 +- math/vec.jai | 134 ++++---- memory/allocators.jai | 139 --------- memory/buffer.jai | 51 ---- memory/module.jai | 223 -------------- meta/macros.jai | 287 ------------------ meta/module.jai | 94 ------ meta/test/module.jai | 89 ------ meta/type_info.jai | 244 --------------- module.jai | 32 +- platform/arch.jai | 107 ------- platform/module.jai | 9 - {encoding => x}/json.jai | 5 +- .../reload/examples/everything-you-need.jai | 0 {meta => x}/reload/examples/quickstart.jai | 0 {meta => x}/reload/module.jai | 0 {meta => x}/reload/reload_main.jai | 0 48 files changed, 870 insertions(+), 2519 deletions(-) create mode 100644 +internal/allocators.jai rename {internal => +internal}/array.jai (71%) rename {internal => +internal}/builtin.jai (92%) rename hash/xxhash.jai => +internal/hashing.jai (51%) rename {internal => +internal}/keywords.jai (76%) create mode 100644 +internal/kv.jai rename {internal => +internal}/memory.jai (63%) rename {internal => +internal}/module.jai (100%) rename {internal => +internal}/testing.jai (92%) create mode 100644 +internal/type_info.jai create mode 100644 PLAN.Judah delete mode 100644 _make_module.jai delete mode 100644 array/bytes.jai delete mode 100644 array/dynamic.jai delete mode 100644 array/module.jai delete mode 100644 array/stable.jai delete mode 100644 array/static.jai delete mode 100644 array/xar.jai delete mode 100644 encoding/module.jai rename {encoding => fmt}/base64.jai (64%) delete mode 100644 hash/module.jai delete mode 100644 hash/murmur.jai delete mode 100644 hash/table.jai delete mode 100644 memory/allocators.jai delete mode 100644 memory/buffer.jai delete mode 100644 memory/module.jai delete mode 100644 meta/macros.jai delete mode 100644 meta/module.jai delete mode 100644 meta/test/module.jai delete mode 100644 meta/type_info.jai delete mode 100644 platform/arch.jai delete mode 100644 platform/module.jai rename {encoding => x}/json.jai (99%) rename {meta => x}/reload/examples/everything-you-need.jai (100%) rename {meta => x}/reload/examples/quickstart.jai (100%) rename {meta => x}/reload/module.jai (100%) rename {meta => x}/reload/reload_main.jai (100%) diff --git a/+internal/allocators.jai b/+internal/allocators.jai new file mode 100644 index 0000000..7ff9518 --- /dev/null +++ b/+internal/allocators.jai @@ -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 diff --git a/internal/array.jai b/+internal/array.jai similarity index 71% rename from internal/array.jai rename to +internal/array.jai index 4725da5..441d09b 100644 --- a/internal/array.jai +++ b/+internal/array.jai @@ -1,7 +1,39 @@ +/// Append pushes value to the end of an array, resizing if necessary. +/// +/// Note: If no allocator has been set, Append will use the current context allocator. +/// Note: Calls to Append may invalidate pre-existing pointers. +Append :: inline (arr: *[..]$T, value: T) -> *T { + TrySetAllocator(arr); + ptr := basic.array_add(arr); + ptr.* = value; + return ptr; +} + +/// Append pushes a default initialized value to the end of an array, +/// returning a pointer to the newly pushed value. +/// +/// Note: If no allocator has been set, Append will use the current context allocator. +/// Note: Calls to Append may invalidate pre-existing pointers. +Append :: inline (arr: *[..]$T) -> *T { + return Append(arr, default_of(T)); +} + +/// Resize grows the memory associated with an array to hold new_count values. +/// +/// Note: If the array has enough allocated memory to accomodate new_count, Resize does nothing. +/// Note: Resize does not guarantee pointer stability. +Resize :: inline (arr: *[..]$T, new_count: int) { + if new_count <= arr.allocated + { return; } + + TrySetAllocator(arr); + basic.array_reserve(arr, new_count,, allocator = arr.allocator); +} + /// Slice returns a subsection of an array. Slice :: (view: []$T, start_idx: int, count := -1, loc := #caller_location) -> []T { - AssertCallsite(start_idx >= +0 && start_idx < view.count, "incorrect slice bounds"); - AssertCallsite(count >= -1 && count < view.count, "incorrect slice length"); + 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; } @@ -9,15 +41,14 @@ Slice :: (view: []$T, start_idx: int, count := -1, loc := #caller_location) -> [ return .{ data = view.data + start_idx, count = count }; } -/// Reset sets an array's length to 0, allowing it to be reused -/// without allocating new memory. +/// 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 the memory of an array and sets its length to 0. -/// -/// Note: Clear does not free the array's memory. +/// 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; @@ -30,6 +61,18 @@ Equal :: (lhs: []$T, rhs: []T) -> bool { 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. @@ -121,7 +164,9 @@ Trim :: (view: []$T, cutset: []T, $flags: TrimFlags = .FromStart) -> []T { #scope_file -#if #exists(RunTests) #run,stallable { +basic :: #import "Basic"; // @future + +#if RunTests #run,stallable { Test("slice", t => { a1 := int.[ 1, 2, 3, 4, 5 ]; a2 := Slice(a1, 2); diff --git a/internal/builtin.jai b/+internal/builtin.jai similarity index 92% rename from internal/builtin.jai rename to +internal/builtin.jai index 3ceb238..8dcdcab 100644 --- a/internal/builtin.jai +++ b/+internal/builtin.jai @@ -67,7 +67,7 @@ b64 :: enum u64 { false_ :: (0 != 0).(u64); true_ :: (0 == 0).(u64); }; /// b64 /// Panic displays the given message and crashes the program. /// /// Note: Defers will not run when Panic is called. -Panic :: (message := "runtime panic", loc := #caller_location) #expand #no_debug { +Panic :: (message := "jc: runtime panic", loc := #caller_location) #expand #no_debug { #if DebugBuild { WriteStderrLocation(loc); WriteStderrString(": "); @@ -80,7 +80,7 @@ Panic :: (message := "runtime panic", loc := #caller_location) #expand #no_debug /// Unreachable displays the given message and causes an execution trap. /// /// Note: Defers will not run when Unreachable is called. -Unreachable :: (message := "unreachable code hit", loc := #caller_location) #expand #no_debug { +Unreachable :: (message := "jc: unreachable code hit", loc := #caller_location) #expand #no_debug { trap :: #ifx DebugBuild then DebugTrap else Trap; #if DebugBuild { @@ -100,7 +100,7 @@ CompileError :: (message: string, loc := #caller_location) #expand #no_debug #co compiler_report(message, loc, .ERROR); } else { - Panic("CompileError can only be called at compile-time", loc = loc); + Panic("jc: CompileError can only be called at compile-time", loc = loc); } } @@ -119,7 +119,7 @@ DebugTrap :: () #expand #no_debug { #if DebugBuild { /// Assert causes a debug break if the given condition is false. - Assert :: (cond: bool, message := "condition was false", loc := #caller_location) #expand #no_debug { + Assert :: (cond: bool, message := "jc: condition was false", loc := #caller_location) #expand #no_debug { // @note(judah): We only need to do this to route into the context's builtin assertion handling. if cond || context.handling_assertion_failure return; @@ -140,7 +140,7 @@ DebugTrap :: () #expand #no_debug { /// Assert(false, loc = loc); // equivalent /// } /// - AssertCallsite :: (cond: bool, message := "condition was false") #expand #no_debug { + AssertCallsite :: (cond: bool, message := "jc: condition was false") #expand #no_debug { Assert(cond, message, loc = `loc); } } @@ -166,6 +166,10 @@ WriteStderrLocation :: (loc: Source_Code_Location) { 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 { @@ -175,6 +179,3 @@ DebugBuild :: #run -> bool { opts := get_build_options(); return opts.emit_debug_info != .NONE; }; - -WriteStderrString :: #bake_arguments write_strings(to_standard_error = true); // Provided by Runtime_Support -WriteStderrNumber :: #bake_arguments write_number(to_standard_error = true); // Provided by Runtime_Support diff --git a/hash/xxhash.jai b/+internal/hashing.jai similarity index 51% rename from hash/xxhash.jai rename to +internal/hashing.jai index 3f69748..7aaafae 100644 --- a/hash/xxhash.jai +++ b/+internal/hashing.jai @@ -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 = <> 16; h *= 0x85ebca6b; + h ^= h >> 13; h *= 0xc2b2ae35; + h ^= h >> 16; + return h; +} // Implementation: Demetri Spanos (github.com/demetri/scribbles) // Jai Port: Jesse Coyle (github.com/Zilarrezko) -XXHash_Seed :: 0; +XXHashSeed :: 0; -xxhash64 :: inline (s: string, seed: u32 = XXHash_Seed) -> u64 { - return xxhash64(s.data, s.count, seed); +XXHash64 :: inline (s: string, seed: u32 = XXHashSeed) -> u64 { + return XXHash64(s.data, s.count, seed); } -xxhash64 :: inline (x: $T, seed: u32 = XXHash_Seed) -> u64 { - return xxhash64(cast(*u8)*x, size_of(T), seed); +XXHash64 :: inline (x: $T, seed: u32 = XXHashSeed) -> u64 { + 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; p2: u64 : 0xc2b2ae3d27d4eb4f; p3: u64 : 0x165667b19e3779f9; diff --git a/internal/keywords.jai b/+internal/keywords.jai similarity index 76% rename from internal/keywords.jai rename to +internal/keywords.jai index e21e8ef..df273c7 100644 --- a/internal/keywords.jai +++ b/+internal/keywords.jai @@ -9,7 +9,7 @@ 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("offset_of can only be used on struct types", loc = loc); + CompileError("jc: offset_of can only be used on struct types", loc = loc); } }(loc); @@ -35,7 +35,7 @@ offset_of :: (#discard value: $T, ident: Code, loc := #caller_location) -> int # // I opted against because if you have a *T, you only want offset_of to get an offset // from that pointer. What would you do with a field offset from **T? if info.type == .POINTER { - CompileError("offset_of only allows one level of pointer indirection.", loc = loc); + CompileError("jc: offset_of only allows one level of pointer indirection.", loc = loc); } } @@ -95,14 +95,14 @@ min_of :: ($T: Type, loc := #caller_location) -> T #expand { case 2; return (ifx i.signed then -0x8000 else 0).(T, no_check); case 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("unknown integer size", loc = loc); + 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("unknown float size", loc = loc); + case ; CompileError("jc: unknown float size", loc = loc); } case .ENUM; @@ -126,7 +126,7 @@ min_of :: ($T: Type, loc := #caller_location) -> T #expand { return min; case; - CompileError("min_of requires an enum, integer, or float type", loc = loc); + CompileError("jc: min_of requires an enum, integer, or float type", loc = loc); } return 0; @@ -147,14 +147,14 @@ max_of :: ($T: Type, loc := #caller_location) -> T #expand { case 2; return (ifx i.signed then 0x7fff else 0xffff).(T, no_check); case 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("unknown integer size", loc = loc); + 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("unknown float size", loc = loc); + case ; CompileError("jc: unknown float size", loc = loc); } case .ENUM; @@ -178,7 +178,7 @@ max_of :: ($T: Type, loc := #caller_location) -> T #expand { return max; case; - CompileError("max_of requires an enum, integer, or float type", loc = loc); + CompileError("jc: max_of requires an enum, integer, or float type", loc = loc); } return 0; @@ -230,4 +230,49 @@ for_expansion :: (v: Sector, code: Code, _: For_Flags) #expand { Sector :: struct(Name: string = "early") {} -basic :: #import "Basic"; +basic :: #import "Basic"; // @future + +#if RunTests #run { + Test("min_of/max_of:enums", t => { + U8Enum :: enum u8 { lo :: -1; hi :: +1; } + S8Enum :: enum s8 { lo :: -2; hi :: -1; } + { + Expect(min_of(U8Enum) == U8Enum.lo); + Expect(min_of(S8Enum) == S8Enum.lo); + Expect(max_of(U8Enum) == U8Enum.hi); + Expect(max_of(S8Enum) == S8Enum.hi); + } + + U16Enum :: enum u16 { lo :: -1; hi :: +1; } + S16Enum :: enum s16 { lo :: -2; hi :: -1; } + { + Expect(min_of(U16Enum) == U16Enum.lo); + Expect(min_of(S16Enum) == S16Enum.lo); + Expect(max_of(U16Enum) == U16Enum.hi); + Expect(max_of(S16Enum) == S16Enum.hi); + } + + U32Enum :: enum u32 { lo :: -1; hi :: +1; } + S32Enum :: enum s32 { lo :: -2; hi :: -1; } + { + Expect(min_of(U32Enum) == U32Enum.lo); + Expect(min_of(S32Enum) == S32Enum.lo); + Expect(max_of(U32Enum) == U32Enum.hi); + Expect(max_of(S32Enum) == S32Enum.hi); + } + + U64Enum :: enum u64 { lo :: -1; hi :: +1; } + S64Enum :: enum s64 { lo :: -2; hi :: -1; } + { + Expect(min_of(U64Enum) == U64Enum.lo); + Expect(min_of(S64Enum) == S64Enum.lo); + Expect(max_of(U64Enum) == U64Enum.hi); + Expect(max_of(S64Enum) == S64Enum.hi); + } + + // @note(judah): just making sure this compiles + lo, hi := range_of(U64Enum); + Expect(lo == U64Enum.lo); + Expect(hi == U64Enum.hi); + }); +} diff --git a/+internal/kv.jai b/+internal/kv.jai new file mode 100644 index 0000000..9a188b7 --- /dev/null +++ b/+internal/kv.jai @@ -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); + }); +} diff --git a/internal/memory.jai b/+internal/memory.jai similarity index 63% rename from internal/memory.jai rename to +internal/memory.jai index aa49082..81e6199 100644 --- a/internal/memory.jai +++ b/+internal/memory.jai @@ -1,9 +1,15 @@ +Kilobyte :: 1024; +Megabyte :: 1024 * Kilobyte; +Gigabyte :: 1024 * Megabyte; + +DefaultAlign :: #run 2 * align_of(*void); + /// MemEqual checks the equality of two pieces of memory. /// /// 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("size_in_bytes cannot be negative"); } + { Panic("jc: size_in_bytes cannot be negative"); } return memcmp(p1, p2, size_in_bytes) == 0; // Provided by Preload } @@ -12,7 +18,7 @@ MemEqual :: (p1: *void, p2: *void, size_in_bytes: int) -> bool { /// 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("size_in_bytes cannot be negative"); } + { Panic("jc: size_in_bytes cannot be negative"); } memcpy(dst, src, size_in_bytes); // Provided by Preload } @@ -21,7 +27,7 @@ MemCopy :: (dst: *void, src: *void, size_in_bytes: int) { /// Note: MemOverwrite will panic if size_in_bytes is negative. MemOverwrite :: (p: *void, size_in_bytes: int, value: u8 = 0) { if size_in_bytes < 0 - { Panic("size_in_bytes cannot be negative"); } + { Panic("jc: size_in_bytes cannot be negative"); } memset(p, value, size_in_bytes); // Provided by preload } @@ -53,3 +59,31 @@ MemReset :: (p: *$T) { 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; +} diff --git a/internal/module.jai b/+internal/module.jai similarity index 100% rename from internal/module.jai rename to +internal/module.jai diff --git a/internal/testing.jai b/+internal/testing.jai similarity index 92% rename from internal/testing.jai rename to +internal/testing.jai index ce334ba..d7f2c39 100644 --- a/internal/testing.jai +++ b/+internal/testing.jai @@ -25,7 +25,7 @@ /// Expect(value2 > 0, "my_proc returned a negative number!"); /// }); /// -Test :: (name: string, proc: (*void) -> (), loc := #caller_location) { +Test :: (name: string, proc: (t: *void) -> (), loc := #caller_location) { // @note(judah): incredibly dumb way to get nicer test runs path := loc.fully_pathed_filename; @@ -76,7 +76,8 @@ Test :: (name: string, proc: (*void) -> (), loc := #caller_location) { /// Expect checks the given condition, failing the current test if it is false. /// -/// Note: Expect must be called within a test. +/// Note: Expect must be called within a test. Additionally, it expects the test +/// parameter to be called 't'. Expect :: (cond: bool, message := "", args: ..Any, loc := #caller_location) #expand { run := `t.(*TestRun); run.total_expects += 1; diff --git a/+internal/type_info.jai b/+internal/type_info.jai new file mode 100644 index 0000000..2db015d --- /dev/null +++ b/+internal/type_info.jai @@ -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); +} diff --git a/INBOX b/INBOX index 0d00d22..eb6716e 100644 --- a/INBOX +++ b/INBOX @@ -2,12 +2,10 @@ Put any questions for me in here! [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.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 @@ -16,7 +14,9 @@ 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. + 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! diff --git a/PLAN.Judah b/PLAN.Judah new file mode 100644 index 0000000..2b0a257 --- /dev/null +++ b/PLAN.Judah @@ -0,0 +1,29 @@ +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, future todos, etc. + +/ 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 + + + diff --git a/README b/README index 7fa7e37..2cd4008 100644 --- a/README +++ b/README @@ -1,46 +1,15 @@ --- -jc --- +-------------- +jc Jai Library +-------------- - # Direct installation - cd [jai install dir]/modules - git clone https://git.brut.systems/judah/jc.git - - # Indirect installation - git clone https://git.brut.systems/judah/jc.git - - ln -s "/path/to/jc" [jai install dir]/modules/jc # POSIX install - mklink /D "C:\path\to\jc" [jai install dir]\jc # Windows install - - #import "jc"; - #import "jc/[module]"; +For installation instructions and documentation, see: +https://judahcaruso.com/jc -What ----- +For local documentation, run 'jai _generate_docs.jai' then +navigate to the 'docs' subdirectory. -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. +License +------- +Public Domain diff --git a/STYLEGUIDE b/STYLEGUIDE index 82c4b47..b0a2ef9 100644 --- a/STYLEGUIDE +++ b/STYLEGUIDE @@ -42,8 +42,8 @@ Communication ------------- If direction is needed, create a single commit with an -entry in 'INBOX' under that person's section; the commit -message should roughly say 'message for [name]'. +entry in 'INBOX' at the top of that person's section; the +commit message should roughly say 'message for [name]'. If an immediate response is needed, opt for direct messages instead. @@ -51,14 +51,14 @@ messages instead. Use this as a base plate to write new messages: [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 Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor 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. @@ -79,13 +79,20 @@ Things I actually care about: premise that code is self-documenting. Take a step back and write a comment (with your name) that explains *why* you're doing *what* you're doing. - Please try to stick to this format: - @note, @todo, @temp, @allocates, @leak + Sticking to this format makes my life easier: + @note(name), @todo, @temp, etc. - Descriptive names. In general, it's fine to use short names for local variables. However, you should almost always opt for longer, more descriptive names 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 @@ -96,48 +103,24 @@ namespace them. The eventual goal of this repo is to be a standalone library that doesn't rely on compiler-shipped modules (unless absolutely required). - basic :: #import "Basic"; + basic :: #import "Basic"; // @future When importing 'jc' modules, ALWAYS namespace them and use absolute import paths. '#import,file' does not function the same as '#import' and causes issues when mixed -with '#load'. '_run_all_tests.jai' is an exception to -this rule. +with '#load'. + #import "jc"; // the base module can be unnamed 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 ----------------- -If a custom type needs dynamically allocated memory to -function, it should always assume the memory came from an -arena allocator. This means it is the responsibility of -the arena to free the memory, not the custom data type. +If a type needs dynamically allocated memory to function, +it should always assume the memory came from an arena +allocator. This means it is the responsibility of the +arena to free the memory, not the data type. In other words: @@ -145,39 +128,7 @@ Do *not* add procedures that 'free' or 'delete' the memory allocated by the data type. Instead, add procedures that 'reset' the data type, -allowing its memory to be reused. For examples, see -the 'reset' procedures in 'jc/kv' or 'jc/array'. - - -OS-Specific Code ----------------- - -When writing code for a specific operating system, use a -switch statement over multiple files. - - // Top-level scope of file: - #if OS == { - case .WINDOWS; - case .MACOS; - case .LINUX; - case; #assert false, "unimplemented platform"; - } - -If multiple files are required, use these file suffixes -and conditionally '#load' them based on 'OS'. - - Windows: '_win.jai' - Mac: '_mac.jai' - Linux: '_linux.jai' - WASM: '_wasm.jai' - - -Architecture-Specific Code --------------------------- - -When writing code for a specific architecture, use -the 'jc/arch' module. NEVER create a new file unless -absolutely needed. +allowing its memory to be reused. Bindings @@ -187,11 +138,10 @@ Binding modules should default to static linking and take an optional '#module_parameter' to link dynamically. Libraries should be pinned to a specific version, and all 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. Bindings should stay as close as possible to the original library. To jai-ify the bindings, create a submodule called 'wrapper' that import and wraps the api. -See: 'thirdparty/raylib' for example bindings. diff --git a/_generate_docs.jai b/_generate_docs.jai index fddb08a..caa0967 100644 --- a/_generate_docs.jai +++ b/_generate_docs.jai @@ -18,14 +18,19 @@ UseLocalLinks :: false; // set to false when deploying the docs // Add freestanding modules here. // Other modules will be automatically generated if they're imported by someone else. - jc :: #import "jc"; - hmm :: #import "jc/ext/hmm"; - luajit :: #import "jc/ext/luajit"; - raylib :: #import "jc/ext/raylib"; - remotery :: #import "jc/ext/remotery"; + _ :: #import "jc"; + _ :: #import "jc/math"; + _ :: #import "jc/fmt/base64"; - // darwin :: #import "jc/ext/darwin"; - // objc :: #import "jc/ext/objc"; + _ :: #import "jc/x/json"; + + _ :: #import "jc/ext/hmm"; + _ :: #import "jc/ext/luajit"; + _ :: #import "jc/ext/raylib"; + _ :: #import "jc/ext/remotery"; + // _ :: #import "jc/ext/darwin"; + // _ :: #import "jc/ext/objc"; + // _ :: #import "jc/ext/win32"; END, ws); CheckImport :: (import: *Code_Directive_Import) -> bool { @@ -103,6 +108,12 @@ UseLocalLinks :: false; // set to false when deploying the docs array = *mod.macros; } + // there should really be a flag on the proc header... + first := to_lower(decl.name[0]); + if !(first >= #char "a" && first <= #char "z") { + decl.name = tprint("operator%", decl.name); + } + array_add(array, .{ decl = decl, node = header, @@ -114,6 +125,9 @@ UseLocalLinks :: false; // set to false when deploying the docs if !(decl.flags & .IS_CONSTANT) { continue; } + if decl.name == "RunTests" || decl.name == "RUN_TESTS" + { continue; } + array := *mod.consts; // Things we don't want to be constant decls @@ -188,20 +202,49 @@ UseLocalLinks :: false; // set to false when deploying the docs join(..contributors, ", "), ); - append(*b, #string END -
-

Modules

-
    - END); - for quick_sort(names, SortAlphabeticallyDescendingLength) { - print_to_builder(*b, "
  • %2
  • ", LinkableName(it), it); - } + sorted_names := quick_sort(names, SortAlphabeticallyDescendingLength); append(*b, #string END -
-
+
END); + { + append(*b, #string END +
+

Modules

+
    + END); + + + for sorted_names if !contains(it, "jc/ext") { + print_to_builder(*b, "
  • %2
  • ", LinkableName(it), it); + } + + append(*b, #string END +
+
+ END); + } + { + append(*b, #string END +
+

Bindings

+
    + END); + + for sorted_names if contains(it, "jc/ext") { + print_to_builder(*b, "
  • %2
  • ", LinkableName(it), it); + } + + append(*b, #string END +
+
+ END); + } + + append(*b, "
"); + + append(*b, #string END

What:
@@ -235,8 +278,8 @@ git clone https://git.brut.systems/judah/jc.git # Indirect installation git clone https://git.brut.systems/judah/jc.git -ln -s "/path/to/jc" [jai install dir]/modules/jc # POSIX install -mklink /D "C:\path\to\jc" [jai install dir]\jc # Windows install +ln -s "$(pwd)jc" [jai install dir]/modules/jc # POSIX install +mklink /D "C:\path\to\jc" [jai install dir]\jc # Windows install # Usage: #import "jc"; @@ -256,9 +299,9 @@ mklink /D "C:\path\to\jc" [jai install dir]\jc # Windows install source_files: Table(string, []string); GetSourceLines :: (filename: string) -> []string, bool { - // old_logger := context.logger; - // context.logger = (message: string, data: *void, info: Log_Info) {}; // do nothing logger - // defer context.logger = old_logger; + old_logger := context.logger; + context.logger = (message: string, data: *void, info: Log_Info) {}; // do nothing logger + defer context.logger = old_logger; fok, src := table_find_new(*source_files, filename); if fok { @@ -266,12 +309,15 @@ GetSourceLines :: (filename: string) -> []string, bool { } - data, rok := read_entire_file(filename); lines: []string; + data, rok := read_entire_file(filename); if rok { lines = split(data, "\n"); table_add(*source_files, filename, lines); } + else { + print("warn: file didn't exist '%'\n", filename); + } return lines, rok; } @@ -374,6 +420,13 @@ ModuleToHtml :: (mod: *Module) -> string { StartPage(*b, tprint("Module % – Jc v%.% Documentation", import_name, JcMajor, JcMinor)); print_to_builder(*b, "

[..] Module – %

", short_name); print_to_builder(*b, "

%

", mod.main_docs); + + if contains(import_name, "jc/x") { + append(*b, ""); + print_to_builder(*b, "Warning: % is experimental and has no stability guarantees or versioning; use with caution.", short_name); + append(*b, ""); + } + print_to_builder(*b, "
#import \"%\";
", import_name); PrintIndexFor :: (b: *String_Builder, name: string, decls: []Decl) { @@ -729,6 +782,10 @@ CleanJaiSource :: (node: *Code_Node) -> string { postfix = tprint("% #compile_time", postfix); } + if header.procedure_flags & .SYMMETRIC { + postfix = tprint("% #symmetric", postfix); + } + return tprint("%1%2", trim(tail), postfix); } // for everything else, get the source range for the node and only strip out @@ -791,18 +848,21 @@ CleanJaiSource :: (node: *Code_Node) -> string { source := join(..cleaned_lines, "\n"); index := find_index_from_left(source, "::"); - // This is like 95% a module parameter + // @todo(judah): handle module parameters correctly if index == -1 { #import "Program_Print"; b: String_Builder; + + is_module_param := true; if decl.expression != null { print_expression(*b, decl.expression); } else if decl.type_inst { print_expression(*b, decl.type_inst); + is_module_param = false; } - append(*b, "; // #module_parameter"); + append(*b, ";"); return builder_to_string(*b); } @@ -939,12 +999,15 @@ MainCss :: #string END } body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif; line-height: 1.6; color: var(--text-primary); background-color: var(--bg-primary); margin: 0; padding: 0; + font-feature-settings: 'kern' 1, 'liga' 1, 'calt' 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } h1 { @@ -966,7 +1029,7 @@ h4 { font-size: 1.1rem; font-weight: 600; margin: 1.5rem 0 1rem 0; - font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; + font-family: 'Menlo', 'Monaco', 'SF Mono', 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; } a { @@ -1045,7 +1108,7 @@ h4 a:hover { .index a { color: var(--link-color); - font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; + font-family: 'Menlo', 'Monaco', 'SF Mono', 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; } .index a:hover { @@ -1073,9 +1136,12 @@ pre.code { padding: 1rem; margin: 1rem 0; overflow-x: auto; - font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; + font-family: 'Menlo', 'Monaco', 'SF Mono', 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; font-size: 0.9rem; line-height: 1.4; + font-feature-settings: 'liga' 1, 'calt' 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } span.ident { @@ -1151,6 +1217,23 @@ main { font-size: 1.6rem; margin: 1.5rem 0 0.75rem 0; } + + h4 { + font-size: 1rem; + font-family: 'Menlo', 'Monaco', Consolas, 'Courier New', monospace; + } + + .index a { + font-family: 'Menlo', 'Monaco', Consolas, 'Courier New', monospace; + } + + pre.code { + font-family: 'Menlo', 'Monaco', Consolas, 'Courier New', monospace; + font-size: 0.85rem; + padding: 0.75rem; + -webkit-text-size-adjust: 100%; + text-rendering: optimizeLegibility; + } } a:focus, @@ -1196,6 +1279,17 @@ html { outline: 2px solid var(--accent-color); outline-offset: 2px; } + +span.module-warning { + background-color: var(--bg-primary); + color: var(--accent-color); + display: block; + border-radius: 6px; + font-weight: bold; + border: 1px solid var(--accent-color); + border-radius: 6px; + padding: 0.5rem; + } END; ResetCss :: #string END @@ -1275,7 +1369,7 @@ textarea:not([rows]) { END; -#load "./module.jai"; +#import "jc"; #import "String"; #import "Compiler"; diff --git a/_make_module.jai b/_make_module.jai deleted file mode 100644 index 03d3352..0000000 --- a/_make_module.jai +++ /dev/null @@ -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"; diff --git a/_run_all_tests.jai b/_run_all_tests.jai index 4afde19..33c51fb 100644 --- a/_run_all_tests.jai +++ b/_run_all_tests.jai @@ -1,8 +1,10 @@ -RunTests :: true; - #scope_file #run { compiler :: #import "Compiler"; compiler.set_build_options_dc(.{ do_output = false }); - #load "module.jai"; + _ :: #import "jc"(true); + _ :: #import "jc/math"(RUN_TESTS = true); + _ :: #import "jc/fmt/base64"(true); + + _ :: #import "jc/x/json"(true); } diff --git a/array/bytes.jai b/array/bytes.jai deleted file mode 100644 index 77e67b7..0000000 --- a/array/bytes.jai +++ /dev/null @@ -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"; -} - diff --git a/array/dynamic.jai b/array/dynamic.jai deleted file mode 100644 index 6f411ea..0000000 --- a/array/dynamic.jai +++ /dev/null @@ -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); - }); -} diff --git a/array/module.jai b/array/module.jai deleted file mode 100644 index a3a2c46..0000000 --- a/array/module.jai +++ /dev/null @@ -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; diff --git a/array/stable.jai b/array/stable.jai deleted file mode 100644 index 7f7866a..0000000 --- a/array/stable.jai +++ /dev/null @@ -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); - }); -} diff --git a/array/static.jai b/array/static.jai deleted file mode 100644 index 8f2ed69..0000000 --- a/array/static.jai +++ /dev/null @@ -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); - }); -} diff --git a/array/xar.jai b/array/xar.jai deleted file mode 100644 index 2ff7f83..0000000 --- a/array/xar.jai +++ /dev/null @@ -1,11 +0,0 @@ -#module_parameters(RUN_TESTS := false); - -#scope_file; - -// ---------------------------------------------------------- -// TESTS -// ---------------------------------------------------------- - -#if RUN_TESTS #run { - test :: #import "jc/meta/test"; -} diff --git a/encoding/module.jai b/encoding/module.jai deleted file mode 100644 index 4f0e72d..0000000 --- a/encoding/module.jai +++ /dev/null @@ -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; diff --git a/encoding/base64.jai b/fmt/base64.jai similarity index 64% rename from encoding/base64.jai rename to fmt/base64.jai index faa8a52..0f2268b 100644 --- a/encoding/base64.jai +++ b/fmt/base64.jai @@ -1,16 +1,16 @@ -#module_parameters(RUN_TESTS := false); +#module_parameters(RunTests := false); -base64_encode :: (str: string, $for_url := false) -> string, bool { - enc, ok := base64_encode(str.([]u8), for_url); +Base64Encode :: (str: string, $for_url := false) -> string, bool { + enc, ok := Base64Encode(str.([]u8), for_url); return enc.(string), ok; } -base64_decode :: (str: string) -> string, bool { - enc, ok := base64_decode(str.([]u8)); +Base64Decode :: (str: string) -> string, bool { + enc, ok := Base64Decode(str.([]u8)); 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 for_url { @@ -21,7 +21,7 @@ base64_encode :: ($$data: []u8, $for_url := false) -> []u8, bool { } 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; dst_idx := 0; @@ -70,7 +70,7 @@ base64_encode :: ($$data: []u8, $for_url := false) -> []u8, bool { return encoded, true; } -base64_decode :: (data: []u8) -> []u8, bool { +Base64Decode :: (data: []u8) -> []u8, bool { if !data.count return .[], false; lookup :: (c: u8) -> u8 #expand { @@ -144,17 +144,20 @@ base64_decode :: (data: []u8) -> []u8, bool { return decoded, true; } -#scope_file; + +#scope_file Padding_Character :: #char "="; Encoding_Url :: "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; return (count * 8 + 5) / 6; } +#import "jc"; + basic :: #import "Basic"; // @future strings :: #import "String"; // @future @@ -163,47 +166,45 @@ strings :: #import "String"; // @future // TESTS // ---------------------------------------------------------- -#if RUN_TESTS #run { - test :: #import "jc/meta/test"; - - test.run("encodes", (t) => { +#if RunTests #run { + Test("encodes", t => { str :: "Hello, World"; - encoded, ok := base64_encode(str); - test.expect(t, ok, "'%' did not properly encode!", str); + encoded, ok := Base64Encode(str); + Expect(ok, "'%' did not properly encode!", str); 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"; - decoded, ok := base64_decode(encoded_str); - test.expect(t, ok, "'%' did not properly decode!", encoded_str); + decoded, ok := Base64Decode(encoded_str); + Expect(ok, "'%' did not properly decode!", encoded_str); 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=="; - first_decode, f_ok := base64_decode(str); - test.expect(t, f_ok, "first decode failed!"); + first_decode, f_ok := Base64Decode(str); + Expect(f_ok, "first decode failed!"); - re_encode, r_ok := base64_encode(first_decode); - test.expect(t, r_ok, "re-encode failed!"); + re_encode, r_ok := Base64Encode(first_decode); + Expect(r_ok, "re-encode failed!"); - second_decode, s_ok := base64_decode(re_encode); - test.expect(t, s_ok, "second decode failed!"); + second_decode, s_ok := Base64Decode(re_encode); + Expect(s_ok, "second decode failed!"); 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 { first: u64; second: u64; @@ -220,14 +221,14 @@ strings :: #import "String"; // @future enc_builder: basic.String_Builder; 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)); - test.expect(t, e_ok, "Encode of structure failed!"); + encoded, e_ok := Base64Encode(basic.builder_to_string(*enc_builder)); + Expect(e_ok, "Encode of structure failed!"); - decoded, d_ok := base64_decode(encoded); - test.expect(t, d_ok, "Decode of encoded structure failed!"); + decoded, d_ok := Base64Decode(encoded); + Expect(d_ok, "Decode of encoded structure failed!"); 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 = ---; @@ -237,17 +238,17 @@ strings :: #import "String"; // @future for info.members { 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.*= value; } } - test.expect(t, 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); - test.expect(t, 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.first == bar.first, "Foo.first (%, %) didn't match between encoding/decoding!", foo.first, bar.first); + Expect(foo.second == bar.second, "Foo.second (%, %) didn't match between encoding/decoding!", foo.second, bar.second); + Expect(foo.third == bar.third, "Foo.third (%, %) didn't match between encoding/decoding!", foo.third, bar.third); + Expect(foo.forth == bar.forth, "Foo.forth (%, %) didn't match between encoding/decoding!", foo.forth, bar.forth); }); } diff --git a/hash/module.jai b/hash/module.jai deleted file mode 100644 index c29efe3..0000000 --- a/hash/module.jai +++ /dev/null @@ -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); -} - diff --git a/hash/murmur.jai b/hash/murmur.jai deleted file mode 100644 index 5fa7fcc..0000000 --- a/hash/murmur.jai +++ /dev/null @@ -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 = <> 16; h *= 0x85ebca6b; - h ^= h >> 13; h *= 0xc2b2ae35; - h ^= h >> 16; - return h; -} diff --git a/hash/table.jai b/hash/table.jai deleted file mode 100644 index daa9b8b..0000000 --- a/hash/table.jai +++ /dev/null @@ -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); - }); -} diff --git a/math/common.jai b/math/common.jai index 33e938d..ab6a11f 100644 --- a/math/common.jai +++ b/math/common.jai @@ -88,12 +88,12 @@ min :: (x: float, y: float) -> float { } 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; } #scope_file +#import "jc"; // sin, cos -meta :: #import "jc/meta"; math :: #import "Math"; diff --git a/math/ease.jai b/math/ease.jai index 2b56f1e..c902888 100644 --- a/math/ease.jai +++ b/math/ease.jai @@ -28,7 +28,7 @@ Transition :: enum { @Note: Progress must be between 0.0 and 1.0 */ 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; @@ -85,8 +85,6 @@ ease :: (progress: $T, $$ease: Ease = .linear, $$transition: Transition = .in) - #scope_file; - -meta :: #import "jc/meta"; - +#import "jc"; math :: #import "Math"; // @future basic :: #import "Basic"; // @future diff --git a/math/mat.jai b/math/mat.jai index a273055..909beb8 100644 --- a/math/mat.jai +++ b/math/mat.jai @@ -404,45 +404,43 @@ inverse :: (m: Mat4) -> Mat4 { #scope_file; -#if #exists(RUN_TESTS) #run { - test :: #import "jc/meta/test"; - - test.run(basic.tprint("%: Mat2", UNITS), t => { +#if RUN_TESTS #run { + Test(basic.tprint("%: Mat2", UNITS), t => { { identity := m2_identity; a: Mat2 = Mat2.{.[1, 2, 3, 4]}; - test.expect(t, a*identity == a); + Expect(a*identity == a); } { rotator := rotation_mat2(from_rad(PI)); v2 := v2f(1.0, 0.5); 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; - 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); det := determinate(m); - test.expect(t, det == -4); + Expect(det == -4); } { rotator := rotation_mat2(from_rad(PI)); inv_rotator := inverse(rotator); v2 := v2f(0.1, 1.0); - test.expect(t, inv_rotator*rotator == m2_identity); - test.expect(t, inv_rotator*(rotator*v2) == v2); + Expect(inv_rotator*rotator == m2_identity); + Expect(inv_rotator*(rotator*v2) == v2); v2 = rotator*v2; v2 = inv_rotator*v2; } }); - test.run(basic.tprint("%: Mat4", UNITS), t => { + Test(basic.tprint("%: Mat4", UNITS), t => { { identity := m4_identity; 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]}; @@ -454,16 +452,16 @@ inverse :: (m: Mat4) -> Mat4 { translator := translate(v3f(10.0, 5.0, 2.0)); v3 := v3f(1.0, 2.0, 1.0); v4 := v4f(v3, 1.0); - test.expect(t, 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(v4 == v4f(1.0, 2.0, 1.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)); v4 := v4f(1.0, 0.5, 0.1, 1.0); 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; - 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)); @@ -476,7 +474,7 @@ inverse :: (m: Mat4) -> Mat4 { inv_rotator := inverse(rotator); 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 = inv_rotator*v4; diff --git a/math/module.jai b/math/module.jai index d0b97e6..dd34aed 100644 --- a/math/module.jai +++ b/math/module.jai @@ -5,22 +5,7 @@ RUN_TESTS := false ); -#assert meta.type_is_scalar(RECT_TYPE); - -// @todo(judah): dumb we can't use meta.range_for here. - -U8_Min, U8_Max :: #run meta.lo_for(u8), #run meta.hi_for(u8); -U16_Min, U16_Max :: #run meta.lo_for(u16), #run meta.hi_for(u16); -U32_Min, U32_Max :: #run meta.lo_for(u32), #run meta.hi_for(u32); -U64_Min, U64_Max :: #run meta.lo_for(u64), #run meta.hi_for(u64); - -S8_Min, S8_Max :: #run meta.lo_for(s8), #run meta.hi_for(s8); -S16_Min, S16_Max :: #run meta.lo_for(s16), #run meta.hi_for(s16); -S32_Min, S32_Max :: #run meta.lo_for(s32), #run meta.hi_for(s32); -S64_Min, S64_Max :: #run meta.lo_for(s64), #run meta.hi_for(s64); - -F32_Min, F32_Max :: #run meta.lo_for(float32), #run meta.hi_for(float32); -F64_Min, F64_Max :: #run meta.lo_for(float64), #run meta.hi_for(float64); +#assert type_is_scalar(RECT_TYPE); #load "common.jai"; #load "vec.jai"; @@ -30,7 +15,8 @@ F64_Min, F64_Max :: #run meta.lo_for(float64), #run meta.hi_for(float64); #scope_module; -meta :: #import "jc/meta"; +#import "jc"; + math :: #import "Math"; // @future basic :: #import "Basic"; // @future diff --git a/math/vec.jai b/math/vec.jai index a8f36c0..5f55b59 100644 --- a/math/vec.jai +++ b/math/vec.jai @@ -103,17 +103,17 @@ Vec :: struct(N: int, T: Type) } operator [] :: (v: Vec, $$idx: int) -> v.T #no_abc { - meta.check_bounds(idx, v.N); + CheckBounds(idx, v.N); return v.components[idx]; } operator *[] :: (v: *Vec, $$idx: int) -> *v.T #no_abc { - meta.check_bounds(idx, v.N); + CheckBounds(idx, v.N); return *v.components[idx]; } operator []= :: (v: *Vec, $$idx: int, value: v.T) #no_abc { - meta.check_bounds(idx, v.N); + CheckBounds(idx, v.N); 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 -#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) = ---; #if l.N <= 4 { 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 -#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) = ---; #if l.N <= 4 { res.x = l.x - r; @@ -216,7 +216,7 @@ operator - :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc return res; } 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) = ---; #if l.N <= 4 { 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 -#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) = ---; #if l.N <= 4 { 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 -#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) = ---; #if l.N <= 4 { 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 -#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) = ---; n := N - 1; 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 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 { return .{ x = x, y = y }; } 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 { return .{ x = x, y = y }; } 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 { return .{ x = x, y = y, z = z }; } 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 { return .{ x = x, y = y, z = z }; } 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 { 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) -#modify { return meta.type_is_integer(T), "use v4f for float arguments"; } +#modify { return type_is_integer(T), "use v4f for float arguments"; } #expand { return .{ x = x, y = y, z = z, w = w }; } @@ -654,76 +654,74 @@ cross :: (a: Vec3, b: Vec3) -> Vec3 { #scope_file; #if RUN_TESTS #run,stallable { - test :: #import "jc/meta/test"; - - test.run(basic.tprint("%: Vec2", UNITS), t => { + Test(basic.tprint("%: Vec2", UNITS), t => { { a: Vec2 = v2f(0.0, 1.0); b: Vec2 = v2f(1.0, 2.0); - test.expect(t, a + b == v2f(1.0, 3.0)); - test.expect(t, b - a == v2f(1.0, 1.0)); - test.expect(t, a*b == v2f(0.0, 2.0)); - test.expect(t, a/b == v2f(0.0, 0.5)); + Expect(a + b == v2f(1.0, 3.0)); + Expect(b - a == v2f(1.0, 1.0)); + Expect(a*b == v2f(0.0, 2.0)); + Expect(a/b == v2f(0.0, 0.5)); } { a: Vec(2, int) = v2i(2, 1); b: Vec(2, int) = v2i(1, 0); - test.expect(t, a + b == v2i(3, 1)); - test.expect(t, b - a == v2i(-1, -1)); - test.expect(t, a*b == v2i(2, 0)); - test.expect(t, b/a == v2i(0, 0)); + Expect(a + b == v2i(3, 1)); + Expect(b - a == v2i(-1, -1)); + Expect(a*b == v2i(2, 0)); + Expect(b/a == v2i(0, 0)); } { a: Vec2 = v2f(2.3, -4.1); b: Vec2 = v2f(1.0, 3.6); c := min(a, b); - test.expect(t, c == v2f(1.0, -4.1)); + Expect(c == v2f(1.0, -4.1)); 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); b: Vec3 = v3f(1.0, 2.0, 3.0); - test.expect(t, a + b == v3f(1.0, 3.0, 5.0)); - test.expect(t, b - a == v3f(1.0, 1.0, 1.0)); - test.expect(t, 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(1.0, 3.0, 5.0)); + Expect(b - a == v3f(1.0, 1.0, 1.0)); + Expect(a*b == v3f(0.0, 2.0, 6.0)); + Expect(a/b == v3f(0.0, 0.5, 0.66666667)); a = v3f(1.0, 1.0, 0.0); b = v3f(1.0, 0.0, 0.0); - test.expect(t, 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)); - test.expect(t, round(v3f(-1.2, -1.7, -1.5)) == v3f(-1.0, -2.0, -2.0)); + Expect(reflect(a, b) == v3f(1.0, -1.0, 0.0)); + Expect(round(v3f(1.2, 1.7, 1.5)) == v3f(1.0, 2.0, 2.0)); + Expect(round(v3f(-1.2, -1.7, -1.5)) == v3f(-1.0, -2.0, -2.0)); a = v3f(1.0, 0.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); b: Vec3 = v3f(1.0, -3.6, 5.0); 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); - 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); 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)); - test.expect(t, 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)); - test.expect(t, a/b == v4f(0.5625, 0.5, 2.0/3.0, 1.0)); + Expect(a + b == v4f(6.25, 3.0, 5.0, 2.0)); + Expect(b - a == v4f(1.75, 1.0, 1.0, 0.0)); + Expect(a*b == v4f(9.0, 2.0, 6.0, 1.0)); + Expect(a/b == v4f(0.5625, 0.5, 2.0/3.0, 1.0)); }); - test.run(basic.tprint("%: VecN", UNITS), t => { + Test(basic.tprint("%: VecN", UNITS), t => { a: Vec(16, float); b: Vec(16, float); for *a { @@ -732,59 +730,59 @@ cross :: (a: Vec3, b: Vec3) -> Vec3 { for *b { 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]}); - 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]}); - 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).{.[1.0, 3.0, 5.0, 7.0, 9.0, 11.0, 13.0, 15.0, 17.0, 19.0, 21.0, 23.0, 25.0, 27.0, 29.0, 31.0]}); + Expect(b - a == Vec(16, float).{.[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]}); + 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); - test.expect(t, 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]}); - 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]}); - 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(min(a, b) == a); + Expect(max(a, b) == b); + Expect(max(a, 12) == Vec(16, float).{.[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 12.0, 12.0, 12.0]}); + 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]}); + 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]}; - 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]}); - 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(ceil(a1) == Vec(16, float).{.[2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 5.0, -1.0, -1.0, -1.0, 12.0, 14.0, 15.0, 14.0, 15.0, 65537]}); + Expect(floor(a1) == Vec(16, float).{.[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 4.0, -2.0, -1.0, -2.0, 11.0, 14.0, 15.0, 14.0, 15.0, 65536]}); - test.expect(t, dot(a, b) == 1360.0); - test.expect(t, abs(a) == a); + Expect(dot(a, b) == 1360.0); + Expect(abs(a) == a); c := a; for *c { // Check making every other component negative if it_index%2 == 0 then it.* = -it.*; } - test.expect(t, abs(c) == a); - test.expect(t, 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]}); - test.expect(t, 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(abs(c) == a); + Expect(float_eq(length(normalize(a)), 1)); + Expect(a + 2 == Vec(16, float).{.[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]}); + Expect(a - 2 == Vec(16, float).{.[-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]}); + Expect(a*2 == Vec(16, float).{.[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]}); }); - test.run(basic.tprint("%: Quat", UNITS), t => { + Test(basic.tprint("%: Quat", UNITS), t => { qi := quat_identity; 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)); qc := conjugate(q); inv_q := inverse(q); - test.expect(t, q*inv_q == qi); + Expect(q*inv_q == qi); q1 := quat(2, 0, 0, 0); q2 = quat(1, 1, -1, 0); 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)); p := v3f(2.0, 0.0, 0.0); c1 := q*quat(p, 0)*conjugate(q); c2 := q*p; - test.expect(t, 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(c2, v3f(math.sqrt(2.0), math.sqrt(2.0), 0.0))); + Expect(v3_eq(c1.xyz, c2)); q = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0)); m := rotation_mat4(q); 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)); q2 = rotation_quat(from_rad(2.0*PI), v3f(0.0, 1.0, 0.0)); diff --git a/memory/allocators.jai b/memory/allocators.jai deleted file mode 100644 index e25ba3d..0000000 --- a/memory/allocators.jai +++ /dev/null @@ -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); - }); -} diff --git a/memory/buffer.jai b/memory/buffer.jai deleted file mode 100644 index 837f3f2..0000000 --- a/memory/buffer.jai +++ /dev/null @@ -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"; -} diff --git a/memory/module.jai b/memory/module.jai deleted file mode 100644 index 41baffc..0000000 --- a/memory/module.jai +++ /dev/null @@ -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); - }); -} diff --git a/meta/macros.jai b/meta/macros.jai deleted file mode 100644 index 3905abd..0000000 --- a/meta/macros.jai +++ /dev/null @@ -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); - }); -} - diff --git a/meta/module.jai b/meta/module.jai deleted file mode 100644 index 9b6434f..0000000 --- a/meta/module.jai +++ /dev/null @@ -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"); - }); -} diff --git a/meta/test/module.jai b/meta/test/module.jai deleted file mode 100644 index 309ddfe..0000000 --- a/meta/test/module.jai +++ /dev/null @@ -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 diff --git a/meta/type_info.jai b/meta/type_info.jai deleted file mode 100644 index 7d20adf..0000000 --- a/meta/type_info.jai +++ /dev/null @@ -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); - }); -} diff --git a/module.jai b/module.jai index 6f36c10..5d3817b 100644 --- a/module.jai +++ b/module.jai @@ -1,15 +1,21 @@ -/* - Module jc contains procedures for working with memory, - arrays, and strings; as well as helpful macros and - constants. +/// 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); - Additionally, it provides a platform-independant - interface for interacting with the target operating - system. -*/ +// @note(judah): this causes some very weird issues in tests so it's ifdef'd out +#if !RunTests #add_context temp_allocator: Allocator; -#load "internal/builtin.jai"; -#load "internal/array.jai"; -#load "internal/memory.jai"; -#load "internal/testing.jai"; -#load "internal/keywords.jai"; +#load "+internal/builtin.jai"; +#load "+internal/array.jai"; +#load "+internal/kv.jai"; +#load "+internal/hashing.jai"; +#load "+internal/memory.jai"; +#load "+internal/allocators.jai"; +#load "+internal/testing.jai"; +#load "+internal/keywords.jai"; +#load "+internal/type_info.jai"; diff --git a/platform/arch.jai b/platform/arch.jai deleted file mode 100644 index d47c056..0000000 --- a/platform/arch.jai +++ /dev/null @@ -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 diff --git a/platform/module.jai b/platform/module.jai deleted file mode 100644 index eb6c097..0000000 --- a/platform/module.jai +++ /dev/null @@ -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; diff --git a/encoding/json.jai b/x/json.jai similarity index 99% rename from encoding/json.jai rename to x/json.jai index f7d8cb0..e519d76 100644 --- a/encoding/json.jai +++ b/x/json.jai @@ -1,4 +1,4 @@ -#module_parameters(RUN_TESTS := false); +#module_parameters(RunTests := false); Json_Value :: struct { 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 { name := member.name; @@ -609,4 +609,3 @@ at_end :: inline (p: *Parser) -> bool { basic :: #import "Basic"; // @future strings :: #import "String"; // @future - diff --git a/meta/reload/examples/everything-you-need.jai b/x/reload/examples/everything-you-need.jai similarity index 100% rename from meta/reload/examples/everything-you-need.jai rename to x/reload/examples/everything-you-need.jai diff --git a/meta/reload/examples/quickstart.jai b/x/reload/examples/quickstart.jai similarity index 100% rename from meta/reload/examples/quickstart.jai rename to x/reload/examples/quickstart.jai diff --git a/meta/reload/module.jai b/x/reload/module.jai similarity index 100% rename from meta/reload/module.jai rename to x/reload/module.jai diff --git a/meta/reload/reload_main.jai b/x/reload/reload_main.jai similarity index 100% rename from meta/reload/reload_main.jai rename to x/reload/reload_main.jai