reorg part 1

This commit is contained in:
Judah Caruso 2025-09-03 20:27:41 -06:00
parent 5cbcc10444
commit a909496e27
22 changed files with 2299 additions and 85 deletions

1
.gitignore vendored
View file

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

View file

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

1288
_generate_docs.jai Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,15 +1,8 @@
RunTests :: true;
#scope_file #run { #scope_file #run {
compiler :: #import "Compiler"; compiler :: #import "Compiler";
compiler.set_build_options_dc(.{ do_output = false }); compiler.set_build_options_dc(.{ do_output = false });
#import "jc/array"(RUN_TESTS = true); #load "module.jai";
#import "jc/encoding"(RUN_TESTS = true);
#import "jc/hash"(RUN_TESTS = true);
#import "jc/memory"(RUN_TESTS = true);
#import "jc/meta"(RUN_TESTS = true);
#import "jc/platform"(RUN_TESTS = true);
rmath :: #import "jc/math"(.radians, RUN_TESTS = true);
dmath :: #import "jc/math"(.degrees, RUN_TESTS = true);
tmath :: #import "jc/math"(.turns, RUN_TESTS = true);
} }

View file

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

View file

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

View file

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

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

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

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

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

View file

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

View file

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

View file

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

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

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

View file

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

View file

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

205
internal/array.jai Normal file
View file

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

180
internal/builtin.jai Normal file
View file

@ -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

233
internal/keywords.jai Normal file
View file

@ -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";

55
internal/memory.jai Normal file
View file

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

1
internal/module.jai Normal file
View file

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

115
internal/testing.jai Normal file
View file

@ -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

View file

@ -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; Additionally, it provides a platform-independant
f32 :: float32; interface for interacting with the target operating
f64 :: float64; system.
*/
cstring :: *byte; #load "internal/builtin.jai";
rawptr :: *void; #load "internal/array.jai";
#load "internal/memory.jai";
#if size_of(int) == size_of(s64) { #load "internal/testing.jai";
sint :: s64; #load "internal/keywords.jai";
uint :: u64;
}
else {
sint :: s32;
uint :: u32;
}
#if size_of(rawptr) == size_of(u64) {
uptr :: u64;
sptr :: s64;
}
else {
uptr :: u32;
sptr :: s32;
}