Compare commits

..

No commits in common. "3bd9b6b3e5e12acb5b0930da7d59ed0974f7e9d2" and "5447a4ee0687295dcb011333d2a56faca289b4f9" have entirely different histories.

48 changed files with 2519 additions and 882 deletions

View file

@ -1,76 +0,0 @@
AllocatorMode :: enum {
using Allocator_Mode;
// Compatability
Allocate :: ALLOCATE;
Resize :: RESIZE;
Free :: FREE;
Startup :: STARTUP;
Shutdown :: SHUTDOWN;
ThreadStart :: THREAD_START;
ThreadStop :: THREAD_STOP;
CreateHeap :: CREATE_HEAP;
DestroyHeap :: DESTROY_HEAP;
IsThisYours :: IS_THIS_YOURS;
Capabilities :: CAPS;
}
/// PanicAllocator causes the program to panic when used.
PanicAllocator :: () -> Allocator {
return .{ proc = xx PanicAllocatorProc };
}
TrySetAllocator :: (thing: *$T, allocator := context.allocator) #modify {
info := T.(*Type_Info_Struct);
ok := false;
if info.type == .STRUCT
{ ok = true; }
if ok for info.members if it.name == "allocator" && it.type == Allocator.(*Type_Info) {
ok = true;
break;
}
return ok, "can only set allocator on struct with an allocator field or dynamic array";
} #expand {
if thing.allocator.proc == null {
thing.allocator = allocator;
}
}
TrySetAllocator :: (array: *[..]$T, allocator := context.allocator) #expand {
if array.allocator.proc == null {
array.allocator = allocator;
}
}
#scope_module
PanicAllocatorProc :: (mode: AllocatorMode, new_size: int, old_size: int, ptr: *void, allocator_data: *void) -> *void {
if mode == {
case .Allocate;
if new_size > 0 {
Panic("jc: allocation using the PanicAllocator");
}
case .Resize;
if new_size > 0 {
Panic("jc: reallocation using the PanicAllocator");
}
case .Free;
if ptr != null {
Panic("jc: free using the PanicAllocator");
}
case .ThreadStart; #through;
case .ThreadStop;
Panic("jc: start/stop thread using the PanicAllocator");
case .CreateHeap; #through;
case .DestroyHeap;
Panic("jc: create/destroy heap using the PanicAllocator");
}
return null;
} @jc.nodocs

View file

@ -1,181 +0,0 @@
// 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);
});
}

View file

@ -1,43 +0,0 @@
// @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);
}

20
INBOX
View file

@ -2,10 +2,12 @@
Put any questions for me in here! Put any questions for me in here!
[Jesse] [Jesse]
05.31.25 Jesse 05.22.25 Judah
platform/arch merge sounds good, we can keep that going. No thoughts so far on that. Instead of going with a separate 'arch' module, I
The caching is a good idea, I wonder if we should make them enum_flags. think we can roll that into the future 'platform'
I don't know if it will go over a u64 though. module instead. There's enough overlap that I
think it makes sense. Let me know what you think,
thanks!
05.23.25 Judah 05.23.25 Judah
I went ahead and created the platform module and I went ahead and created the platform module and
added arch-specific extension checking. Sadly added arch-specific extension checking. Sadly
@ -14,9 +16,7 @@
it to add a 'SIMD' constant that we set for it to add a 'SIMD' constant that we set for
targets that will almost always support SIMD targets that will almost always support SIMD
instructions; unsure for now. instructions; unsure for now.
05.22.25 Judah 05.31.25 Jesse
Instead of going with a separate 'arch' module, I platform/arch merge sounds good, we can keep that going. No thoughts so far on that.
think we can roll that into the future 'platform' The caching is a good idea, I wonder if we should make them enum_flags.
module instead. There's enough overlap that I I don't know if it will go over a u64 though.
think it makes sense. Let me know what you think,
thanks!

View file

@ -1,41 +0,0 @@
Plan:
This is what I'm doing day-to-day.
/ day marker
+ completed that day
> completed on a later day
< decided against on a later day
: notes, todos, etc.
/ 09.06.25
+ fixed doc generator
+ encoding is now fmt
+ math tests pass after reorg
+ docs look nicer
: need to reimplement dynamic arrays
: stable arrays too?
/ 09.05.25
> cleaned up repo and finally merged everything in, sorry for breaking everything Jesse
+ got rid of jc/meta
+ had to change how tests work... again
+ updated encoding/*.jai
+ clearer Expect docs
+ cleaned up documentation
+ added temp_allocator to context when jc is imported. assumes all modules will import jc which should be a safe bet
+ internal/allocators.jai with PanicAllocator
+ updated my compiler version to 0.2.017, no changes needed
+ error messages now start with 'jc:'
+ decided to start using John Carmack-styled plan files to keep track of what I'm working on. would be cool to document the repo as it changes over time.
: planning out how I want allocators to work. I want to go big into arenas, but don't want to completely abandon Jai's builtin allocators

51
README
View file

@ -1,15 +1,46 @@
-------------- --
jc Jai Library jc
-------------- --
For installation instructions and documentation, see: # Direct installation
https://judahcaruso.com/jc 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 local documentation, run 'jai _generate_docs.jai' then What
navigate to the 'docs' subdirectory. ----
A set of modules for things I usually want in Jai, namely,
utilities, bindings, and experiments.
License How
------- ---
If you'd like to learn more about *what* a specific module
does, take a look at its 'module.jai' file.
If you'd like to contribute, read STYLEGUIDE.
Why
---
Because Jai is still in closed beta (as of May 15, 2025),
updates to the compiler and "standard library" will break
projects of mine; sometimes in a very annoying way.
jc was made to 1) give myself an escape hatch/skin-suit to
cause fewer breaking changes when updating the compiler,
and 2) put all of my non-project code in a single place
that's easier to manage.
While I do use many of the modules shipped with the
compiler, my goal is to eventually replace them.
Public Domain

View file

@ -42,8 +42,8 @@ Communication
------------- -------------
If direction is needed, create a single commit with an If direction is needed, create a single commit with an
entry in 'INBOX' at the top of that person's section; the entry in 'INBOX' under that person's section; the commit
commit message should roughly say 'message for [name]'. message should roughly say 'message for [name]'.
If an immediate response is needed, opt for direct If an immediate response is needed, opt for direct
messages instead. messages instead.
@ -51,14 +51,14 @@ messages instead.
Use this as a base plate to write new messages: Use this as a base plate to write new messages:
[ToName] [ToName]
05.25.25 ToName // This is a response
Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim
id est laborum.
05.22.25 FromName // This is a question 05.22.25 FromName // This is a question
Lorem ipsum dolor sit amet, consectetur Lorem ipsum dolor sit amet, consectetur
adipisicing elit, sed do eiusmod tempor adipisicing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. incididunt ut labore et dolore magna aliqua.
05.22.25 ToName // This is a response
Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim
id est laborum.
You should check your inbox *every time* you pull. You should check your inbox *every time* you pull.
@ -79,20 +79,13 @@ Things I actually care about:
premise that code is self-documenting. Take a step premise that code is self-documenting. Take a step
back and write a comment (with your name) that back and write a comment (with your name) that
explains *why* you're doing *what* you're doing. explains *why* you're doing *what* you're doing.
Sticking to this format makes my life easier: Please try to stick to this format:
@note(name), @todo, @temp, etc. @note, @todo, @temp, @allocates, @leak
- Descriptive names. In general, it's fine to use short - Descriptive names. In general, it's fine to use short
names for local variables. However, you should names for local variables. However, you should
almost always opt for longer, more descriptive names almost always opt for longer, more descriptive names
in every other context. in every other context.
- Documentation will get auto generated if the comments
start with '///'. General format is: '/// Foo ...'
where 'Foo' is a procedure, type, whatever. To get
examples to show up, indent them more than the other
text. To prevent something from being documented,
attach '@jc.nodocs' to it.
Imports Imports
@ -103,24 +96,48 @@ namespace them. The eventual goal of this repo is to be a
standalone library that doesn't rely on compiler-shipped standalone library that doesn't rely on compiler-shipped
modules (unless absolutely required). modules (unless absolutely required).
basic :: #import "Basic"; // @future basic :: #import "Basic";
When importing 'jc' modules, ALWAYS namespace them and use When importing 'jc' modules, ALWAYS namespace them and use
absolute import paths. '#import,file' does not function absolute import paths. '#import,file' does not function
the same as '#import' and causes issues when mixed the same as '#import' and causes issues when mixed
with '#load'. with '#load'. '_run_all_tests.jai' is an exception to
this rule.
#import "jc"; // the base module can be unnamed
math :: #import "jc/math"; math :: #import "jc/math";
Modules
-------
Modules should *generally* have a small scope and not be
nested unless it allows better organization. Files within
modules are not intended to be imported directly; it is
the job of 'module.jai' to '#load' and '#scope_export'
module files.
When authoring a new module, use this as a base plate:
// Within module_name/module.jai
#module_parameters(RUN_TESTS := false); // Should be the last parameter if others are required
#load "file_a.jai";
#load "file_b.jai";
#scope_file;
#if RUN_TESTS {
test :: #import "jc/test";
}
Memory Management Memory Management
----------------- -----------------
If a type needs dynamically allocated memory to function, If a custom type needs dynamically allocated memory to
it should always assume the memory came from an arena function, it should always assume the memory came from an
allocator. This means it is the responsibility of the arena allocator. This means it is the responsibility of
arena to free the memory, not the data type. the arena to free the memory, not the custom data type.
In other words: In other words:
@ -128,7 +145,39 @@ Do *not* add procedures that 'free' or 'delete' the memory
allocated by the data type. allocated by the data type.
Instead, add procedures that 'reset' the data type, Instead, add procedures that 'reset' the data type,
allowing its memory to be reused. 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.
Bindings Bindings
@ -138,10 +187,11 @@ Binding modules should default to static linking and take
an optional '#module_parameter' to link dynamically. an optional '#module_parameter' to link dynamically.
Libraries should be pinned to a specific version, and all Libraries should be pinned to a specific version, and all
binaries (.dll, .dylib, etc.) *must* be checked into binaries (.dll, .dylib, etc.) *must* be checked into
source control. If possible, use a release build of the source control. If possible, use the release build of the
library that includes debug information. library that includes debug information.
Bindings should stay as close as possible to the original Bindings should stay as close as possible to the original
library. To jai-ify the bindings, create a submodule library. To jai-ify the bindings, create a submodule
called 'wrapper' that import and wraps the api. called 'wrapper' that import and wraps the api.
See: 'thirdparty/raylib' for example bindings.

View file

@ -18,19 +18,14 @@ UseLocalLinks :: false; // set to false when deploying the docs
// Add freestanding modules here. // Add freestanding modules here.
// Other modules will be automatically generated if they're imported by someone else. // Other modules will be automatically generated if they're imported by someone else.
_ :: #import "jc"; jc :: #import "jc";
_ :: #import "jc/math"; hmm :: #import "jc/ext/hmm";
_ :: #import "jc/fmt/base64"; luajit :: #import "jc/ext/luajit";
raylib :: #import "jc/ext/raylib";
remotery :: #import "jc/ext/remotery";
_ :: #import "jc/x/json"; // darwin :: #import "jc/ext/darwin";
// objc :: #import "jc/ext/objc";
_ :: #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); END, ws);
CheckImport :: (import: *Code_Directive_Import) -> bool { CheckImport :: (import: *Code_Directive_Import) -> bool {
@ -108,12 +103,6 @@ UseLocalLinks :: false; // set to false when deploying the docs
array = *mod.macros; 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, .{ array_add(array, .{
decl = decl, decl = decl,
node = header, node = header,
@ -125,9 +114,6 @@ UseLocalLinks :: false; // set to false when deploying the docs
if !(decl.flags & .IS_CONSTANT) if !(decl.flags & .IS_CONSTANT)
{ continue; } { continue; }
if decl.name == "RunTests" || decl.name == "RUN_TESTS"
{ continue; }
array := *mod.consts; array := *mod.consts;
// Things we don't want to be constant decls // Things we don't want to be constant decls
@ -202,49 +188,20 @@ UseLocalLinks :: false; // set to false when deploying the docs
join(..contributors, ", "), join(..contributors, ", "),
); );
sorted_names := quick_sort(names, SortAlphabeticallyDescendingLength); append(*b, #string END
<details id='index' class='index' open='true'>
<summary><h3>Modules</h3></summary>
<ul>
END);
for quick_sort(names, SortAlphabeticallyDescendingLength) {
print_to_builder(*b, "<li><a href='%1'>%2</a></li>", LinkableName(it), it);
}
append(*b, #string END append(*b, #string END
<div class='index'> </ul>
</details>
END); END);
{
append(*b, #string END
<details open='true'>
<summary><h3>Modules</h3></summary>
<ul>
END);
for sorted_names if !contains(it, "jc/ext") {
print_to_builder(*b, "<li><a href='%1'>%2</a></li>", LinkableName(it), it);
}
append(*b, #string END
</ul>
</details>
END);
}
{
append(*b, #string END
<details>
<summary><h3>Bindings</h3></summary>
<ul>
END);
for sorted_names if contains(it, "jc/ext") {
print_to_builder(*b, "<li><a href='%1'>%2</a></li>", LinkableName(it), it);
}
append(*b, #string END
</ul>
</details>
END);
}
append(*b, "</div>");
append(*b, #string END append(*b, #string END
<p> <p>
<strong>What:</strong><br/> <strong>What:</strong><br/>
@ -278,8 +235,8 @@ git clone https://git.brut.systems/judah/jc.git
# Indirect installation # Indirect installation
git clone https://git.brut.systems/judah/jc.git git clone https://git.brut.systems/judah/jc.git
ln -s "$(pwd)jc" [jai install dir]/modules/jc # POSIX install ln -s "/path/to/jc" [jai install dir]/modules/jc # POSIX install
mklink /D "C:\path\to\jc" [jai install dir]\jc # Windows install mklink /D "C:\path\to\jc" [jai install dir]\jc # Windows install
# Usage: # Usage:
#import "jc"; #import "jc";
@ -299,9 +256,9 @@ mklink /D "C:\path\to\jc" [jai install dir]\jc # Windows install
source_files: Table(string, []string); source_files: Table(string, []string);
GetSourceLines :: (filename: string) -> []string, bool { GetSourceLines :: (filename: string) -> []string, bool {
old_logger := context.logger; // old_logger := context.logger;
context.logger = (message: string, data: *void, info: Log_Info) {}; // do nothing logger // context.logger = (message: string, data: *void, info: Log_Info) {}; // do nothing logger
defer context.logger = old_logger; // defer context.logger = old_logger;
fok, src := table_find_new(*source_files, filename); fok, src := table_find_new(*source_files, filename);
if fok { if fok {
@ -309,15 +266,12 @@ GetSourceLines :: (filename: string) -> []string, bool {
} }
lines: []string;
data, rok := read_entire_file(filename); data, rok := read_entire_file(filename);
lines: []string;
if rok { if rok {
lines = split(data, "\n"); lines = split(data, "\n");
table_add(*source_files, filename, lines); table_add(*source_files, filename, lines);
} }
else {
print("warn: file didn't exist '%'\n", filename);
}
return lines, rok; return lines, rok;
} }
@ -420,13 +374,6 @@ ModuleToHtml :: (mod: *Module) -> string {
StartPage(*b, tprint("Module % &ndash; Jc v%.% Documentation", import_name, JcMajor, JcMinor)); StartPage(*b, tprint("Module % &ndash; Jc v%.% Documentation", import_name, JcMajor, JcMinor));
print_to_builder(*b, "<h1><a href='index.html'>[..]</a> Module &ndash; %</h1>", short_name); print_to_builder(*b, "<h1><a href='index.html'>[..]</a> Module &ndash; %</h1>", short_name);
print_to_builder(*b, "<p>%</p>", mod.main_docs); print_to_builder(*b, "<p>%</p>", mod.main_docs);
if contains(import_name, "jc/x") {
append(*b, "<span class='module-warning'>");
print_to_builder(*b, "Warning: % is experimental and has no stability guarantees or versioning; use with caution.", short_name);
append(*b, "</span>");
}
print_to_builder(*b, "<pre class='code'>#import \"%\";</pre>", import_name); print_to_builder(*b, "<pre class='code'>#import \"%\";</pre>", import_name);
PrintIndexFor :: (b: *String_Builder, name: string, decls: []Decl) { PrintIndexFor :: (b: *String_Builder, name: string, decls: []Decl) {
@ -782,10 +729,6 @@ CleanJaiSource :: (node: *Code_Node) -> string {
postfix = tprint("% #compile_time", postfix); postfix = tprint("% #compile_time", postfix);
} }
if header.procedure_flags & .SYMMETRIC {
postfix = tprint("% #symmetric", postfix);
}
return tprint("%1%2", trim(tail), postfix); return tprint("%1%2", trim(tail), postfix);
} }
// for everything else, get the source range for the node and only strip out // for everything else, get the source range for the node and only strip out
@ -848,21 +791,18 @@ CleanJaiSource :: (node: *Code_Node) -> string {
source := join(..cleaned_lines, "\n"); source := join(..cleaned_lines, "\n");
index := find_index_from_left(source, "::"); index := find_index_from_left(source, "::");
// @todo(judah): handle module parameters correctly // This is like 95% a module parameter
if index == -1 { if index == -1 {
#import "Program_Print"; #import "Program_Print";
b: String_Builder; b: String_Builder;
is_module_param := true;
if decl.expression != null { if decl.expression != null {
print_expression(*b, decl.expression); print_expression(*b, decl.expression);
} }
else if decl.type_inst { else if decl.type_inst {
print_expression(*b, decl.type_inst); print_expression(*b, decl.type_inst);
is_module_param = false;
} }
append(*b, ";"); append(*b, "; // #module_parameter");
return builder_to_string(*b); return builder_to_string(*b);
} }
@ -999,15 +939,12 @@ MainCss :: #string END
} }
body { body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
line-height: 1.6; line-height: 1.6;
color: var(--text-primary); color: var(--text-primary);
background-color: var(--bg-primary); background-color: var(--bg-primary);
margin: 0; margin: 0;
padding: 0; padding: 0;
font-feature-settings: 'kern' 1, 'liga' 1, 'calt' 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
} }
h1 { h1 {
@ -1029,7 +966,7 @@ h4 {
font-size: 1.1rem; font-size: 1.1rem;
font-weight: 600; font-weight: 600;
margin: 1.5rem 0 1rem 0; margin: 1.5rem 0 1rem 0;
font-family: 'Menlo', 'Monaco', 'SF Mono', 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
} }
a { a {
@ -1108,7 +1045,7 @@ h4 a:hover {
.index a { .index a {
color: var(--link-color); color: var(--link-color);
font-family: 'Menlo', 'Monaco', 'SF Mono', 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
} }
.index a:hover { .index a:hover {
@ -1136,12 +1073,9 @@ pre.code {
padding: 1rem; padding: 1rem;
margin: 1rem 0; margin: 1rem 0;
overflow-x: auto; overflow-x: auto;
font-family: 'Menlo', 'Monaco', 'SF Mono', 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
font-size: 0.9rem; font-size: 0.9rem;
line-height: 1.4; line-height: 1.4;
font-feature-settings: 'liga' 1, 'calt' 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
} }
span.ident { span.ident {
@ -1217,23 +1151,6 @@ main {
font-size: 1.6rem; font-size: 1.6rem;
margin: 1.5rem 0 0.75rem 0; 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, a:focus,
@ -1279,17 +1196,6 @@ html {
outline: 2px solid var(--accent-color); outline: 2px solid var(--accent-color);
outline-offset: 2px; 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; END;
ResetCss :: #string END ResetCss :: #string END
@ -1369,7 +1275,7 @@ textarea:not([rows]) {
END; END;
#import "jc"; #load "./module.jai";
#import "String"; #import "String";
#import "Compiler"; #import "Compiler";

85
_make_module.jai Normal file
View file

@ -0,0 +1,85 @@
#run {
set_build_options_dc(.{ do_output = false });
args := get_build_options().compile_time_command_line;
usage :: () {
print("creates the template for a module or submodule\n\n");
print("usage: jai _make_module.jai - (module|submodule) [path]\n\n");
print("options:\n");
print("\tpath: a simple module path without an extension (example: 'foo' or 'foo/bar')\n");
}
if args.count < 2 {
usage();
return;
}
kind := trim(args[0]);
if kind.count == 0 {
usage();
return;
}
module_path := trim(args[1]);
if module_path.count == 0 {
usage();
return;
}
if kind == {
case "module";
assert(make_directory_if_it_does_not_exist(module_path), "could not create module directory: '%'", module_path);
entry_file := tprint("%/module.jai", module_path);
assert(write_entire_file(entry_file, tprint(MODULE_STRING, module_path)), "could not create %", entry_file);
case "submodule";
entry_file := tprint("%.jai", module_path);
assert(write_entire_file(entry_file, tprint(SUBMODULE_STRING, module_path)), "could not create %", entry_file);
case;
usage();
return;
}
}
MODULE_STRING :: #string END
// %1 is a module that does things.
//
#module_parameters(RUN_TESTS := false, IMPORTED_INTERNALLY := false);
#scope_export;
#if !IMPORTED_INTERNALLY {
// #import "jc/%1/submodule"(RUN_TESTS);
// ...
}
END;
SUBMODULE_STRING :: #string END
// %1 is a module that does stuff.
//
#module_parameters(RUN_TESTS := false);
#scope_export;
// ...
#scope_file;
// ----------------------------------------------------------
// TESTS
// ----------------------------------------------------------
#if RUN_TESTS #run {
test :: #import "jc/meta/test";
test.run("%1:works", t => {
test.expect(t, true);
});
}
END;
#import "File";
#import "Basic";
#import "String";
#import "Compiler";

View file

@ -1,10 +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"(true); #load "module.jai";
_ :: #import "jc/math"(RUN_TESTS = true);
_ :: #import "jc/fmt/base64"(true);
_ :: #import "jc/x/json"(true);
} }

58
array/bytes.jai Normal file
View file

@ -0,0 +1,58 @@
#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";
}

116
array/dynamic.jai Normal file
View file

@ -0,0 +1,116 @@
#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);
});
}

13
array/module.jai Normal file
View file

@ -0,0 +1,13 @@
#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;

194
array/stable.jai Normal file
View file

@ -0,0 +1,194 @@
#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);
});
}

171
array/static.jai Normal file
View file

@ -0,0 +1,171 @@
#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);
});
}

11
array/xar.jai Normal file
View file

@ -0,0 +1,11 @@
#module_parameters(RUN_TESTS := false);
#scope_file;
// ----------------------------------------------------------
// TESTS
// ----------------------------------------------------------
#if RUN_TESTS #run {
test :: #import "jc/meta/test";
}

View file

@ -1,16 +1,16 @@
#module_parameters(RunTests := false); #module_parameters(RUN_TESTS := false);
Base64Encode :: (str: string, $for_url := false) -> string, bool { base64_encode :: (str: string, $for_url := false) -> string, bool {
enc, ok := Base64Encode(str.([]u8), for_url); enc, ok := base64_encode(str.([]u8), for_url);
return enc.(string), ok; return enc.(string), ok;
} }
Base64Decode :: (str: string) -> string, bool { base64_decode :: (str: string) -> string, bool {
enc, ok := Base64Decode(str.([]u8)); enc, ok := base64_decode(str.([]u8));
return enc.(string), ok; return enc.(string), ok;
} }
Base64Encode :: ($$data: []u8, $for_url := false) -> []u8, bool { base64_encode :: ($$data: []u8, $for_url := false) -> []u8, bool {
if data.count == 0 return .[], false; if data.count == 0 return .[], false;
#if for_url { #if for_url {
@ -21,7 +21,7 @@ Base64Encode :: ($$data: []u8, $for_url := false) -> []u8, bool {
} }
padded := strings.contains(data.(string), Padding_Character); padded := strings.contains(data.(string), Padding_Character);
encoded := basic.NewArray(EncodedLength(data.count, padded), u8, true); encoded := basic.NewArray(encoded_length(data.count, padded), u8, true);
src_idx := 0; src_idx := 0;
dst_idx := 0; dst_idx := 0;
@ -70,7 +70,7 @@ Base64Encode :: ($$data: []u8, $for_url := false) -> []u8, bool {
return encoded, true; return encoded, true;
} }
Base64Decode :: (data: []u8) -> []u8, bool { base64_decode :: (data: []u8) -> []u8, bool {
if !data.count return .[], false; if !data.count return .[], false;
lookup :: (c: u8) -> u8 #expand { lookup :: (c: u8) -> u8 #expand {
@ -144,20 +144,17 @@ Base64Decode :: (data: []u8) -> []u8, bool {
return decoded, true; return decoded, true;
} }
#scope_file;
#scope_file
Padding_Character :: #char "="; Padding_Character :: #char "=";
Encoding_Url :: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; Encoding_Url :: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
Encoding_Standard :: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; Encoding_Standard :: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
EncodedLength :: (count: int, with_padding := false) -> int { encoded_length :: (count: int, with_padding := false) -> int {
if with_padding then return (count + 2) / 3 * 4; if with_padding then return (count + 2) / 3 * 4;
return (count * 8 + 5) / 6; return (count * 8 + 5) / 6;
} }
#import "jc";
basic :: #import "Basic"; // @future basic :: #import "Basic"; // @future
strings :: #import "String"; // @future strings :: #import "String"; // @future
@ -166,45 +163,47 @@ strings :: #import "String"; // @future
// TESTS // TESTS
// ---------------------------------------------------------- // ----------------------------------------------------------
#if RunTests #run { #if RUN_TESTS #run {
Test("encodes", t => { test :: #import "jc/meta/test";
test.run("encodes", (t) => {
str :: "Hello, World"; str :: "Hello, World";
encoded, ok := Base64Encode(str); encoded, ok := base64_encode(str);
Expect(ok, "'%' did not properly encode!", str); test.expect(t, ok, "'%' did not properly encode!", str);
expected :: "SGVsbG8sIFdvcmxk"; expected :: "SGVsbG8sIFdvcmxk";
Expect(encoded == expected, "wanted: '%', received: '%'", expected, encoded); test.expect(t, encoded == expected, "wanted: '%', received: '%'", expected, encoded);
}); });
Test("decodes", (t) => { test.run("decodes", (t) => {
encoded_str :: "SGVsbG8sIFdvcmxk"; encoded_str :: "SGVsbG8sIFdvcmxk";
decoded, ok := Base64Decode(encoded_str); decoded, ok := base64_decode(encoded_str);
Expect(ok, "'%' did not properly decode!", encoded_str); test.expect(t, ok, "'%' did not properly decode!", encoded_str);
expected :: "Hello, World"; expected :: "Hello, World";
Expect(decoded == expected, "wanted: '%', received: '%'", expected, decoded); test.expect(t, decoded == expected, "wanted: '%', received: '%'", expected, decoded);
}); });
Test("encodes/decodes padding", (t) => { test.run("encodes/decodes padding", (t) => {
str :: "VkdocGN5QnBjeUJ6YjIxbGRHaHBibWNnYlc5eVpTQmpiMjF3YkdWNExDQnBkQ0J6YUc5MWJHUWdhR0YyWlNCd1lXUmthVzVuUHc9PQ=="; str :: "VkdocGN5QnBjeUJ6YjIxbGRHaHBibWNnYlc5eVpTQmpiMjF3YkdWNExDQnBkQ0J6YUc5MWJHUWdhR0YyWlNCd1lXUmthVzVuUHc9PQ==";
first_decode, f_ok := Base64Decode(str); first_decode, f_ok := base64_decode(str);
Expect(f_ok, "first decode failed!"); test.expect(t, f_ok, "first decode failed!");
re_encode, r_ok := Base64Encode(first_decode); re_encode, r_ok := base64_encode(first_decode);
Expect(r_ok, "re-encode failed!"); test.expect(t, r_ok, "re-encode failed!");
second_decode, s_ok := Base64Decode(re_encode); second_decode, s_ok := base64_decode(re_encode);
Expect(s_ok, "second decode failed!"); test.expect(t, s_ok, "second decode failed!");
for 0..first_decode.count - 1 { for 0..first_decode.count - 1 {
Expect(first_decode[it] == second_decode[it], "Decoded byte '%' did not match expected output", it); test.expect(t, first_decode[it] == second_decode[it], "Decoded byte '%' did not match expected output", it);
} }
}); });
Test("encodes/decodes struct", (t) => { test.run("encodes/decodes struct", (t) => {
Foo :: struct { Foo :: struct {
first: u64; first: u64;
second: u64; second: u64;
@ -221,14 +220,14 @@ strings :: #import "String"; // @future
enc_builder: basic.String_Builder; enc_builder: basic.String_Builder;
basic.print_to_builder(*enc_builder, "%,%,%,%", foo.first, foo.second, foo.third, foo.forth); basic.print_to_builder(*enc_builder, "%,%,%,%", foo.first, foo.second, foo.third, foo.forth);
encoded, e_ok := Base64Encode(basic.builder_to_string(*enc_builder)); encoded, e_ok := base64_encode(basic.builder_to_string(*enc_builder));
Expect(e_ok, "Encode of structure failed!"); test.expect(t, e_ok, "Encode of structure failed!");
decoded, d_ok := Base64Decode(encoded); decoded, d_ok := base64_decode(encoded);
Expect(d_ok, "Decode of encoded structure failed!"); test.expect(t, d_ok, "Decode of encoded structure failed!");
parts := strings.split(decoded, ","); parts := strings.split(decoded, ",");
Expect(parts.count == 4, "Invalid number of parts after decode: %", parts.count); test.expect(t, parts.count == 4, "Invalid number of parts after decode: %", parts.count);
bar: Foo = ---; bar: Foo = ---;
@ -238,17 +237,17 @@ strings :: #import "String"; // @future
for info.members { for info.members {
value, ok := strings.parse_int(*parts[it_index], u64); value, ok := strings.parse_int(*parts[it_index], u64);
Expect(ok, "Integer parse of value % ('%') failed!", it_index, parts[it_index]); test.expect(t, ok, "Integer parse of value % ('%') failed!", it_index, parts[it_index]);
offset := (ptr + it.offset_in_bytes).(*u64); offset := (ptr + it.offset_in_bytes).(*u64);
offset.*= value; offset.*= value;
} }
} }
Expect(foo.first == bar.first, "Foo.first (%, %) didn't match between encoding/decoding!", foo.first, bar.first); test.expect(t, 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); test.expect(t, 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); test.expect(t, 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); test.expect(t, foo.forth == bar.forth, "Foo.forth (%, %) didn't match between encoding/decoding!", foo.forth, bar.forth);
}); });
} }

View file

@ -1,4 +1,4 @@
#module_parameters(RunTests := false); #module_parameters(RUN_TESTS := false);
Json_Value :: struct { Json_Value :: struct {
kind: enum { kind: enum {
@ -327,7 +327,7 @@ to_string :: (value: Json_Value, indent := 0) -> string {
} }
#scope_file #scope_file;
get_json_member_name :: (member: Type_Info_Struct_Member) -> string { get_json_member_name :: (member: Type_Info_Struct_Member) -> string {
name := member.name; name := member.name;
@ -609,3 +609,4 @@ at_end :: inline (p: *Parser) -> bool {
basic :: #import "Basic"; // @future basic :: #import "Basic"; // @future
strings :: #import "String"; // @future strings :: #import "String"; // @future

10
encoding/module.jai Normal file
View file

@ -0,0 +1,10 @@
#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;

10
hash/module.jai Normal file
View file

@ -0,0 +1,10 @@
#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);
}

58
hash/murmur.jai Normal file
View file

@ -0,0 +1,58 @@
#module_parameters(RUN_TESTS := false);
// Implementation: Demetri Spanos (github.com/demetri/scribbles)
// Jai Port: Jesse Coyle (github.com/Zilarrezko)
MurMur_Seed : u32 : 0xa3c91521;
murmur32 :: inline (s: string, seed: u32 = MurMur_Seed) -> u32 {
return murmur32(s.data, s.count, seed);
}
murmur32 :: inline (x: $T, seed: u32 = MurMur_Seed) -> u32 {
return murmur32(*x, size_of(T), seed);
}
murmur32 :: (key: *void, len: int, seed: u32 = MurMur_Seed) -> u32 {
scrambler :: (k: u32) -> u32 #expand {
c1: u32 : 0xcc9e2d51;
c2: u32 : 0x1b873593;
r1: int : 15;
k = k*c1;
k = k <<< r1;
k = k*c2;
return k;
}
h: u32 = seed;
tail: *u8 = cast(*u8)key + (len/4)*4;
p: *u32 = cast(*u32)key;
while cast(*u8)p < tail {
k: u32 = <<p;
k = scrambler(k);
h = h^k;
h = h <<< 13;
h = h*5 + 0xe6546b64;
p += 1;
}
t: u32;
if len & 3 == {
case 3;
t ^= cast(u32)(tail[2]) << 16;
#through;
case 2;
t ^= cast(u32)(tail[1]) << 8;
#through;
case 1;
t ^= cast(u32)(tail[0]);
t = scrambler(t);
h = h^t;
}
h ^= cast,trunc(u32)len;
h ^= h >> 16; h *= 0x85ebca6b;
h ^= h >> 13; h *= 0xc2b2ae35;
h ^= h >> 16;
return h;
}

191
hash/table.jai Normal file
View file

@ -0,0 +1,191 @@
#module_parameters(RUN_TESTS := false);
// Dead simple key-value pair type (aka. hash table or hash map)
Table :: struct(Key: Type, Value: Type) {
allocator: Allocator;
slots: [..]Slot;
free_slots: [..]int;
count: int;
Slot :: struct {
hash: u32 = invalid_hash;
key: Key = ---;
value: Value = ---;
}
hash_proc :: hash.murmur32;
invalid_hash :: (0x8000_dead).(u32); // @note(judah): I'm curious what values would hit this hash on accident
number_of_items_to_allocate_initially :: 16; // @note(judah): must be a power of two
}
get :: (t: *Table, key: t.Key) -> t.Value, bool {
slot, ok := find_slot(t, get_hash(t, key));
if !ok {
return mem.zero_of(t.Value), false;
}
return slot.value, true;
}
set :: (t: *Table, key: t.Key, value: t.Value) {
hash := get_hash(t, key);
slot, exists := find_slot(t, hash);
if !exists {
slot = create_or_reuse_slot(t);
slot.hash = hash;
}
slot.key = key;
slot.value = value;
}
exists :: (t: *Table, key: t.Key) -> bool {
_, exists := find_slot(t, get_hash(t, key));
return exists;
}
// @note(judah): we use 'delete' instead of 'remove' because it's a keyword...
delete :: (t: *Table, key: t.Key) -> t.Value, bool {
slot, ok, idx := find_slot(t, get_hash(t, key));
if !ok return mem.zero_of(t.Value), false;
last_value := slot.value;
mark_slot_for_reuse(t, idx);
return last_value, true;
}
reset :: (t: *Table) {
t.count = 0;
t.slots.count = 0;
t.free_slots.count = 0;
}
for_expansion :: (t: *Table, body: Code, flags: For_Flags) #expand {
#assert (flags & .POINTER == 0) "cannot iterate by pointer";
for <=(flags & .REVERSE == .REVERSE) slot: t.slots if slot.hash != t.invalid_hash {
`it := slot.value;
`it_index := slot.key;
#insert,scope(body)(break = break slot) body;
}
}
#scope_file;
get_hash :: inline (t: *Table, key: t.Key) -> u32 {
hash := t.hash_proc(key);
basic.assert(hash != t.invalid_hash, "key % collided with invalid hash marker (%)", key, t.invalid_hash);
return hash;
}
find_slot :: (t: *Table, hash: u32) -> *t.Slot, bool, int {
for * t.slots if it.hash == hash {
return it, true, it_index;
}
return null, false, -1;
}
create_or_reuse_slot :: (t: *Table) -> *t.Slot {
inline try_lazy_init(t);
if t.free_slots.count > 0 {
slot_idx := t.free_slots[t.free_slots.count - 1];
t.free_slots.count -= 1;
return *t.slots[slot_idx];
}
if t.slots.allocated == 0 {
array.resize(*t.slots, t.number_of_items_to_allocate_initially);
}
else if t.slots.count >= t.slots.allocated {
array.resize(*t.slots, mem.next_power_of_two(t.slots.allocated));
}
slot := array.append(*t.slots);
t.count = t.slots.count;
return slot;
}
mark_slot_for_reuse :: (t: *Table, index: int) {
inline try_lazy_init(t);
t.count -= 1;
t.slots[index] = .{ hash = t.invalid_hash };
array.append(*t.free_slots, index);
}
try_lazy_init :: inline (t: *Table) {
mem.lazy_set_allocator(t);
mem.lazy_set_allocator(*t.slots);
mem.lazy_set_allocator(*t.free_slots);
}
mem :: #import "jc/memory";
array :: #import "jc/array";
hash :: #import "jc/hash";
basic :: #import "Basic"; // @future
// ----------------------------------------------------------
// TESTS
// ----------------------------------------------------------
#if RUN_TESTS #run {
test :: #import "jc/meta/test";
test.run("basic operations", t => {
ITERATIONS :: 64;
values: Table(int, int);
for 0..ITERATIONS {
set(*values, it, it * it);
}
for 0..ITERATIONS {
v, ok := get(*values, it);
test.expect(t, v == it * it);
}
for 0..ITERATIONS if it % 2 == 0 {
_, ok := delete(*values, it);
test.expect(t, ok);
}
for 0..ITERATIONS if it % 2 == 0 {
_, ok := get(*values, it);
test.expect(t, !ok);
}
});
test.run("free slots", t => {
values: Table(int, int);
set(*values, 1, 100);
set(*values, 2, 200);
set(*values, 3, 300);
test.expect(t, values.count == 3);
test.expect(t, values.slots.allocated == values.number_of_items_to_allocate_initially);
// deleting something that doesn't exist should do nothing
_, ok := delete(*values, 0);
test.expect(t, !ok);
test.expect(t, values.count == 3);
delete(*values, 2);
test.expect(t, values.count == 2);
});
test.run("iteration", t => {
values: Table(int, int);
for 0..10 set(*values, it, it * it);
test.expect(t, values.count == 11);
for v, k: values test.expect(t, v == k * k);
for < v, k: values test.expect(t, v == k * k);
});
}

View file

@ -1,74 +1,19 @@
// Implementation: Demetri Spanos (github.com/demetri/scribbles) #module_parameters(RUN_TESTS := false);
// Jai Port: Jesse Coyle (github.com/Zilarrezko)
MurmurSeed : u32 : 0xa3c91521;
Murmur32 :: inline (s: string, seed: u32 = MurmurSeed) -> u32 {
return Murmur32(s.data, s.count, seed);
}
Murmur32 :: inline (x: $T, seed: u32 = MurmurSeed) -> u32 {
return Murmur32(*x, size_of(T), seed);
}
Murmur32 :: (key: *void, len: int, seed: u32 = MurmurSeed) -> u32 {
scrambler :: (k: u32) -> u32 #expand {
c1: u32 : 0xcc9e2d51;
c2: u32 : 0x1b873593;
r1: int : 15;
k = k*c1;
k = k <<< r1;
k = k*c2;
return k;
}
h: u32 = seed;
tail: *u8 = cast(*u8)key + (len/4)*4;
p: *u32 = cast(*u32)key;
while cast(*u8)p < tail {
k: u32 = <<p;
k = scrambler(k);
h = h^k;
h = h <<< 13;
h = h*5 + 0xe6546b64;
p += 1;
}
t: u32;
if len & 3 == {
case 3;
t ^= cast(u32)(tail[2]) << 16;
#through;
case 2;
t ^= cast(u32)(tail[1]) << 8;
#through;
case 1;
t ^= cast(u32)(tail[0]);
t = scrambler(t);
h = h^t;
}
h ^= cast,trunc(u32)len;
h ^= h >> 16; h *= 0x85ebca6b;
h ^= h >> 13; h *= 0xc2b2ae35;
h ^= h >> 16;
return h;
}
// Implementation: Demetri Spanos (github.com/demetri/scribbles) // Implementation: Demetri Spanos (github.com/demetri/scribbles)
// Jai Port: Jesse Coyle (github.com/Zilarrezko) // Jai Port: Jesse Coyle (github.com/Zilarrezko)
XXHashSeed :: 0; XXHash_Seed :: 0;
XXHash64 :: inline (s: string, seed: u32 = XXHashSeed) -> u64 { xxhash64 :: inline (s: string, seed: u32 = XXHash_Seed) -> u64 {
return XXHash64(s.data, s.count, seed); return xxhash64(s.data, s.count, seed);
} }
XXHash64 :: inline (x: $T, seed: u32 = XXHashSeed) -> u64 { xxhash64 :: inline (x: $T, seed: u32 = XXHash_Seed) -> u64 {
return XXHash64(cast(*u8)*x, size_of(T), seed); return xxhash64(cast(*u8)*x, size_of(T), seed);
} }
XXHash64 :: (key: *void, len: int, seed: u64 = XXHashSeed) -> u64 { xxhash64 :: (key: *void, len: int, seed: u64 = XXHash_Seed) -> u64 {
p1: u64 : 0x9e3779b185ebca87; p1: u64 : 0x9e3779b185ebca87;
p2: u64 : 0xc2b2ae3d27d4eb4f; p2: u64 : 0xc2b2ae3d27d4eb4f;
p3: u64 : 0x165667b19e3779f9; p3: u64 : 0x165667b19e3779f9;

View file

@ -1,39 +1,7 @@
/// Append pushes value to the end of an array, resizing if necessary.
///
/// Note: If no allocator has been set, Append will use the current context allocator.
/// Note: Calls to Append may invalidate pre-existing pointers.
Append :: inline (arr: *[..]$T, value: T) -> *T {
TrySetAllocator(arr);
ptr := basic.array_add(arr);
ptr.* = value;
return ptr;
}
/// Append pushes a default initialized value to the end of an array,
/// returning a pointer to the newly pushed value.
///
/// Note: If no allocator has been set, Append will use the current context allocator.
/// Note: Calls to Append may invalidate pre-existing pointers.
Append :: inline (arr: *[..]$T) -> *T {
return Append(arr, default_of(T));
}
/// Resize grows the memory associated with an array to hold new_count values.
///
/// Note: If the array has enough allocated memory to accomodate new_count, Resize does nothing.
/// Note: Resize does not guarantee pointer stability.
Resize :: inline (arr: *[..]$T, new_count: int) {
if new_count <= arr.allocated
{ return; }
TrySetAllocator(arr);
basic.array_reserve(arr, new_count,, allocator = arr.allocator);
}
/// Slice returns a subsection of an array. /// Slice returns a subsection of an array.
Slice :: (view: []$T, start_idx: int, count := -1, loc := #caller_location) -> []T { Slice :: (view: []$T, start_idx: int, count := -1, loc := #caller_location) -> []T {
AssertCallsite(start_idx >= +0 && start_idx < view.count, "jc: incorrect slice bounds"); AssertCallsite(start_idx >= +0 && start_idx < view.count, "incorrect slice bounds");
AssertCallsite(count >= -1 && count < view.count, "jc: incorrect slice length"); AssertCallsite(count >= -1 && count < view.count, "incorrect slice length");
if count == -1 if count == -1
{ count = view.count - start_idx; } { count = view.count - start_idx; }
@ -41,14 +9,15 @@ Slice :: (view: []$T, start_idx: int, count := -1, loc := #caller_location) -> [
return .{ data = view.data + start_idx, count = count }; return .{ data = view.data + start_idx, count = count };
} }
/// Reset sets an array's length to 0, but leaves its memory intact. /// Reset sets an array's length to 0, allowing it to be reused
/// Note: To reset the associated memory as well, see Clear. /// without allocating new memory.
Reset :: (view: *[]$T) { Reset :: (view: *[]$T) {
view.count = 0; view.count = 0;
} }
/// Clear zeroes an array's memory and sets its length to 0. /// Clear zeroes the memory of an array and sets its length to 0.
/// Note: To leave the associated memory intact, see Reset. ///
/// Note: Clear does not free the array's memory.
Clear :: (view: *[]$T) { Clear :: (view: *[]$T) {
MemZero(view.data, view.count * size_of(T)); MemZero(view.data, view.count * size_of(T));
view.count = 0; view.count = 0;
@ -61,18 +30,6 @@ Equal :: (lhs: []$T, rhs: []T) -> bool {
return MemEqual(lhs.data, rhs.data, lhs.count * size_of(T)); return MemEqual(lhs.data, rhs.data, lhs.count * size_of(T));
} }
CheckBounds :: ($$index: $T, $$count: T, loc := #caller_location) #expand {
Message :: "bounds check failed!";
#if is_constant(index) && is_constant(count) {
if index < 0 || index >= count {
CompileError(Message, loc = loc);
}
}
else if index < 0 || index > count {
Panic(Message, loc = loc);
}
}
FindFlags :: enum_flags { FindFlags :: enum_flags {
Last; // Return the last matching element. Last; // Return the last matching element.
@ -164,9 +121,7 @@ Trim :: (view: []$T, cutset: []T, $flags: TrimFlags = .FromStart) -> []T {
#scope_file #scope_file
basic :: #import "Basic"; // @future #if #exists(RunTests) #run,stallable {
#if RunTests #run,stallable {
Test("slice", t => { Test("slice", t => {
a1 := int.[ 1, 2, 3, 4, 5 ]; a1 := int.[ 1, 2, 3, 4, 5 ];
a2 := Slice(a1, 2); a2 := Slice(a1, 2);

View file

@ -67,7 +67,7 @@ b64 :: enum u64 { false_ :: (0 != 0).(u64); true_ :: (0 == 0).(u64); }; /// b64
/// Panic displays the given message and crashes the program. /// Panic displays the given message and crashes the program.
/// ///
/// Note: Defers will not run when Panic is called. /// Note: Defers will not run when Panic is called.
Panic :: (message := "jc: runtime panic", loc := #caller_location) #expand #no_debug { Panic :: (message := "runtime panic", loc := #caller_location) #expand #no_debug {
#if DebugBuild { #if DebugBuild {
WriteStderrLocation(loc); WriteStderrLocation(loc);
WriteStderrString(": "); WriteStderrString(": ");
@ -80,7 +80,7 @@ Panic :: (message := "jc: runtime panic", loc := #caller_location) #expand #no_d
/// Unreachable displays the given message and causes an execution trap. /// Unreachable displays the given message and causes an execution trap.
/// ///
/// Note: Defers will not run when Unreachable is called. /// Note: Defers will not run when Unreachable is called.
Unreachable :: (message := "jc: unreachable code hit", loc := #caller_location) #expand #no_debug { Unreachable :: (message := "unreachable code hit", loc := #caller_location) #expand #no_debug {
trap :: #ifx DebugBuild then DebugTrap else Trap; trap :: #ifx DebugBuild then DebugTrap else Trap;
#if DebugBuild { #if DebugBuild {
@ -100,7 +100,7 @@ CompileError :: (message: string, loc := #caller_location) #expand #no_debug #co
compiler_report(message, loc, .ERROR); compiler_report(message, loc, .ERROR);
} }
else { else {
Panic("jc: CompileError can only be called at compile-time", loc = loc); Panic("CompileError can only be called at compile-time", loc = loc);
} }
} }
@ -119,7 +119,7 @@ DebugTrap :: () #expand #no_debug {
#if DebugBuild #if DebugBuild
{ {
/// Assert causes a debug break if the given condition is false. /// Assert causes a debug break if the given condition is false.
Assert :: (cond: bool, message := "jc: condition was false", loc := #caller_location) #expand #no_debug { 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. // @note(judah): We only need to do this to route into the context's builtin assertion handling.
if cond || context.handling_assertion_failure return; if cond || context.handling_assertion_failure return;
@ -140,7 +140,7 @@ DebugTrap :: () #expand #no_debug {
/// Assert(false, loc = loc); // equivalent /// Assert(false, loc = loc); // equivalent
/// } /// }
/// ///
AssertCallsite :: (cond: bool, message := "jc: condition was false") #expand #no_debug { AssertCallsite :: (cond: bool, message := "condition was false") #expand #no_debug {
Assert(cond, message, loc = `loc); Assert(cond, message, loc = `loc);
} }
} }
@ -166,10 +166,6 @@ WriteStderrLocation :: (loc: Source_Code_Location) {
WriteStderrNumber(loc.character_number); WriteStderrNumber(loc.character_number);
} @jc.nodocs } @jc.nodocs
WriteStderrString :: #bake_arguments write_strings(to_standard_error = true); // Provided by Runtime_Support
WriteStderrNumber :: #bake_arguments write_number(to_standard_error = true); // Provided by Runtime_Support
#scope_file #scope_file
DebugBuild :: #run -> bool { DebugBuild :: #run -> bool {
@ -179,3 +175,6 @@ DebugBuild :: #run -> bool {
opts := get_build_options(); opts := get_build_options();
return opts.emit_debug_info != .NONE; 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

View file

@ -9,7 +9,7 @@ offset_of :: ($T: Type, ident: Code, loc := #caller_location) -> int #expand {
#run (loc: Source_Code_Location) { #run (loc: Source_Code_Location) {
info := type_info(T); info := type_info(T);
if info.type != .STRUCT { if info.type != .STRUCT {
CompileError("jc: offset_of can only be used on struct types", loc = loc); CompileError("offset_of can only be used on struct types", loc = 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 // 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? // from that pointer. What would you do with a field offset from **T?
if info.type == .POINTER { if info.type == .POINTER {
CompileError("jc: offset_of only allows one level of pointer indirection.", loc = loc); CompileError("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 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 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 8; return (ifx i.signed then -0x8000_0000_0000_0000 else 0).(T, no_check);
case ; CompileError("jc: unknown integer size", loc = loc); case ; CompileError("unknown integer size", loc = loc);
} }
case .FLOAT; case .FLOAT;
if info.runtime_size == { if info.runtime_size == {
case 4; return (0h0080_0000).(T, no_check); case 4; return (0h0080_0000).(T, no_check);
case 8; return (0h00100000_00000000).(T, no_check); case 8; return (0h00100000_00000000).(T, no_check);
case ; CompileError("jc: unknown float size", loc = loc); case ; CompileError("unknown float size", loc = loc);
} }
case .ENUM; case .ENUM;
@ -126,7 +126,7 @@ min_of :: ($T: Type, loc := #caller_location) -> T #expand {
return min; return min;
case; case;
CompileError("jc: min_of requires an enum, integer, or float type", loc = loc); CompileError("min_of requires an enum, integer, or float type", loc = loc);
} }
return 0; 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 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 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 8; return (ifx i.signed then 0x7fff_ffff_ffff_ffff else 0xffff_ffff_ffff_ffff).(T, no_check);
case ; CompileError("jc: unknown integer size", loc = loc); case ; CompileError("unknown integer size", loc = loc);
} }
case .FLOAT; case .FLOAT;
if info.runtime_size == { if info.runtime_size == {
case 4; return (0h7F7FFFFF).(T, no_check); case 4; return (0h7F7FFFFF).(T, no_check);
case 8; return (0h7FEFFFFF_FFFFFFFF).(T, no_check); case 8; return (0h7FEFFFFF_FFFFFFFF).(T, no_check);
case ; CompileError("jc: unknown float size", loc = loc); case ; CompileError("unknown float size", loc = loc);
} }
case .ENUM; case .ENUM;
@ -178,7 +178,7 @@ max_of :: ($T: Type, loc := #caller_location) -> T #expand {
return max; return max;
case; case;
CompileError("jc: max_of requires an enum, integer, or float type", loc = loc); CompileError("max_of requires an enum, integer, or float type", loc = loc);
} }
return 0; return 0;
@ -230,49 +230,4 @@ for_expansion :: (v: Sector, code: Code, _: For_Flags) #expand {
Sector :: struct(Name: string = "early") {} Sector :: struct(Name: string = "early") {}
basic :: #import "Basic"; // @future basic :: #import "Basic";
#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);
});
}

View file

@ -1,15 +1,9 @@
Kilobyte :: 1024;
Megabyte :: 1024 * Kilobyte;
Gigabyte :: 1024 * Megabyte;
DefaultAlign :: #run 2 * align_of(*void);
/// MemEqual checks the equality of two pieces of memory. /// MemEqual checks the equality of two pieces of memory.
/// ///
/// Note: MemEqual will panic if size_in_bytes is negative. /// Note: MemEqual will panic if size_in_bytes is negative.
MemEqual :: (p1: *void, p2: *void, size_in_bytes: int) -> bool { MemEqual :: (p1: *void, p2: *void, size_in_bytes: int) -> bool {
if size_in_bytes < 0 if size_in_bytes < 0
{ Panic("jc: size_in_bytes cannot be negative"); } { Panic("size_in_bytes cannot be negative"); }
return memcmp(p1, p2, size_in_bytes) == 0; // Provided by Preload return memcmp(p1, p2, size_in_bytes) == 0; // Provided by Preload
} }
@ -18,7 +12,7 @@ MemEqual :: (p1: *void, p2: *void, size_in_bytes: int) -> bool {
/// Note: MemCopy will panic if size_in_bytes is negative. /// Note: MemCopy will panic if size_in_bytes is negative.
MemCopy :: (dst: *void, src: *void, size_in_bytes: int) { MemCopy :: (dst: *void, src: *void, size_in_bytes: int) {
if size_in_bytes < 0 if size_in_bytes < 0
{ Panic("jc: size_in_bytes cannot be negative"); } { Panic("size_in_bytes cannot be negative"); }
memcpy(dst, src, size_in_bytes); // Provided by Preload memcpy(dst, src, size_in_bytes); // Provided by Preload
} }
@ -27,7 +21,7 @@ MemCopy :: (dst: *void, src: *void, size_in_bytes: int) {
/// Note: MemOverwrite will panic if size_in_bytes is negative. /// Note: MemOverwrite will panic if size_in_bytes is negative.
MemOverwrite :: (p: *void, size_in_bytes: int, value: u8 = 0) { MemOverwrite :: (p: *void, size_in_bytes: int, value: u8 = 0) {
if size_in_bytes < 0 if size_in_bytes < 0
{ Panic("jc: size_in_bytes cannot be negative"); } { Panic("size_in_bytes cannot be negative"); }
memset(p, value, size_in_bytes); // Provided by preload memset(p, value, size_in_bytes); // Provided by preload
} }
@ -59,31 +53,3 @@ MemReset :: (p: *$T) {
inline MemZero(p); 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;
}

View file

@ -25,7 +25,7 @@
/// Expect(value2 > 0, "my_proc returned a negative number!"); /// Expect(value2 > 0, "my_proc returned a negative number!");
/// }); /// });
/// ///
Test :: (name: string, proc: (t: *void) -> (), loc := #caller_location) { Test :: (name: string, proc: (*void) -> (), loc := #caller_location) {
// @note(judah): incredibly dumb way to get nicer test runs // @note(judah): incredibly dumb way to get nicer test runs
path := loc.fully_pathed_filename; path := loc.fully_pathed_filename;
@ -76,8 +76,7 @@ Test :: (name: string, proc: (t: *void) -> (), loc := #caller_location) {
/// Expect checks the given condition, failing the current test if it is false. /// Expect checks the given condition, failing the current test if it is false.
/// ///
/// Note: Expect must be called within a test. Additionally, it expects the test /// Note: Expect must be called within a test.
/// parameter to be called 't'.
Expect :: (cond: bool, message := "", args: ..Any, loc := #caller_location) #expand { Expect :: (cond: bool, message := "", args: ..Any, loc := #caller_location) #expand {
run := `t.(*TestRun); run := `t.(*TestRun);
run.total_expects += 1; run.total_expects += 1;

View file

@ -88,12 +88,12 @@ min :: (x: float, y: float) -> float {
} }
abs :: (v: $T) -> T abs :: (v: $T) -> T
#modify { return type_is_scalar(T), "Only accepting scalar types, integer and float"; } { #modify { return meta.type_is_scalar(T), "Only accepting scalar types, integer and float"; } {
return ifx v < 0 then -v else v; return ifx v < 0 then -v else v;
} }
#scope_file #scope_file
#import "jc";
// sin, cos // sin, cos
meta :: #import "jc/meta";
math :: #import "Math"; math :: #import "Math";

View file

@ -28,7 +28,7 @@ Transition :: enum {
@Note: Progress must be between 0.0 and 1.0 @Note: Progress must be between 0.0 and 1.0
*/ */
ease :: (progress: $T, $$ease: Ease = .linear, $$transition: Transition = .in) -> T ease :: (progress: $T, $$ease: Ease = .linear, $$transition: Transition = .in) -> T
#modify { return type_is_float(T); } #modify { return meta.type_is_float(T); }
{ {
p := progress; p := progress;
@ -85,6 +85,8 @@ ease :: (progress: $T, $$ease: Ease = .linear, $$transition: Transition = .in) -
#scope_file; #scope_file;
#import "jc";
meta :: #import "jc/meta";
math :: #import "Math"; // @future math :: #import "Math"; // @future
basic :: #import "Basic"; // @future basic :: #import "Basic"; // @future

View file

@ -404,43 +404,45 @@ inverse :: (m: Mat4) -> Mat4 {
#scope_file; #scope_file;
#if RUN_TESTS #run { #if #exists(RUN_TESTS) #run {
Test(basic.tprint("%: Mat2", UNITS), t => { test :: #import "jc/meta/test";
test.run(basic.tprint("%: Mat2", UNITS), t => {
{ {
identity := m2_identity; identity := m2_identity;
a: Mat2 = Mat2.{.[1, 2, 3, 4]}; a: Mat2 = Mat2.{.[1, 2, 3, 4]};
Expect(a*identity == a); test.expect(t, a*identity == a);
} }
{ {
rotator := rotation_mat2(from_rad(PI)); rotator := rotation_mat2(from_rad(PI));
v2 := v2f(1.0, 0.5); v2 := v2f(1.0, 0.5);
v2 = rotator*v2; v2 = rotator*v2;
Expect(v2_eq(v2, v2f(-1.0, -0.5))); test.expect(t, v2_eq(v2, v2f(-1.0, -0.5)));
v2 = rotator*v2; v2 = rotator*v2;
Expect(v2_eq(v2, v2f(1.0, 0.5))); test.expect(t, v2_eq(v2, v2f(1.0, 0.5)));
} }
{ {
m := m2(1.0, 3.0, 2.0, 2.0); m := m2(1.0, 3.0, 2.0, 2.0);
det := determinate(m); det := determinate(m);
Expect(det == -4); test.expect(t, det == -4);
} }
{ {
rotator := rotation_mat2(from_rad(PI)); rotator := rotation_mat2(from_rad(PI));
inv_rotator := inverse(rotator); inv_rotator := inverse(rotator);
v2 := v2f(0.1, 1.0); v2 := v2f(0.1, 1.0);
Expect(inv_rotator*rotator == m2_identity); test.expect(t, inv_rotator*rotator == m2_identity);
Expect(inv_rotator*(rotator*v2) == v2); test.expect(t, inv_rotator*(rotator*v2) == v2);
v2 = rotator*v2; v2 = rotator*v2;
v2 = inv_rotator*v2; v2 = inv_rotator*v2;
} }
}); });
Test(basic.tprint("%: Mat4", UNITS), t => { test.run(basic.tprint("%: Mat4", UNITS), t => {
{ {
identity := m4_identity; identity := m4_identity;
a: Mat4 = Mat4.{.[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]}; a: Mat4 = Mat4.{.[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]};
Expect(a*identity == a); test.expect(t, a*identity == a);
} }
{ {
UP_VECTOR :: Vec3.{.[0.0, 1.0, 0.0]}; UP_VECTOR :: Vec3.{.[0.0, 1.0, 0.0]};
@ -452,16 +454,16 @@ inverse :: (m: Mat4) -> Mat4 {
translator := translate(v3f(10.0, 5.0, 2.0)); translator := translate(v3f(10.0, 5.0, 2.0));
v3 := v3f(1.0, 2.0, 1.0); v3 := v3f(1.0, 2.0, 1.0);
v4 := v4f(v3, 1.0); v4 := v4f(v3, 1.0);
Expect(v4 == v4f(1.0, 2.0, 1.0, 1.0)); test.expect(t, v4 == v4f(1.0, 2.0, 1.0, 1.0));
Expect(translator*v4 == v4f(11.0, 7.0, 3.0, 1.0)); test.expect(t, translator*v4 == v4f(11.0, 7.0, 3.0, 1.0));
} }
{ {
rotator := rotation_mat4(v3f(0.0, 1.0, 0.0), from_rad(PI)); rotator := rotation_mat4(v3f(0.0, 1.0, 0.0), from_rad(PI));
v4 := v4f(1.0, 0.5, 0.1, 1.0); v4 := v4f(1.0, 0.5, 0.1, 1.0);
v4 = rotator*v4; v4 = rotator*v4;
Expect(v4_eq(v4, v4f(-1.0, 0.5, -0.1, 1.0))); test.expect(t, v4_eq(v4, v4f(-1.0, 0.5, -0.1, 1.0)));
v4 = rotator*v4; v4 = rotator*v4;
Expect(v4_eq(v4, v4f(1.0, 0.5, 0.1, 1.0))); test.expect(t, v4_eq(v4, v4f(1.0, 0.5, 0.1, 1.0)));
} }
{ {
rotator_x := rotation_mat4(v3f(1.0, 0.0, 0.0), from_rad(0.4*PI)); rotator_x := rotation_mat4(v3f(1.0, 0.0, 0.0), from_rad(0.4*PI));
@ -474,7 +476,7 @@ inverse :: (m: Mat4) -> Mat4 {
inv_rotator := inverse(rotator); inv_rotator := inverse(rotator);
v4 := v4f(0.1, 1.0, 0.0, 1.0); v4 := v4f(0.1, 1.0, 0.0, 1.0);
Expect(inv_rotator*rotator == m4_identity); test.expect(t, inv_rotator*rotator == m4_identity);
v4 = rotator*v4; v4 = rotator*v4;
v4 = inv_rotator*v4; v4 = inv_rotator*v4;

View file

@ -5,7 +5,22 @@
RUN_TESTS := false RUN_TESTS := false
); );
#assert type_is_scalar(RECT_TYPE); #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);
#load "common.jai"; #load "common.jai";
#load "vec.jai"; #load "vec.jai";
@ -15,8 +30,7 @@
#scope_module; #scope_module;
#import "jc"; meta :: #import "jc/meta";
math :: #import "Math"; // @future math :: #import "Math"; // @future
basic :: #import "Basic"; // @future basic :: #import "Basic"; // @future

View file

@ -103,17 +103,17 @@ Vec :: struct(N: int, T: Type)
} }
operator [] :: (v: Vec, $$idx: int) -> v.T #no_abc { operator [] :: (v: Vec, $$idx: int) -> v.T #no_abc {
CheckBounds(idx, v.N); meta.check_bounds(idx, v.N);
return v.components[idx]; return v.components[idx];
} }
operator *[] :: (v: *Vec, $$idx: int) -> *v.T #no_abc { operator *[] :: (v: *Vec, $$idx: int) -> *v.T #no_abc {
CheckBounds(idx, v.N); meta.check_bounds(idx, v.N);
return *v.components[idx]; return *v.components[idx];
} }
operator []= :: (v: *Vec, $$idx: int, value: v.T) #no_abc { operator []= :: (v: *Vec, $$idx: int, value: v.T) #no_abc {
CheckBounds(idx, v.N); meta.check_bounds(idx, v.N);
v.components[idx] = value; v.components[idx] = value;
} }
@ -187,7 +187,7 @@ operator / :: inline (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc {
} }
operator + :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc #symmetric operator + :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc #symmetric
#modify { return type_is_scalar(R), "type is not integer or float"; } { #modify { return meta.type_is_scalar(R), "type is not integer or float"; } {
res: Vec(l.N, l.T) = ---; res: Vec(l.N, l.T) = ---;
#if l.N <= 4 { #if l.N <= 4 {
res.x = l.x + r; res.x = l.x + r;
@ -202,7 +202,7 @@ operator + :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc #symmetric
} }
operator - :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc operator - :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc
#modify { return type_is_scalar(R), "type is not integer or float"; } { #modify { return meta.type_is_scalar(R), "type is not integer or float"; } {
res: Vec(l.N, l.T) = ---; res: Vec(l.N, l.T) = ---;
#if l.N <= 4 { #if l.N <= 4 {
res.x = l.x - r; res.x = l.x - r;
@ -216,7 +216,7 @@ operator - :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc
return res; return res;
} }
operator - :: inline (l: $R, r: Vec) -> Vec(l.N, l.T) #no_abc operator - :: inline (l: $R, r: Vec) -> Vec(l.N, l.T) #no_abc
#modify { return type_is_scalar(R), "type is not integer or float"; } { #modify { return meta.type_is_scalar(R), "type is not integer or float"; } {
res: Vec(l.N, l.T) = ---; res: Vec(l.N, l.T) = ---;
#if l.N <= 4 { #if l.N <= 4 {
res.x = l - r.x; res.x = l - r.x;
@ -245,7 +245,7 @@ operator- :: inline(v: Vec) -> Vec(v.N, v.T) #no_abc {
} }
operator * :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc #symmetric operator * :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc #symmetric
#modify { return type_is_scalar(R), "type is not integer or float"; } { #modify { return meta.type_is_scalar(R), "type is not integer or float"; } {
res: Vec(l.N, l.T) = ---; res: Vec(l.N, l.T) = ---;
#if l.N <= 4 { #if l.N <= 4 {
res.x = l.x*r; res.x = l.x*r;
@ -260,7 +260,7 @@ operator * :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc #symmetric
} }
operator / :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc operator / :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc
#modify { return type_is_scalar(R), "type is not integer or float"; } { #modify { return meta.type_is_scalar(R), "type is not integer or float"; } {
res: Vec(l.N, l.T) = ---; res: Vec(l.N, l.T) = ---;
#if l.N <= 4 { #if l.N <= 4 {
res.x = l.x/r; res.x = l.x/r;
@ -486,7 +486,7 @@ reflect :: (v: Vec2, p: Vec2) -> Vec2 #no_abc {
} }
round :: (v: Vec($N, $T)) -> Vec(N, T) #no_abc round :: (v: Vec($N, $T)) -> Vec(N, T) #no_abc
#modify { return type_is_float(T), "Used non-float vector on round"; } { #modify { return meta.type_is_float(T), "Used non-float vector on round"; } {
r: Vec(N, T) = ---; r: Vec(N, T) = ---;
n := N - 1; n := N - 1;
while n >= 0 { while n >= 0 {
@ -507,31 +507,31 @@ Vec4 :: Vec(4, float);
Quat :: #type,distinct Vec4; // Note(Jesse): I Had to make this distinct, otherwise operators stomp on eachother Quat :: #type,distinct Vec4; // Note(Jesse): I Had to make this distinct, otherwise operators stomp on eachother
v2f :: (x: $T = 0, y: T = 0) -> Vec2 v2f :: (x: $T = 0, y: T = 0) -> Vec2
#modify { return type_is_float(T), "use v2i for integer arguments"; } #modify { return meta.type_is_float(T), "use v2i for integer arguments"; }
#expand { #expand {
return .{ x = x, y = y }; return .{ x = x, y = y };
} }
v2i :: (x: $T = 0, y: T = 0) -> Vec(2, T) v2i :: (x: $T = 0, y: T = 0) -> Vec(2, T)
#modify { return type_is_integer(T), "use v2f for float arguments"; } #modify { return meta.type_is_integer(T), "use v2f for float arguments"; }
#expand { #expand {
return .{ x = x, y = y }; return .{ x = x, y = y };
} }
v3f :: (x: $T = 0, y: T = 0, z: T = 0) -> Vec3 v3f :: (x: $T = 0, y: T = 0, z: T = 0) -> Vec3
#modify { return type_is_float(T), "use v3i for integer arguments"; } #modify { return meta.type_is_float(T), "use v3i for integer arguments"; }
#expand { #expand {
return .{ x = x, y = y, z = z }; return .{ x = x, y = y, z = z };
} }
v3i :: (x: $T = 0, y: T = 0, z: T = 0) -> Vec(3, T) v3i :: (x: $T = 0, y: T = 0, z: T = 0) -> Vec(3, T)
#modify { return type_is_integer(T), "use v3f for float arguments"; } #modify { return meta.type_is_integer(T), "use v3f for float arguments"; }
#expand { #expand {
return .{ x = x, y = y, z = z }; return .{ x = x, y = y, z = z };
} }
v4f :: (x: $T = 0, y: T = 0, z: T = 0, w: T = 0) -> Vec4 v4f :: (x: $T = 0, y: T = 0, z: T = 0, w: T = 0) -> Vec4
#modify { return type_is_float(T), "use v4i for integer arguments"; } #modify { return meta.type_is_float(T), "use v4i for integer arguments"; }
#expand { #expand {
return .{ x = x, y = y, z = z, w = w }; return .{ x = x, y = y, z = z, w = w };
} }
@ -541,7 +541,7 @@ v4f :: (v: Vec3, $$w: float) -> Vec4 #expand {
} }
v4i :: (x: $T = 0, y: T = 0, z: T = 0, w: T = 0) -> Vec(4, T) v4i :: (x: $T = 0, y: T = 0, z: T = 0, w: T = 0) -> Vec(4, T)
#modify { return type_is_integer(T), "use v4f for float arguments"; } #modify { return meta.type_is_integer(T), "use v4f for float arguments"; }
#expand { #expand {
return .{ x = x, y = y, z = z, w = w }; return .{ x = x, y = y, z = z, w = w };
} }
@ -654,74 +654,76 @@ cross :: (a: Vec3, b: Vec3) -> Vec3 {
#scope_file; #scope_file;
#if RUN_TESTS #run,stallable { #if RUN_TESTS #run,stallable {
Test(basic.tprint("%: Vec2", UNITS), t => { test :: #import "jc/meta/test";
test.run(basic.tprint("%: Vec2", UNITS), t => {
{ {
a: Vec2 = v2f(0.0, 1.0); a: Vec2 = v2f(0.0, 1.0);
b: Vec2 = v2f(1.0, 2.0); b: Vec2 = v2f(1.0, 2.0);
Expect(a + b == v2f(1.0, 3.0)); test.expect(t, a + b == v2f(1.0, 3.0));
Expect(b - a == v2f(1.0, 1.0)); test.expect(t, b - a == v2f(1.0, 1.0));
Expect(a*b == v2f(0.0, 2.0)); test.expect(t, a*b == v2f(0.0, 2.0));
Expect(a/b == v2f(0.0, 0.5)); test.expect(t, a/b == v2f(0.0, 0.5));
} }
{ {
a: Vec(2, int) = v2i(2, 1); a: Vec(2, int) = v2i(2, 1);
b: Vec(2, int) = v2i(1, 0); b: Vec(2, int) = v2i(1, 0);
Expect(a + b == v2i(3, 1)); test.expect(t, a + b == v2i(3, 1));
Expect(b - a == v2i(-1, -1)); test.expect(t, b - a == v2i(-1, -1));
Expect(a*b == v2i(2, 0)); test.expect(t, a*b == v2i(2, 0));
Expect(b/a == v2i(0, 0)); test.expect(t, b/a == v2i(0, 0));
} }
{ {
a: Vec2 = v2f(2.3, -4.1); a: Vec2 = v2f(2.3, -4.1);
b: Vec2 = v2f(1.0, 3.6); b: Vec2 = v2f(1.0, 3.6);
c := min(a, b); c := min(a, b);
Expect(c == v2f(1.0, -4.1)); test.expect(t, c == v2f(1.0, -4.1));
c = max(a, b); c = max(a, b);
Expect(c == v2f(2.3, 3.6)); test.expect(t, c == v2f(2.3, 3.6));
} }
}); });
Test(basic.tprint("%: Vec3", UNITS), t => { test.run(basic.tprint("%: Vec3", UNITS), t => {
{ {
a: Vec3 = v3f(0.0, 1.0, 2.0); a: Vec3 = v3f(0.0, 1.0, 2.0);
b: Vec3 = v3f(1.0, 2.0, 3.0); b: Vec3 = v3f(1.0, 2.0, 3.0);
Expect(a + b == v3f(1.0, 3.0, 5.0)); test.expect(t, a + b == v3f(1.0, 3.0, 5.0));
Expect(b - a == v3f(1.0, 1.0, 1.0)); test.expect(t, b - a == v3f(1.0, 1.0, 1.0));
Expect(a*b == v3f(0.0, 2.0, 6.0)); test.expect(t, a*b == v3f(0.0, 2.0, 6.0));
Expect(a/b == v3f(0.0, 0.5, 0.66666667)); test.expect(t, a/b == v3f(0.0, 0.5, 0.66666667));
a = v3f(1.0, 1.0, 0.0); a = v3f(1.0, 1.0, 0.0);
b = v3f(1.0, 0.0, 0.0); b = v3f(1.0, 0.0, 0.0);
Expect(reflect(a, b) == v3f(1.0, -1.0, 0.0)); test.expect(t, 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)); test.expect(t, round(v3f(1.2, 1.7, 1.5)) == v3f(1.0, 2.0, 2.0));
Expect(round(v3f(-1.2, -1.7, -1.5)) == v3f(-1.0, -2.0, -2.0)); test.expect(t, round(v3f(-1.2, -1.7, -1.5)) == v3f(-1.0, -2.0, -2.0));
a = v3f(1.0, 0.0, 0.0); a = v3f(1.0, 0.0, 0.0);
b = v3f(0.0, 1.0, 0.0); b = v3f(0.0, 1.0, 0.0);
Expect(cross(a, b) == v3f(0.0, 0.0, 1.0)); test.expect(t, cross(a, b) == v3f(0.0, 0.0, 1.0));
} }
{ {
a: Vec3 = v3f(2.3, 4.1, 9.0); a: Vec3 = v3f(2.3, 4.1, 9.0);
b: Vec3 = v3f(1.0, -3.6, 5.0); b: Vec3 = v3f(1.0, -3.6, 5.0);
c := min(a, b); c := min(a, b);
Expect(c == v3f(1.0, -3.6, 5.0)); test.expect(t, c == v3f(1.0, -3.6, 5.0));
c = max(a, b); c = max(a, b);
Expect(c == v3f(2.3, 4.1, 9.0)); test.expect(t, c == v3f(2.3, 4.1, 9.0));
} }
}); });
Test(basic.tprint("%: Vec4", UNITS), t => { test.run(basic.tprint("%: Vec4", UNITS), t => {
a: Vec4 = v4f(2.25, 1.0, 2.0, 1.0); a: Vec4 = v4f(2.25, 1.0, 2.0, 1.0);
b: Vec4 = v4f(4.0, 2.0, 3.0, 1.0); b: Vec4 = v4f(4.0, 2.0, 3.0, 1.0);
Expect(a + b == v4f(6.25, 3.0, 5.0, 2.0)); test.expect(t, a + b == v4f(6.25, 3.0, 5.0, 2.0));
Expect(b - a == v4f(1.75, 1.0, 1.0, 0.0)); test.expect(t, b - a == v4f(1.75, 1.0, 1.0, 0.0));
Expect(a*b == v4f(9.0, 2.0, 6.0, 1.0)); test.expect(t, 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.expect(t, a/b == v4f(0.5625, 0.5, 2.0/3.0, 1.0));
}); });
Test(basic.tprint("%: VecN", UNITS), t => { test.run(basic.tprint("%: VecN", UNITS), t => {
a: Vec(16, float); a: Vec(16, float);
b: Vec(16, float); b: Vec(16, float);
for *a { for *a {
@ -730,59 +732,59 @@ cross :: (a: Vec3, b: Vec3) -> Vec3 {
for *b { for *b {
it.* = xx(it_index + 1); it.* = xx(it_index + 1);
} }
Expect(a + b == Vec(16, float).{.[1.0, 3.0, 5.0, 7.0, 9.0, 11.0, 13.0, 15.0, 17.0, 19.0, 21.0, 23.0, 25.0, 27.0, 29.0, 31.0]}); test.expect(t, 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]}); test.expect(t, b - a == Vec(16, float).{.[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]});
Expect(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, 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(min(a, b) == a); test.expect(t, min(a, b) == a);
Expect(max(a, b) == b); test.expect(t, 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]}); test.expect(t, max(a, 12) == Vec(16, float).{.[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 12.0, 12.0, 12.0]});
Expect(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, 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]}); 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]});
a1: Vec(16, float) = Vec(16, float).{.[1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 4.7, -1.2, -1.0, -1.5, 11.2, 14.0, 15.0, 14.0, 15.0, 65536.2]}; a1: Vec(16, float) = Vec(16, float).{.[1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 4.7, -1.2, -1.0, -1.5, 11.2, 14.0, 15.0, 14.0, 15.0, 65536.2]};
Expect(ceil(a1) == Vec(16, float).{.[2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 5.0, -1.0, -1.0, -1.0, 12.0, 14.0, 15.0, 14.0, 15.0, 65537]}); test.expect(t, 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, 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(dot(a, b) == 1360.0); test.expect(t, dot(a, b) == 1360.0);
Expect(abs(a) == a); test.expect(t, abs(a) == a);
c := a; c := a;
for *c { // Check making every other component negative for *c { // Check making every other component negative
if it_index%2 == 0 then it.* = -it.*; if it_index%2 == 0 then it.* = -it.*;
} }
Expect(abs(c) == a); test.expect(t, abs(c) == a);
Expect(float_eq(length(normalize(a)), 1)); test.expect(t, 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]}); test.expect(t, a + 2 == Vec(16, float).{.[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]});
Expect(a - 2 == Vec(16, float).{.[-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]}); test.expect(t, a - 2 == Vec(16, float).{.[-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]});
Expect(a*2 == Vec(16, float).{.[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]}); test.expect(t, a*2 == Vec(16, float).{.[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]});
}); });
Test(basic.tprint("%: Quat", UNITS), t => { test.run(basic.tprint("%: Quat", UNITS), t => {
qi := quat_identity; qi := quat_identity;
q: Quat = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0)); q: Quat = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0));
q2: Quat = rotation_quat(from_rad(PI), v3f(1.0, 0.0, 1.0)); q2: Quat = rotation_quat(from_rad(PI), v3f(1.0, 0.0, 1.0));
qc := conjugate(q); qc := conjugate(q);
inv_q := inverse(q); inv_q := inverse(q);
Expect(q*inv_q == qi); test.expect(t, q*inv_q == qi);
q1 := quat(2, 0, 0, 0); q1 := quat(2, 0, 0, 0);
q2 = quat(1, 1, -1, 0); q2 = quat(1, 1, -1, 0);
c := q1*q2*conjugate(q1); c := q1*q2*conjugate(q1);
Expect(float_eq(c.w, 0.0)); test.expect(t, float_eq(c.w, 0.0));
q = rotation_quat(from_rad(PI/4.0), v3f(0.0, 0.0, 1.0)); q = rotation_quat(from_rad(PI/4.0), v3f(0.0, 0.0, 1.0));
p := v3f(2.0, 0.0, 0.0); p := v3f(2.0, 0.0, 0.0);
c1 := q*quat(p, 0)*conjugate(q); c1 := q*quat(p, 0)*conjugate(q);
c2 := q*p; c2 := q*p;
Expect(v3_eq(c2, v3f(math.sqrt(2.0), math.sqrt(2.0), 0.0))); test.expect(t, v3_eq(c2, v3f(math.sqrt(2.0), math.sqrt(2.0), 0.0)));
Expect(v3_eq(c1.xyz, c2)); test.expect(t, v3_eq(c1.xyz, c2));
q = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0)); q = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0));
m := rotation_mat4(q); m := rotation_mat4(q);
p1 := v4f(2.0, 0.0, 0.0, 1.0); p1 := v4f(2.0, 0.0, 0.0, 1.0);
Expect(v4_eq(m*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)));
q1 = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0)); q1 = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0));
q2 = rotation_quat(from_rad(2.0*PI), v3f(0.0, 1.0, 0.0)); q2 = rotation_quat(from_rad(2.0*PI), v3f(0.0, 1.0, 0.0));

