Reorganize everything #2
22 changed files with 2299 additions and 85 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
||||||
.build/
|
.build/
|
||||||
**.dSYM
|
**.dSYM
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
docs/
|
||||||
|
|
|
||||||
|
|
@ -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
1288
_generate_docs.jai
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
ext/darwin/Foundation/NSObject.jai
Normal file
1
ext/darwin/Foundation/NSObject.jai
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
NSObject :: struct {};
|
||||||
21
ext/darwin/Foundation/NSString.jai
Normal file
21
ext/darwin/Foundation/NSString.jai
Normal 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);
|
||||||
8
ext/darwin/Foundation/module.jai
Normal file
8
ext/darwin/Foundation/module.jai
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
NSObject :: struct {
|
||||||
|
id: u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString :: struct {
|
||||||
|
#as using isa: NSObject;
|
||||||
|
}
|
||||||
|
|
||||||
38
ext/darwin/foundation.jai
Normal file
38
ext/darwin/foundation.jai
Normal 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
47
ext/darwin/module.jai
Normal 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";
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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,
|
||||||
SIMD := true,
|
/// Enable SIMD support.
|
||||||
UNITS := (enum { radians; degrees; turns; }).radians
|
SIMD := true,
|
||||||
|
/// Angle units to use.
|
||||||
|
UNITS : enum { radians; degrees; turns; } = .radians
|
||||||
);
|
);
|
||||||
|
|
||||||
#scope_export;
|
#scope_export;
|
||||||
|
|
|
||||||
|
|
@ -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
47
ext/objc/module.jai
Normal 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";
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
205
internal/array.jai
Normal 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
180
internal/builtin.jai
Normal 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
233
internal/keywords.jai
Normal 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
55
internal/memory.jai
Normal 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
1
internal/module.jai
Normal 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
115
internal/testing.jai
Normal 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
|
||||||
37
module.jai
37
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;
|
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;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue