+ - Converted procedure argument names from PascalCase to snake_case + - Converted struct field names from PascalCase to snake_case ++*/ #module_parameters( + /// Statically link to HandmadeMath. STATIC := true, - SIMD := true, - UNITS := (enum { radians; degrees; turns; }).radians + /// Enable SIMD support. + SIMD := true, + /// Angle units to use. + UNITS : enum { radians; degrees; turns; } = .radians ); #scope_export; diff --git a/ext/luajit/module.jai b/ext/luajit/module.jai index 6dd9b9e..cd7d641 100644 --- a/ext/luajit/module.jai +++ b/ext/luajit/module.jai @@ -1,3 +1,7 @@ +/* + Module luajit provides bindings for the LuaJIT C + library (2.1.1744318430) +*/ #module_parameters(STATIC := true); #if STATIC { diff --git a/ext/objc/module.jai b/ext/objc/module.jai new file mode 100644 index 0000000..5788182 --- /dev/null +++ b/ext/objc/module.jai @@ -0,0 +1,47 @@ +#scope_export; + +UInt :: u64; + +Id :: *object; +Class :: *class; +Sel :: *selector; + +Bool :: u8; + +True : Bool : 1; +False : Bool : 0; + +msg_send :: () #foreign objc "objc_msgSend"; +msg_send_super :: () #foreign objc "objc_msgSend_super"; +msg_send_fpret :: () #foreign objc "objc_msgSend_fpret"; +msg_send_stret :: () #foreign objc "objc_msgSend_stret"; + +get_class :: (name: *u8) -> Class #foreign objc "objc_getClass"; + +sel_get_name :: (sel: Sel) -> *u8 #foreign objc "sel_getName"; +sel_register_name :: (str: *u8) -> Sel #foreign objc "sel_registerName"; +sel_get_uid :: (str: *u8) -> Sel #foreign objc "sel_getUid"; + +obj_get_class :: (obj: Id) -> Class #foreign objc "object_getClass"; +obj_set_class :: (obj: Id, cls: Class) -> Class #foreign objc "object_setClass"; +obj_is_class :: (obj: Id) -> Bool #foreign objc "object_isClass"; +obj_get_class_name :: (obj: Id) -> *u8 #foreign objc "object_getClassName"; +obj_copy :: (obj: Id, size: u64) -> Id #foreign objc "object_copy"; +obj_dispose :: (obj: Id) -> Id #foreign objc "object_dispose"; + +class_get_name :: (cls: Class) -> *u8 #foreign objc "class_getName"; +class_get_super :: (cls: Class) -> Class #foreign objc "class_getSuperclass"; + +#scope_module; + +class :: struct {}; +object :: struct {}; +method :: struct {}; +ivar :: struct {}; +category :: struct {}; +protocol :: struct {}; +selector :: struct {}; + +objc :: #library,system,link_always,no_dll "libobjc"; + +#import "jc"; diff --git a/ext/raylib/module.jai b/ext/raylib/module.jai index b080be5..503dc97 100644 --- a/ext/raylib/module.jai +++ b/ext/raylib/module.jai @@ -1,3 +1,10 @@ +/* + Module raylib provides bindings for the raylib C + library (v5.5). + + Supported platforms: Windows, Mac, Linux +*/ + #module_parameters(STATIC := true); #scope_export diff --git a/ext/remotery/module.jai b/ext/remotery/module.jai index c956ee4..23ec8f2 100644 --- a/ext/remotery/module.jai +++ b/ext/remotery/module.jai @@ -1,3 +1,7 @@ +/* + Module remotery provides bindings for the Remotery + CPU/GPU profiling library. +*/ #module_parameters(STATIC := true); #if STATIC { diff --git a/internal/array.jai b/internal/array.jai new file mode 100644 index 0000000..7039bc4 --- /dev/null +++ b/internal/array.jai @@ -0,0 +1,205 @@ +/// 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"); + + if count == -1 + { count = view.count - start_idx; } + + 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 :: (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 :: (view: *[]$T) { + MemZero(view.data, view.count * size_of(T)); + view.count = 0; +} + +/// Equal checks the equality of two arrays. +Equal :: (lhs: []$T, rhs: []T) -> bool { + if lhs.count != rhs.count + { return false; } + return MemEqual(lhs.data, rhs.data, lhs.count * size_of(T)); +} + + +FindFlags :: enum_flags { + Last; // The last matching element should be returned. + FromEnd; // The search be done in reverse. +} + +FindResult :: struct(T: Type) { + value: T = ---; + index: int; +} + +/// Find searches through an array, returning the first element +/// that matches the given value. +Find :: (view: []$T, value: T, $flags: FindFlags = 0) -> (bool, FindResult(T)) { + found: bool; + + result: FindResult(T); + result.index = -1; + + REVERSE :: #run (flags & .FromEnd).(bool); + for #v2 <=REVERSE view if it == value { + found = true; + result.index = it_index; + result.value = it; + #if !(flags & .Last) break; + } + + return found, result; +} + +/// Contains checks if the given value exists in an array. +Contains :: (view: []$T, value: T) -> bool { + return Find(view, value); +} + + +TrimFlags :: enum_flags { + FromStart; // The start of the array should be trimmed. + FromEnd; // The end of the array should be trimmed. + MatchInFull; // Only trim when the cutset matches exactly. +} + +/// Trim returns a subsection of an array with all leading/trailing values +/// from cutset removed. +Trim :: (view: []$T, cutset: []T, $flags: TrimFlags = .FromStart) -> []T { + result := view; + if cutset.count == 0 || cutset.count > view.count { + return result; + } + + #if flags & .FromStart { + #if flags & .MatchInFull { + if Equal(Slice(view, 0, cutset.count), cutset) { + result = Slice(view, cutset.count, -1); + } + } + else { + while result.count > 0 { + if !Contains(cutset, result[0]) { + break; + } + + result.data += 1; + result.count -= 1; + } + } + } + + #if flags & .FromEnd { + #if flags & .MatchInFull { + if Equal(Slice(view, view.count - cutset.count), cutset) { + result.count -= cutset.count; + } + } + else { + while result.count > 0 { + if !Contains(cutset, result[result.count - 1]) { + break; + } + + result.count -= 1; + } + } + } + + return result; +} + + +#scope_file + +#if #exists(RunTests) #run,stallable { + Test("slice", t => { + a1 := int.[ 1, 2, 3, 4, 5 ]; + a2 := Slice(a1, 2); + Expect(a2.count == 3); + Expect(Equal(a2, int.[ 3, 4, 5 ])); + + b1 := int.[ 1, 2, 3, 4, 5 ]; + b2 := Slice(b1, 2, 0); + Expect(b2.count == 0); + Expect(b2.data == b1.data + 2); + + c1 := int.[ 1, 2, 3, 4, 5 ]; + c2 := Slice(c1, 3, 1); + Expect(c2.count == 1); + Expect(Equal(c2, int.[ 4 ])); + + d1 := int.[ 1, 2, 3 ]; + d2 := Slice(d1, 2); + Expect(d2.count == 1); + Expect(Equal(d2, int.[ 3 ])); + }); + + Test("find", t => { + a := int.[ 1, 2, 3, 4, 5 ]; + + ok, res := Find(a, 3); + Expect(ok && res.index == 2); + Expect(res.value == 3); + + ok, res = Find(a, -1); + Expect(!ok && res.index == -1); + + b := int.[ 1, 2, 2, 3, 4, 5, 2 ]; + + ok, res = Find(b, 2); + Expect(ok && res.index == 1); + + ok, res = Find(b, 2, .FromEnd); + Expect(ok && res.index == 6); + + c := int.[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ]; + + ok, res = Find(c, 0, .Last); + Expect(ok && res.index == 8); + + ok, res = Find(c, 0, .FromEnd | .Last); + Expect(ok && res.index == 0); + }); + + Test("contains", t => { + a := int.[ 1, 2, 3, 4, 5 ]; + Expect(Contains(a, 3)); + Expect(!Contains(a, -1)); + }); + + Test("trim", t => { + a1 := int.[ 0, 0, 0, 1, 2, 3 ]; + a2 := Trim(a1, .[ 0 ]); + Expect(Equal(a1, .[ 0, 0, 0, 1, 2, 3 ])); + Expect(Equal(a2, .[ 1, 2, 3 ])); + + b1 := int.[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ]; + b2 := Trim(b1, .[ 0 ], .FromEnd); + Expect(Equal(b1, .[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ])); + Expect(Equal(b2, .[ 0, 0, 0, 1, 2, 3 ])); + + c1 := int.[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ]; + c2 := Trim(c1, .[ 0 ], .FromStart | .FromEnd); + Expect(Equal(c1, .[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ])); + Expect(Equal(c2, .[ 1, 2, 3 ])); + + d1 := int.[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ]; + d2 := Trim(d1, .[ 0, 0, 0 ], .FromStart | .MatchInFull); + d3 := Trim(d1, .[ 0, 0, 0 ], .FromEnd | .MatchInFull); + d4 := Trim(d1, .[ 0, 0, 0 ], .FromStart | .FromEnd | .MatchInFull); + Expect(Equal(d1, .[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ])); + Expect(Equal(d2, .[ 1, 2, 3, 0, 0, 0 ])); + Expect(Equal(d3, .[ 0, 0, 0, 1, 2, 3 ])); + Expect(Equal(d4, .[ 1, 2, 3 ])); + }); +} diff --git a/internal/builtin.jai b/internal/builtin.jai new file mode 100644 index 0000000..3ceb238 --- /dev/null +++ b/internal/builtin.jai @@ -0,0 +1,180 @@ +/// JcMajor is the current major version of this library. +/// +/// Major versions are guaranteed to have stable apis +/// for their duration. +JcMajor :: 0; + +/// JcMinor is the current minor version of this library. +/// +/// Minor versions denote bug fixes, additions, or improvements +/// that do not affect api stability. +JcMinor :: 1; + +// @note(judah): we can't use range_of here because a compiler bug? + +S8Min :: #run min_of(s8); /// -128 (-0x80) +S8Max :: #run max_of(s8); /// 127 (0x7f) +S16Min :: #run min_of(s16); /// -32768 (-0x8000) +S16Max :: #run max_of(s16); /// 32767 (0x7f_ff) +S32Min :: #run min_of(s32); /// -2147483648 (-0x8000_0000) +S32Max :: #run max_of(s32); /// 2147483647 (0x7fff_ffff) +S64Min :: #run min_of(s64); /// -9223372036854775808 (-0x80000000_00000000) +S64Max :: #run max_of(s64); /// 9223372036854775807 (0x7fffffff_ffffffff) + +U8Min :: #run min_of(u8); /// 0 (0x00) +U8Max :: #run max_of(u8); /// 255 (0xff) +U16Min :: #run min_of(u16); /// 0 (0x00_00) +U16Max :: #run max_of(u16); /// 65535 (0xff_ff) +U32Min :: #run min_of(u32); /// 0 (0x0000_0000) +U32Max :: #run max_of(u32); /// 4294967295 (0xffff_ffff) +U64Min :: #run min_of(u64); /// 0 (0x00000000_00000000) +U64Max :: #run max_of(u64); /// 18446744073709551615 (0xffffffff_ffffffff) + +Float32Min :: #run min_of(float32); /// 1.17549e-38 (0h0080_0000) +Float32Max :: #run max_of(float32); /// 3.40282e+38 (0h7f7fffff) +Float64Min :: #run min_of(float64); /// 2.22507e-308 (0h00100000_00000000) +Float64Max :: #run max_of(float64); /// 1.79769e+308 (0h7fefffff_ffffffff) + +/// m0 is a 0-size marker type. +/// +/// It allows specific offsets within a type to be marked which is useful for (de)serialization. +/// +/// MyType :: struct { +/// do_not_serialize_1: *void; +/// +/// _start: m0; // Has the same offset as serialize_1 +/// serialize_1: [32]u8; +/// serialize_2: u64; +/// serialize_3: bool; +/// serialize_4: float32; +/// _end: m0; // Has the same offset as serialize_4 +/// +/// do_not_serialize_2: [..]int; +/// } +/// +/// value := MyType.{}; +/// start := *value + offset_of(value, #code _start); +/// end := *value + offset_of(value, #code _end); +/// WriteToDisk(data = start, count = end - start); +/// +m0 :: #type void; + +b8 :: enum u8 { false_ :: (0 != 0).(u8); true_ :: (0 == 0).(u8); }; /// b8 is an 8-bit boolean. +b16 :: enum u16 { false_ :: (0 != 0).(u16); true_ :: (0 == 0).(u16); }; /// b16 is a 16-bit boolean. +b32 :: enum u32 { false_ :: (0 != 0).(u32); true_ :: (0 == 0).(u32); }; /// b32 is a 32-bit boolean. +b64 :: enum u64 { false_ :: (0 != 0).(u64); true_ :: (0 == 0).(u64); }; /// b64 is a 64-bit boolean. + +/// Panic displays the given message and crashes the program. +/// +/// Note: Defers will not run when Panic is called. +Panic :: (message := "runtime panic", loc := #caller_location) #expand #no_debug { + #if DebugBuild { + WriteStderrLocation(loc); + WriteStderrString(": "); + } + + WriteStderrString(message, "\n"); + Trap(); +} + +/// Unreachable displays the given message and causes an execution trap. +/// +/// Note: Defers will not run when Unreachable is called. +Unreachable :: (message := "unreachable code hit", loc := #caller_location) #expand #no_debug { + trap :: #ifx DebugBuild then DebugTrap else Trap; + + #if DebugBuild { + WriteStderrLocation(loc); + WriteStderrString(": "); + } + + WriteStderrString(message, "\n"); + trap(); +} + +/// CompileError displays the given message and stops compilation. +/// +/// Note: By default, the error is reported at the callsite. +CompileError :: (message: string, loc := #caller_location) #expand #no_debug #compile_time { + if #compile_time { + compiler_report(message, loc, .ERROR); + } + else { + Panic("CompileError can only be called at compile-time", loc = loc); + } +} + +// @todo(judah): these should be different! + +/// Trap causes an execution trap. +Trap :: () #expand #no_debug { + debug_break(); // Provided by Runtime_Support +} + +/// DebugTrap causes an execution trap that grabs the attention of a debugger. +DebugTrap :: () #expand #no_debug { + debug_break(); // Provided by Runtime_Support +} + +#if DebugBuild +{ + /// Assert causes a debug break if the given condition is false. + Assert :: (cond: bool, message := "condition was false", loc := #caller_location) #expand #no_debug { + // @note(judah): We only need to do this to route into the context's builtin assertion handling. + if cond || context.handling_assertion_failure return; + + context.handling_assertion_failure = true; + should_trap := context.assertion_failed(loc, message); + context.handling_assertion_failure = false; + + if should_trap { + DebugTrap(); + } + } + + /// AssertCallsite works identically to Assert, except that it expects a + /// Source_Code_Location (called 'loc') to exist in the calling scope. + /// + /// MyProc :: (loc := #caller_location) { + /// AssertCallsite(false); // 'loc' is passed implicitly + /// Assert(false, loc = loc); // equivalent + /// } + /// + AssertCallsite :: (cond: bool, message := "condition was false") #expand #no_debug { + Assert(cond, message, loc = `loc); + } +} +else +{ + // @note(judah): these need to be separate declarations so we can use #discard. + // otherwise, the compiler will generate instructions to setup the call when assertions are disabled. + Assert :: (#discard cond: bool, #discard message := "", #discard loc := #caller_location) #expand #no_debug {} + AssertCallsite :: (#discard cond: bool, #discard message := "") #expand #no_debug {} +} + + +#scope_module + +WriteString :: write_strings; @jc.nodocs // Provided by Runtime_Support +WriteNumber :: write_number; @jc.nodocs // Provided by Runtime_Support + +// @note(judah): This is a direct copy of Runtime_Support's write_loc since it's not exported +WriteStderrLocation :: (loc: Source_Code_Location) { + WriteStderrString(loc.fully_pathed_filename, ":"); + WriteStderrNumber(loc.line_number); + WriteStderrString(","); + WriteStderrNumber(loc.character_number); +} @jc.nodocs + +#scope_file + +DebugBuild :: #run -> bool { + // @note(judah): there's not really a good way to detect opt level/build type, + // so just check if debug info is being emitted. + #import "Compiler"; + opts := get_build_options(); + return opts.emit_debug_info != .NONE; +}; + +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/internal/keywords.jai b/internal/keywords.jai new file mode 100644 index 0000000..2fb7110 --- /dev/null +++ b/internal/keywords.jai @@ -0,0 +1,233 @@ +/// offset_of returns the byte offset of a field within the type T. +/// +/// Note: T must be a struct type. +/// +/// MyType :: struct { x: int; y: int; z: int; }; +/// offset_of(MyType, #code y); // 8 +/// +offset_of :: ($T: Type, ident: Code, loc := #caller_location) -> int #expand { + #run (loc: Source_Code_Location) { + info := type_info(T); + if info.type != .STRUCT { + CompileError("offset_of can only be used on struct types", loc = loc); + } + }(loc); + + return #run -> int { + t: T = ---; + return (*t.#insert ident).(*void) - (*t).(*void); + }; +} + +/// offset_of returns the byte offset of a field within the type of value. +/// +/// Note: If offset_of is given a pointer value, it will use the type pointed to. +/// +/// value := struct{ x: int; y: int; z: int; }.{}; +/// offset_of(value, #code y); // 8 +/// +offset_of :: (#discard value: $T, ident: Code, loc := #caller_location) -> int #expand { + type :: #run -> Type { + info := T.(*Type_Info); + if info.type == .POINTER { + info = info.(*Type_Info_Pointer).pointer_to; + // @question(judah): do we want it to traverse all the way up to a non-pointer type? + // I opted against because if you have a *T, you only want offset_of to get an offset + // from that pointer. What would you do with a field offset from **T? + if info.type == .POINTER { + CompileError("offset_of only allows one level of pointer indirection.", loc = loc); + } + } + + return get_type(info); + }; + + return offset_of(type, ident, loc = loc); +} + +/// align_of returns the alignment of the given type. +align_of :: ($T: Type) -> int #expand { + return #run -> int { + if size_of(T) == 0 + { return 0; } + + info := type_info(struct{ p: u8; t: T; }); + return info.members[1].offset_in_bytes.(int); + }; +} + +/// default_of returns a value of type T as if it was just instantiated. +/// +/// Note: default_of will call the initializer for aggregate types, so you +/// may want zero_of instead. +default_of :: ($T: Type) -> T #expand { + default: T; + return default; +} + +/// undefined_of returns a value of type T that has not been initialized. +undefined_of :: ($T: Type) -> T #expand { + uninit: T = ---; + return uninit; +} + +/// zero_of returns a value of type T that has been zero-initialized. +/// +/// Note: zero_of will not call the initializer for aggregate types, so you +/// may want default_of instead. +zero_of :: ($T: Type) -> T #expand { + zero := undefined_of(T); + MemZero(*zero); + return zero; +} + +/// min_of returns the minimum value T can represent. +/// +/// Note: T must be an integer, float, or enum type. +min_of :: ($T: Type, loc := #caller_location) -> T #expand { + return #run -> T { + info := T.(*Type_Info); + if info.type == { + case .INTEGER; + i := info.(*Type_Info_Integer); + if i.runtime_size == { + case 1; return (ifx i.signed then -0x80 else 0).(T, no_check); + case 2; return (ifx i.signed then -0x8000 else 0).(T, no_check); + case 4; return (ifx i.signed then -0x8000_0000 else 0).(T, no_check); + case 8; return (ifx i.signed then -0x8000_0000_0000_0000 else 0).(T, no_check); + case ; CompileError("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 .ENUM; + i := info.(*Type_Info_Enum); + if i.values.count == 0 { + return 0; + } + + min: T = i.values[0].(T, no_check); + if i.internal_type.signed { + for i.values if it.(T) < min { + min = it.(T); + } + } + else { + for i.values if it.(T) < min { + min = it.(T); + } + } + + return min; + + case; + CompileError("min_of requires an enum, integer, or float type", loc = loc); + } + + return 0; + }; +} + +/// max_of returns the maximum value T can represent. +/// +/// Note: T must be an integer, float, or enum type. +max_of :: ($T: Type, loc := #caller_location) -> T #expand { + return #run -> T { + info := T.(*Type_Info); + if info.type == { + case .INTEGER; + i := info.(*Type_Info_Integer); + if i.runtime_size == { + case 1; return (ifx i.signed then 0x7f else 0xff).(T, no_check); + case 2; return (ifx i.signed then 0x7fff else 0xffff).(T, no_check); + case 4; return (ifx i.signed then 0x7fff_ffff else 0xffff_ffff).(T, no_check); + case 8; return (ifx i.signed then 0x7fff_ffff_ffff_ffff else 0xffff_ffff_ffff_ffff).(T, no_check); + case ; CompileError("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 .ENUM; + i := info.(*Type_Info_Enum); + if i.values.count == 0 { + return 0; + } + + max := i.values[0].(T, no_check); + if i.internal_type.signed { + for i.values if xx it > max { + max = xx it; + } + } + else { + for i.values if xx it > max { + max = xx it; + } + } + + return max; + + case; + CompileError("max_of requires an enum, integer, or float type", loc = loc); + } + + return 0; + }; +} + +/// range_of returns the minimum and maximum values T can represent. +/// +/// Note: T must be an integer, float, or enum type. +range_of :: ($T: Type, loc := #caller_location) -> (T, T) #expand { + return min_of(T, loc = loc), max_of(T, loc = loc); +} + +/// sector creates a named block that can exit early via the 'break' keyword. +/// +/// Note: The block created by sector is called 'early' by default. +/// +/// for sector() { +/// break; +/// break early; // automatically created +/// } +/// +/// for sector("render_player") { +/// break render_player; +/// } +/// +sector :: ($name := Sector().Name) -> Sector(name) #expand { return .{}; } + +// @note(judah): there seems to be a weird race condition in the compiler +// that causes this to hit a null reference check error if running at compile-time. +for_expansion :: (v: Sector, code: Code, _: For_Flags) #expand { + // @todo(judah): fix this case? + // 'for this: sector() { break early; break this; }' + // both names valid here! + #insert #run basic.tprint(#string END + for `%1: 0..0 { + `it :: #run zero_of(void); + `it_index :: #run zero_of(void); + #insert,scope(code) code; + } + END, + // @note(judah): guards against calling this_block with + // an empty string which results in weird error messages. + ifx v.Name.count != 0 v.Name else Sector().Name); +} + + +#scope_file + +Sector :: struct(Name: string = "early") {} + +basic :: #import "Basic"; diff --git a/internal/memory.jai b/internal/memory.jai new file mode 100644 index 0000000..aa49082 --- /dev/null +++ b/internal/memory.jai @@ -0,0 +1,55 @@ +/// 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"); } + return memcmp(p1, p2, size_in_bytes) == 0; // Provided by Preload +} + +/// MemCopy copies the memory of src to dst. +/// +/// Note: MemCopy will panic if size_in_bytes is negative. +MemCopy :: (dst: *void, src: *void, size_in_bytes: int) { + if size_in_bytes < 0 + { Panic("size_in_bytes cannot be negative"); } + memcpy(dst, src, size_in_bytes); // Provided by Preload +} + +/// MemOverwrite overwites the memory of p with value. +/// +/// Note: MemOverwrite will panic if size_in_bytes is negative. +MemOverwrite :: (p: *void, size_in_bytes: int, value: u8 = 0) { + if size_in_bytes < 0 + { Panic("size_in_bytes cannot be negative"); } + memset(p, value, size_in_bytes); // Provided by preload +} + +/// MemZero zeroes the memory of p. +/// +/// Note: MemZero will panic if size_in_bytes is negative. +MemZero :: (p: *void, size_in_bytes: int) { + MemOverwrite(p, size_in_bytes, 0); +} + +/// MemZero zeroes the memory of p. +/// +/// Note: MemZero will not call the initializer for aggregate types, +/// so you may want MemReset instead. +MemZero :: (p: *$T) { + MemOverwrite(p, size_of(T), 0); +} + +/// MemReset resets the memory of p, as if it was just instantiated. +/// +/// Note: MemReset will call the initializer for aggregate types, so you +/// may want MemZero instead. +MemReset :: (p: *$T) { + initializer :: initializer_of(T); + #if initializer { + inline initializer(p); + } + else { + inline MemZero(p); + } +} diff --git a/internal/module.jai b/internal/module.jai new file mode 100644 index 0000000..b1d82d4 --- /dev/null +++ b/internal/module.jai @@ -0,0 +1 @@ +#assert false "This module (jc/internal) is not expected to be imported directly. Import 'jc' instead."; diff --git a/internal/testing.jai b/internal/testing.jai new file mode 100644 index 0000000..ce334ba --- /dev/null +++ b/internal/testing.jai @@ -0,0 +1,115 @@ +// Usage: +#if 0 { + #run,stallable { + Test("thing", t => { + Expect(some_condition, "error message: %", value); + }); + + Test("other thing", t => { + Expect(other_condition, "error message: %", value); + }); + + // ... + } +} + +/// Test defines a new suite to be executed by the test runner. +/// +/// See: Expect for more information. +/// +/// Test("my_proc does what it should", t => { +/// value1 := my_proc(/* ... */); +/// Expect(value1 != 0, "my_proc returned zero!"); +/// +/// value2 := my_proc(/* .... */); +/// Expect(value2 > 0, "my_proc returned a negative number!"); +/// }); +/// +Test :: (name: string, proc: (*void) -> (), loc := #caller_location) { + // @note(judah): incredibly dumb way to get nicer test runs + path := loc.fully_pathed_filename; + + i := path.count - 1; + found_first_slash := false; + + while i >= 0 { + if path[i] == "/" { + if found_first_slash { + i += 1; + break; + } + + found_first_slash = true; + } + + i -= 1; + } + + if !found_first_slash { + path = strings.path_filename(loc.fully_pathed_filename); + } + else { + path.count -= i; + path.data += i; + } + + WriteString(path, ","); + WriteNumber(loc.line_number); + WriteString(": ", name, "... "); + + t: TestRun; + proc(*t); + + if t.failed { + WriteString("failed"); + } + else { + WriteString("ok"); + } + + WriteString(" ("); + WriteNumber(t.total_ok); + WriteString("/"); + WriteNumber(t.total_expects); + WriteString(")\n"); +} + +/// Expect checks the given condition, failing the current test if it is false. +/// +/// Note: Expect must be called within a test. +Expect :: (cond: bool, message := "", args: ..Any, loc := #caller_location) #expand { + run := `t.(*TestRun); + run.total_expects += 1; + if cond { + run.total_ok += 1; + return; + } + + msg := "expectation failed"; + if message.count != 0 { + msg = basic.tprint(message, ..args); + } + + run.failed = true; + if #compile_time { + CompileError(msg, loc = loc); + } + else { + WriteStderrLocation(loc); + WriteStderrString(": ", msg, "\n"); + } +} + + +#scope_file + +TestRun :: struct { + location: Source_Code_Location; + total_expects: s64; + total_ok: s64; + failed: bool; +} + +basic :: #import "Basic"; // @future +strings :: #import "String"; // @future +compiler :: #import "Compiler"; // @future diff --git a/module.jai b/module.jai index eb9858c..6f36c10 100644 --- a/module.jai +++ b/module.jai @@ -1,26 +1,15 @@ -#scope_export; +/* + Module jc contains procedures for working with memory, + arrays, and strings; as well as helpful macros and + constants. -byte :: u8; -f32 :: float32; -f64 :: float64; + Additionally, it provides a platform-independant + interface for interacting with the target operating + system. +*/ -cstring :: *byte; -rawptr :: *void; - -#if size_of(int) == size_of(s64) { - sint :: s64; - uint :: u64; -} -else { - sint :: s32; - uint :: u32; -} - -#if size_of(rawptr) == size_of(u64) { - uptr :: u64; - sptr :: s64; -} -else { - uptr :: u32; - sptr :: s32; -} +#load "internal/builtin.jai"; +#load "internal/array.jai"; +#load "internal/memory.jai"; +#load "internal/testing.jai"; +#load "internal/keywords.jai"; -- 2.45.2 From 5447a4ee0687295dcb011333d2a56faca289b4f9 Mon Sep 17 00:00:00 2001 From: Judah Caruso
> 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, "
-
Modules
+ -
+ END);
+
+
+ for sorted_names if !contains(it, "jc/ext") {
+ print_to_builder(*b, "
- %2 ", LinkableName(it), it); + } + + 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 +
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
--
2.45.2
From 3bd9b6b3e5e12acb5b0930da7d59ed0974f7e9d2 Mon Sep 17 00:00:00 2001
From: Judah Caruso