139
memory/allocators.jai Normal file
View file

@ -0,0 +1,139 @@
#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);
});
}

51
memory/buffer.jai Normal file
View file

@ -0,0 +1,51 @@
#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";
}

223
memory/module.jai Normal file
View file

@ -0,0 +1,223 @@
#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);
});
}

287
meta/macros.jai Normal file
View file

@ -0,0 +1,287 @@
#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);
});
}

94
meta/module.jai Normal file
View file

@ -0,0 +1,94 @@
#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");
});
}

89
meta/test/module.jai Normal file
View file

@ -0,0 +1,89 @@
/*
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

244
meta/type_info.jai Normal file
View file

@ -0,0 +1,244 @@
#module_parameters(RUN_TESTS := false);
// These return the bool first so you can check in a conditional,
// rather than having to do '_, ok := ...'
check_type_tag :: ($$T: Type, tag: Type_Info_Tag) -> bool, *Type_Info {
#if is_constant(T) {
info :: type_info(T);
if info.type == tag return true, info;
}
else {
info := T.(*Type_Info);
if info.type == tag return true, info;
}
return false, null;
}
type_is_integer :: ($$T: Type) -> bool, *Type_Info_Integer {
ok, info := check_type_tag(T, .INTEGER);
return ok, info.(*Type_Info_Integer);
}
type_is_float :: ($$T: Type) -> bool, *Type_Info_Float {
ok, info := check_type_tag(T, .FLOAT);
return ok, info.(*Type_Info_Float);
}
type_is_scalar :: (t: Type) -> bool {
return type_is_integer(t) || type_is_float(t);
}
type_is_array :: ($$T: Type) -> bool, *Type_Info_Array {
ok, info := check_type_tag(T, .ARRAY);
return ok, info.(*Type_Info_Array);
}
type_is_struct :: ($$T: Type) -> bool, *Type_Info_Struct {
ok, info := check_type_tag(T, .STRUCT);
return ok, info.(*Type_Info_Struct);
}
type_is_enum :: ($$T: Type) -> bool, *Type_Info_Enum {
ok, info := check_type_tag(T, .ENUM);
return ok, info.(*Type_Info_Enum);
}
// Returns the lowest and highest values T can represent.
// Note: T must be an integer, float, or enum type.
range_for :: ($T: Type, loc := #caller_location) -> (T, T) #expand {
// @note(judah): we need to runs here because jai is weird.
return #run lo_for(T, loc = loc), #run hi_for(T, loc = loc);
}
// Returns the lowest value T can represent.
// Note: T must be an integer, float, or enum type.
lo_for :: ($T: Type, loc := #caller_location) -> T #expand {
return #run -> T {
info := T.(*Type_Info);
if info.type == {
case .INTEGER;
i := info.(*Type_Info_Integer);
if i.runtime_size == {
case 1; return (ifx i.signed then -0x80 else 0).(T, no_check);
case 2; return (ifx i.signed then -0x8000 else 0).(T, no_check);
case 4; return (ifx i.signed then -0x8000_0000 else 0).(T, no_check);
case 8; return (ifx i.signed then -0x8000_0000_0000_0000 else 0).(T, no_check);
case;
compiler.compiler_report("unhandled integer type", loc = loc);
}
case .FLOAT;
if info.runtime_size == {
case 4; return (0h00800000).(T, no_check);
case 8; return (0h00100000_00000000).(T, no_check);
case;
compiler.compiler_report("unhandled float type", loc = loc);
}
case .ENUM;
i := info.(*Type_Info_Enum);
if i.values.count == 0 {
return 0;
}
min: T = i.values[0].(T, no_check);
if i.internal_type.signed {
for i.values if it.(T) < min {
min = it.(T);
}
}
else {
for i.values if it.(T) < min {
min = it.(T);
}
}
return min;
case;
compiler.compiler_report("min requires an enum, integer, or float type", loc = loc);
}
return 0;
};
}
// Returns the highest value T can represent.
// Note: T must be an integer, float, or enum type.
hi_for :: ($T: Type, loc := #caller_location) -> T #expand {
return #run -> T {
info := T.(*Type_Info);
if info.type == {
case .INTEGER;
i := info.(*Type_Info_Integer);
if i.runtime_size == {
case 1; return (ifx i.signed then 0x7f else 0xff).(T, no_check);
case 2; return (ifx i.signed then 0x7fff else 0xffff).(T, no_check);
case 4; return (ifx i.signed then 0x7fff_ffff else 0xffff_ffff).(T, no_check);
case 8; return (ifx i.signed then 0x7fff_ffff_ffff_ffff else 0xffff_ffff_ffff_ffff).(T, no_check);
case;
compiler.compiler_report("unhandled integer type", loc = loc);
}
case .FLOAT;
if info.runtime_size == {
case 4; return (0h7F7FFFFF).(T, no_check);
case 8; return (0h7FEFFFFF_FFFFFFFF).(T, no_check);
case;
compiler.compiler_report("unhandled float type", loc = loc);
}
case .ENUM;
i := info.(*Type_Info_Enum);
if i.values.count == 0 {
return 0;
}
max := i.values[0].(T, no_check);
if i.internal_type.signed {
for i.values if xx it > max {
max = xx it;
}
}
else {
for i.values if xx it > max {
max = xx it;
}
}
return max;
case;
compiler.compiler_report("max requires an enum, integer, or float type", loc = loc);
}
return 0;
};
}
#scope_file;
compiler :: #import "Compiler"; // @future
// ----------------------------------------------------------
// TESTS
// ----------------------------------------------------------
#if RUN_TESTS #run {
test :: #import "jc/meta/test";
test.run("lo_for:primitives", t => {
test.expect(t, lo_for(u8) == 0);
test.expect(t, lo_for(s8) == -128);
test.expect(t, lo_for(u16) == 0);
test.expect(t, lo_for(s16) == -32768);
test.expect(t, lo_for(u32) == 0);
test.expect(t, lo_for(s32) == -2147483648);
test.expect(t, lo_for(u64) == 0);
test.expect(t, lo_for(s64) == -9223372036854775808);
test.expect(t, lo_for(float32) == 0h00800000);
test.expect(t, lo_for(float64) == 0h00100000_00000000);
});
test.run("hi_for:primitives", t => {
test.expect(t, hi_for(u8) == 255);
test.expect(t, hi_for(s8) == 127);
test.expect(t, hi_for(u16) == 65535);
test.expect(t, hi_for(s16) == 32767);
test.expect(t, hi_for(u32) == 4294967295);
test.expect(t, hi_for(s32) == 2147483647);
test.expect(t, hi_for(u64) == 18446744073709551615);
test.expect(t, hi_for(s64) == 9223372036854775807);
test.expect(t, hi_for(float32) == 340282346638528859000000000000000000000.0);
test.expect(t, hi_for(float64) == 179769313486231570900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0);
});
test.run("lo_for/hi_for:enums", t => {
U8_Enum :: enum u8 { lo :: -1; hi :: +1; }
S8_Enum :: enum s8 { lo :: -2; hi :: -1; }
{
test.expect(t, lo_for(U8_Enum) == U8_Enum.lo);
test.expect(t, lo_for(S8_Enum) == S8_Enum.lo);
test.expect(t, hi_for(U8_Enum) == U8_Enum.hi);
test.expect(t, hi_for(S8_Enum) == S8_Enum.hi);
}
U16_Enum :: enum u16 { lo :: -1; hi :: +1; }
S16_Enum :: enum s16 { lo :: -2; hi :: -1; }
{
test.expect(t, lo_for(U16_Enum) == U16_Enum.lo);
test.expect(t, lo_for(S16_Enum) == S16_Enum.lo);
test.expect(t, hi_for(U16_Enum) == U16_Enum.hi);
test.expect(t, hi_for(S16_Enum) == S16_Enum.hi);
}
U32_Enum :: enum u32 { lo :: -1; hi :: +1; }
S32_Enum :: enum s32 { lo :: -2; hi :: -1; }
{
test.expect(t, lo_for(U32_Enum) == U32_Enum.lo);
test.expect(t, lo_for(S32_Enum) == S32_Enum.lo);
test.expect(t, hi_for(U32_Enum) == U32_Enum.hi);
test.expect(t, hi_for(S32_Enum) == S32_Enum.hi);
}
U64_Enum :: enum u64 { lo :: -1; hi :: +1; }
S64_Enum :: enum s64 { lo :: -2; hi :: -1; }
{
test.expect(t, lo_for(U64_Enum) == U64_Enum.lo);
test.expect(t, lo_for(S64_Enum) == S64_Enum.lo);
test.expect(t, hi_for(U64_Enum) == U64_Enum.hi);
test.expect(t, hi_for(S64_Enum) == S64_Enum.hi);
}
// @note(judah): just making sure this compiles
lo, hi := range_for(U64_Enum);
test.expect(t, lo == U64_Enum.lo);
test.expect(t, hi == U64_Enum.hi);
});
}

View file

@ -1,21 +1,15 @@
/// Module jc contains procedures for working with memory, /*
/// arrays, and strings; as well as helpful macros and Module jc contains procedures for working with memory,
/// constants. 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);
// @note(judah): this causes some very weird issues in tests so it's ifdef'd out Additionally, it provides a platform-independant
#if !RunTests #add_context temp_allocator: Allocator; interface for interacting with the target operating
system.
*/
#load "+internal/builtin.jai"; #load "internal/builtin.jai";
#load "+internal/array.jai"; #load "internal/array.jai";
#load "+internal/kv.jai"; #load "internal/memory.jai";
#load "+internal/hashing.jai"; #load "internal/testing.jai";
#load "+internal/memory.jai"; #load "internal/keywords.jai";
#load "+internal/allocators.jai";
#load "+internal/testing.jai";
#load "+internal/keywords.jai";
#load "+internal/type_info.jai";

107
platform/arch.jai Normal file
View file

@ -0,0 +1,107 @@
#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

9
platform/module.jai Normal file
View file

@ -0,0 +1,9 @@
#module_parameters(RUN_TESTS := false, WITH_SUBMODULES := true);
#scope_export;
#if WITH_SUBMODULES {
using #import "jc/platform/arch"(RUN_TESTS);
}
#scope_module;