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

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

View file

@ -18,19 +18,14 @@ UseLocalLinks :: false; // set to false when deploying the docs
// Add freestanding modules here.
// Other modules will be automatically generated if they're imported by someone else.
_ :: #import "jc";
_ :: #import "jc/math";
_ :: #import "jc/fmt/base64";
jc :: #import "jc";
hmm :: #import "jc/ext/hmm";
luajit :: #import "jc/ext/luajit";
raylib :: #import "jc/ext/raylib";
remotery :: #import "jc/ext/remotery";
_ :: #import "jc/x/json";
_ :: #import "jc/ext/hmm";
_ :: #import "jc/ext/luajit";
_ :: #import "jc/ext/raylib";
_ :: #import "jc/ext/remotery";
// _ :: #import "jc/ext/darwin";
// _ :: #import "jc/ext/objc";
// _ :: #import "jc/ext/win32";
// darwin :: #import "jc/ext/darwin";
// objc :: #import "jc/ext/objc";
END, ws);
CheckImport :: (import: *Code_Directive_Import) -> bool {
@ -108,12 +103,6 @@ UseLocalLinks :: false; // set to false when deploying the docs
array = *mod.macros;
}
// there should really be a flag on the proc header...
first := to_lower(decl.name[0]);
if !(first >= #char "a" && first <= #char "z") {
decl.name = tprint("operator%", decl.name);
}
array_add(array, .{
decl = decl,
node = header,
@ -125,9 +114,6 @@ UseLocalLinks :: false; // set to false when deploying the docs
if !(decl.flags & .IS_CONSTANT)
{ continue; }
if decl.name == "RunTests" || decl.name == "RUN_TESTS"
{ continue; }
array := *mod.consts;
// Things we don't want to be constant decls
@ -202,49 +188,20 @@ UseLocalLinks :: false; // set to false when deploying the docs
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
<div class='index'>
</ul>
</details>
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
<p>
<strong>What:</strong><br/>
@ -278,8 +235,8 @@ git clone https://git.brut.systems/judah/jc.git
# Indirect installation
git clone https://git.brut.systems/judah/jc.git
ln -s "$(pwd)jc" [jai install dir]/modules/jc # POSIX install
mklink /D "C:\path\to\jc" [jai install dir]\jc # Windows 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
# Usage:
#import "jc";
@ -299,9 +256,9 @@ mklink /D "C:\path\to\jc" [jai install dir]\jc # Windows install
source_files: Table(string, []string);
GetSourceLines :: (filename: string) -> []string, bool {
old_logger := context.logger;
context.logger = (message: string, data: *void, info: Log_Info) {}; // do nothing logger
defer context.logger = old_logger;
// old_logger := context.logger;
// context.logger = (message: string, data: *void, info: Log_Info) {}; // do nothing logger
// defer context.logger = old_logger;
fok, src := table_find_new(*source_files, filename);
if fok {
@ -309,15 +266,12 @@ GetSourceLines :: (filename: string) -> []string, bool {
}
lines: []string;
data, rok := read_entire_file(filename);
lines: []string;
if rok {
lines = split(data, "\n");
table_add(*source_files, filename, lines);
}
else {
print("warn: file didn't exist '%'\n", filename);
}
return lines, rok;
}
@ -420,13 +374,6 @@ ModuleToHtml :: (mod: *Module) -> string {
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, "<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);
PrintIndexFor :: (b: *String_Builder, name: string, decls: []Decl) {
@ -782,10 +729,6 @@ CleanJaiSource :: (node: *Code_Node) -> string {
postfix = tprint("% #compile_time", postfix);
}
if header.procedure_flags & .SYMMETRIC {
postfix = tprint("% #symmetric", postfix);
}
return tprint("%1%2", trim(tail), postfix);
}
// for everything else, get the source range for the node and only strip out
@ -848,21 +791,18 @@ CleanJaiSource :: (node: *Code_Node) -> string {
source := join(..cleaned_lines, "\n");
index := find_index_from_left(source, "::");
// @todo(judah): handle module parameters correctly
// This is like 95% a module parameter
if index == -1 {
#import "Program_Print";
b: String_Builder;
is_module_param := true;
if decl.expression != null {
print_expression(*b, decl.expression);
}
else if decl.type_inst {
print_expression(*b, decl.type_inst);
is_module_param = false;
}
append(*b, ";");
append(*b, "; // #module_parameter");
return builder_to_string(*b);
}
@ -999,15 +939,12 @@ MainCss :: #string END
}
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;
color: var(--text-primary);
background-color: var(--bg-primary);
margin: 0;
padding: 0;
font-feature-settings: 'kern' 1, 'liga' 1, 'calt' 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1 {
@ -1029,7 +966,7 @@ h4 {
font-size: 1.1rem;
font-weight: 600;
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 {
@ -1108,7 +1045,7 @@ h4 a:hover {
.index a {
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 {
@ -1136,12 +1073,9 @@ pre.code {
padding: 1rem;
margin: 1rem 0;
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;
line-height: 1.4;
font-feature-settings: 'liga' 1, 'calt' 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
span.ident {
@ -1217,23 +1151,6 @@ main {
font-size: 1.6rem;
margin: 1.5rem 0 0.75rem 0;
}
h4 {
font-size: 1rem;
font-family: 'Menlo', 'Monaco', Consolas, 'Courier New', monospace;
}
.index a {
font-family: 'Menlo', 'Monaco', Consolas, 'Courier New', monospace;
}
pre.code {
font-family: 'Menlo', 'Monaco', Consolas, 'Courier New', monospace;
font-size: 0.85rem;
padding: 0.75rem;
-webkit-text-size-adjust: 100%;
text-rendering: optimizeLegibility;
}
}
a:focus,
@ -1279,17 +1196,6 @@ html {
outline: 2px solid var(--accent-color);
outline-offset: 2px;
}
span.module-warning {
background-color: var(--bg-primary);
color: var(--accent-color);
display: block;
border-radius: 6px;
font-weight: bold;
border: 1px solid var(--accent-color);
border-radius: 6px;
padding: 0.5rem;
}
END;
ResetCss :: #string END
@ -1369,7 +1275,7 @@ textarea:not([rows]) {
END;
#import "jc";
#load "./module.jai";
#import "String";
#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 {
compiler :: #import "Compiler";
compiler.set_build_options_dc(.{ do_output = false });
_ :: #import "jc"(true);
_ :: #import "jc/math"(RUN_TESTS = true);
_ :: #import "jc/fmt/base64"(true);
_ :: #import "jc/x/json"(true);
#load "module.jai";
}

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 {
enc, ok := Base64Encode(str.([]u8), for_url);
base64_encode :: (str: string, $for_url := false) -> string, bool {
enc, ok := base64_encode(str.([]u8), for_url);
return enc.(string), ok;
}
Base64Decode :: (str: string) -> string, bool {
enc, ok := Base64Decode(str.([]u8));
base64_decode :: (str: string) -> string, bool {
enc, ok := base64_decode(str.([]u8));
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 for_url {
@ -21,7 +21,7 @@ Base64Encode :: ($$data: []u8, $for_url := false) -> []u8, bool {
}
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;
dst_idx := 0;
@ -70,7 +70,7 @@ Base64Encode :: ($$data: []u8, $for_url := false) -> []u8, bool {
return encoded, true;
}
Base64Decode :: (data: []u8) -> []u8, bool {
base64_decode :: (data: []u8) -> []u8, bool {
if !data.count return .[], false;
lookup :: (c: u8) -> u8 #expand {
@ -144,20 +144,17 @@ Base64Decode :: (data: []u8) -> []u8, bool {
return decoded, true;
}
#scope_file
#scope_file;
Padding_Character :: #char "=";
Encoding_Url :: "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;
return (count * 8 + 5) / 6;
}
#import "jc";
basic :: #import "Basic"; // @future
strings :: #import "String"; // @future
@ -166,45 +163,47 @@ strings :: #import "String"; // @future
// TESTS
// ----------------------------------------------------------
#if RunTests #run {
Test("encodes", t => {
#if RUN_TESTS #run {
test :: #import "jc/meta/test";
test.run("encodes", (t) => {
str :: "Hello, World";
encoded, ok := Base64Encode(str);
Expect(ok, "'%' did not properly encode!", str);
encoded, ok := base64_encode(str);
test.expect(t, ok, "'%' did not properly encode!", str);
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";
decoded, ok := Base64Decode(encoded_str);
Expect(ok, "'%' did not properly decode!", encoded_str);
decoded, ok := base64_decode(encoded_str);
test.expect(t, ok, "'%' did not properly decode!", encoded_str);
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==";
first_decode, f_ok := Base64Decode(str);
Expect(f_ok, "first decode failed!");
first_decode, f_ok := base64_decode(str);
test.expect(t, f_ok, "first decode failed!");
re_encode, r_ok := Base64Encode(first_decode);
Expect(r_ok, "re-encode failed!");
re_encode, r_ok := base64_encode(first_decode);
test.expect(t, r_ok, "re-encode failed!");
second_decode, s_ok := Base64Decode(re_encode);
Expect(s_ok, "second decode failed!");
second_decode, s_ok := base64_decode(re_encode);
test.expect(t, s_ok, "second decode failed!");
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 {
first: u64;
second: u64;
@ -221,14 +220,14 @@ strings :: #import "String"; // @future
enc_builder: basic.String_Builder;
basic.print_to_builder(*enc_builder, "%,%,%,%", foo.first, foo.second, foo.third, foo.forth);
encoded, e_ok := Base64Encode(basic.builder_to_string(*enc_builder));
Expect(e_ok, "Encode of structure failed!");
encoded, e_ok := base64_encode(basic.builder_to_string(*enc_builder));
test.expect(t, e_ok, "Encode of structure failed!");
decoded, d_ok := Base64Decode(encoded);
Expect(d_ok, "Decode of encoded structure failed!");
decoded, d_ok := base64_decode(encoded);
test.expect(t, d_ok, "Decode of encoded structure failed!");
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 = ---;
@ -238,17 +237,17 @@ strings :: #import "String"; // @future
for info.members {
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.*= value;
}
}
Expect(foo.first == bar.first, "Foo.first (%, %) didn't match between encoding/decoding!", foo.first, bar.first);
Expect(foo.second == bar.second, "Foo.second (%, %) didn't match between encoding/decoding!", foo.second, bar.second);
Expect(foo.third == bar.third, "Foo.third (%, %) didn't match between encoding/decoding!", foo.third, bar.third);
Expect(foo.forth == bar.forth, "Foo.forth (%, %) didn't match between encoding/decoding!", foo.forth, bar.forth);
test.expect(t, foo.first == bar.first, "Foo.first (%, %) didn't match between encoding/decoding!", foo.first, bar.first);
test.expect(t, foo.second == bar.second, "Foo.second (%, %) didn't match between encoding/decoding!", foo.second, bar.second);
test.expect(t, foo.third == bar.third, "Foo.third (%, %) didn't match between encoding/decoding!", foo.third, bar.third);
test.expect(t, foo.forth == bar.forth, "Foo.forth (%, %) didn't match between encoding/decoding!", foo.forth, bar.forth);
});
}

View file

@ -1,4 +1,4 @@
#module_parameters(RunTests := false);
#module_parameters(RUN_TESTS := false);
Json_Value :: struct {
kind: enum {
@ -327,7 +327,7 @@ to_string :: (value: Json_Value, indent := 0) -> string {
}
#scope_file
#scope_file;
get_json_member_name :: (member: Type_Info_Struct_Member) -> string {
name := member.name;
@ -609,3 +609,4 @@ at_end :: inline (p: *Parser) -> bool {
basic :: #import "Basic"; // @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)
// 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;
}
#module_parameters(RUN_TESTS := false);
// Implementation: Demetri Spanos (github.com/demetri/scribbles)
// Jai Port: Jesse Coyle (github.com/Zilarrezko)
XXHashSeed :: 0;
XXHash_Seed :: 0;
XXHash64 :: inline (s: string, seed: u32 = XXHashSeed) -> u64 {
return XXHash64(s.data, s.count, seed);
xxhash64 :: inline (s: string, seed: u32 = XXHash_Seed) -> u64 {
return xxhash64(s.data, s.count, seed);
}
XXHash64 :: inline (x: $T, seed: u32 = XXHashSeed) -> u64 {
return XXHash64(cast(*u8)*x, size_of(T), seed);
xxhash64 :: inline (x: $T, seed: u32 = XXHash_Seed) -> u64 {
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;
p2: u64 : 0xc2b2ae3d27d4eb4f;
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 :: (view: []$T, start_idx: int, count := -1, loc := #caller_location) -> []T {
AssertCallsite(start_idx >= +0 && start_idx < view.count, "jc: incorrect slice bounds");
AssertCallsite(count >= -1 && count < view.count, "jc: incorrect slice length");
AssertCallsite(start_idx >= +0 && start_idx < view.count, "incorrect slice bounds");
AssertCallsite(count >= -1 && count < view.count, "incorrect slice length");
if count == -1
{ count = view.count - start_idx; }
@ -41,14 +9,15 @@ Slice :: (view: []$T, start_idx: int, count := -1, loc := #caller_location) -> [
return .{ data = view.data + start_idx, count = count };
}
/// Reset sets an array's length to 0, but leaves its memory intact.
/// Note: To reset the associated memory as well, see Clear.
/// Reset sets an array's length to 0, allowing it to be reused
/// without allocating new memory.
Reset :: (view: *[]$T) {
view.count = 0;
}
/// Clear zeroes an array's memory and sets its length to 0.
/// Note: To leave the associated memory intact, see Reset.
/// Clear zeroes the memory of an array and sets its length to 0.
///
/// Note: Clear does not free the array's memory.
Clear :: (view: *[]$T) {
MemZero(view.data, view.count * size_of(T));
view.count = 0;
@ -61,18 +30,6 @@ Equal :: (lhs: []$T, rhs: []T) -> bool {
return MemEqual(lhs.data, rhs.data, lhs.count * size_of(T));
}
CheckBounds :: ($$index: $T, $$count: T, loc := #caller_location) #expand {
Message :: "bounds check failed!";
#if is_constant(index) && is_constant(count) {
if index < 0 || index >= count {
CompileError(Message, loc = loc);
}
}
else if index < 0 || index > count {
Panic(Message, loc = loc);
}
}
FindFlags :: enum_flags {
Last; // Return the last matching element.
@ -164,9 +121,7 @@ Trim :: (view: []$T, cutset: []T, $flags: TrimFlags = .FromStart) -> []T {
#scope_file
basic :: #import "Basic"; // @future
#if RunTests #run,stallable {
#if #exists(RunTests) #run,stallable {
Test("slice", t => {
a1 := int.[ 1, 2, 3, 4, 5 ];
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.
///
/// 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 {
WriteStderrLocation(loc);
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.
///
/// 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;
#if DebugBuild {
@ -100,7 +100,7 @@ CompileError :: (message: string, loc := #caller_location) #expand #no_debug #co
compiler_report(message, loc, .ERROR);
}
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
{
/// 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.
if cond || context.handling_assertion_failure return;
@ -140,7 +140,7 @@ DebugTrap :: () #expand #no_debug {
/// 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);
}
}
@ -166,10 +166,6 @@ WriteStderrLocation :: (loc: Source_Code_Location) {
WriteStderrNumber(loc.character_number);
} @jc.nodocs
WriteStderrString :: #bake_arguments write_strings(to_standard_error = true); // Provided by Runtime_Support
WriteStderrNumber :: #bake_arguments write_number(to_standard_error = true); // Provided by Runtime_Support
#scope_file
DebugBuild :: #run -> bool {
@ -179,3 +175,6 @@ DebugBuild :: #run -> bool {
opts := get_build_options();
return opts.emit_debug_info != .NONE;
};
WriteStderrString :: #bake_arguments write_strings(to_standard_error = true); // Provided by Runtime_Support
WriteStderrNumber :: #bake_arguments write_number(to_standard_error = true); // Provided by Runtime_Support

View file

@ -9,7 +9,7 @@ offset_of :: ($T: Type, ident: Code, loc := #caller_location) -> int #expand {
#run (loc: Source_Code_Location) {
info := type_info(T);
if info.type != .STRUCT {
CompileError("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);
@ -35,7 +35,7 @@ offset_of :: (#discard value: $T, ident: Code, loc := #caller_location) -> int #
// I opted against because if you have a *T, you only want offset_of to get an offset
// from that pointer. What would you do with a field offset from **T?
if info.type == .POINTER {
CompileError("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 4; return (ifx i.signed then -0x8000_0000 else 0).(T, no_check);
case 8; return (ifx i.signed then -0x8000_0000_0000_0000 else 0).(T, no_check);
case ; CompileError("jc: unknown integer size", loc = loc);
case ; CompileError("unknown integer size", loc = loc);
}
case .FLOAT;
if info.runtime_size == {
case 4; return (0h0080_0000).(T, no_check);
case 8; return (0h00100000_00000000).(T, no_check);
case ; CompileError("jc: unknown float size", loc = loc);
case ; CompileError("unknown float size", loc = loc);
}
case .ENUM;
@ -126,7 +126,7 @@ min_of :: ($T: Type, loc := #caller_location) -> T #expand {
return min;
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;
@ -147,14 +147,14 @@ max_of :: ($T: Type, loc := #caller_location) -> T #expand {
case 2; return (ifx i.signed then 0x7fff else 0xffff).(T, no_check);
case 4; return (ifx i.signed then 0x7fff_ffff else 0xffff_ffff).(T, no_check);
case 8; return (ifx i.signed then 0x7fff_ffff_ffff_ffff else 0xffff_ffff_ffff_ffff).(T, no_check);
case ; CompileError("jc: unknown integer size", loc = loc);
case ; CompileError("unknown integer size", loc = loc);
}
case .FLOAT;
if info.runtime_size == {
case 4; return (0h7F7FFFFF).(T, no_check);
case 8; return (0h7FEFFFFF_FFFFFFFF).(T, no_check);
case ; CompileError("jc: unknown float size", loc = loc);
case ; CompileError("unknown float size", loc = loc);
}
case .ENUM;
@ -178,7 +178,7 @@ max_of :: ($T: Type, loc := #caller_location) -> T #expand {
return max;
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;
@ -230,49 +230,4 @@ for_expansion :: (v: Sector, code: Code, _: For_Flags) #expand {
Sector :: struct(Name: string = "early") {}
basic :: #import "Basic"; // @future
#if RunTests #run {
Test("min_of/max_of:enums", t => {
U8Enum :: enum u8 { lo :: -1; hi :: +1; }
S8Enum :: enum s8 { lo :: -2; hi :: -1; }
{
Expect(min_of(U8Enum) == U8Enum.lo);
Expect(min_of(S8Enum) == S8Enum.lo);
Expect(max_of(U8Enum) == U8Enum.hi);
Expect(max_of(S8Enum) == S8Enum.hi);
}
U16Enum :: enum u16 { lo :: -1; hi :: +1; }
S16Enum :: enum s16 { lo :: -2; hi :: -1; }
{
Expect(min_of(U16Enum) == U16Enum.lo);
Expect(min_of(S16Enum) == S16Enum.lo);
Expect(max_of(U16Enum) == U16Enum.hi);
Expect(max_of(S16Enum) == S16Enum.hi);
}
U32Enum :: enum u32 { lo :: -1; hi :: +1; }
S32Enum :: enum s32 { lo :: -2; hi :: -1; }
{
Expect(min_of(U32Enum) == U32Enum.lo);
Expect(min_of(S32Enum) == S32Enum.lo);
Expect(max_of(U32Enum) == U32Enum.hi);
Expect(max_of(S32Enum) == S32Enum.hi);
}
U64Enum :: enum u64 { lo :: -1; hi :: +1; }
S64Enum :: enum s64 { lo :: -2; hi :: -1; }
{
Expect(min_of(U64Enum) == U64Enum.lo);
Expect(min_of(S64Enum) == S64Enum.lo);
Expect(max_of(U64Enum) == U64Enum.hi);
Expect(max_of(S64Enum) == S64Enum.hi);
}
// @note(judah): just making sure this compiles
lo, hi := range_of(U64Enum);
Expect(lo == U64Enum.lo);
Expect(hi == U64Enum.hi);
});
}
basic :: #import "Basic";

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.
///
/// Note: MemEqual will panic if size_in_bytes is negative.
MemEqual :: (p1: *void, p2: *void, size_in_bytes: int) -> bool {
if size_in_bytes < 0
{ Panic("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
}
@ -18,7 +12,7 @@ MemEqual :: (p1: *void, p2: *void, size_in_bytes: int) -> bool {
/// Note: MemCopy will panic if size_in_bytes is negative.
MemCopy :: (dst: *void, src: *void, size_in_bytes: int) {
if size_in_bytes < 0
{ Panic("jc: size_in_bytes cannot be negative"); }
{ Panic("size_in_bytes cannot be negative"); }
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.
MemOverwrite :: (p: *void, size_in_bytes: int, value: u8 = 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
}
@ -59,31 +53,3 @@ MemReset :: (p: *$T) {
inline MemZero(p);
}
}
AlignUpwards :: (ptr: int, align: int = DefaultAlign) -> int {
Assert(PowerOfTwo(align), "alignment must be a power of two");
p := ptr;
mod := p & (align - 1);
if mod != 0 then p += align - mod;
return p;
}
PowerOfTwo :: (x: int) -> bool {
if x == 0 return false;
return x & (x - 1) == 0;
}
NextPowerOfTwo :: (x: int) -> int #no_aoc {
Assert(PowerOfTwo(x), "value must be a power of two");
// Bit twiddling hacks next power of two
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x |= x >> 32;
return x + 1;
}

View file

@ -25,7 +25,7 @@
/// 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
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.
///
/// Note: Expect must be called within a test. Additionally, it expects the test
/// parameter to be called 't'.
/// Note: Expect must be called within a test.
Expect :: (cond: bool, message := "", args: ..Any, loc := #caller_location) #expand {
run := `t.(*TestRun);
run.total_expects += 1;

View file

@ -88,12 +88,12 @@ min :: (x: float, y: float) -> float {
}
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;
}
#scope_file
#import "jc";
// sin, cos
meta :: #import "jc/meta";
math :: #import "Math";

View file

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

View file

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

View file

@ -5,7 +5,22 @@
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 "vec.jai";
@ -15,8 +30,7 @@
#scope_module;
#import "jc";
meta :: #import "jc/meta";
math :: #import "Math"; // @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 {
CheckBounds(idx, v.N);
meta.check_bounds(idx, v.N);
return v.components[idx];
}
operator *[] :: (v: *Vec, $$idx: int) -> *v.T #no_abc {
CheckBounds(idx, v.N);
meta.check_bounds(idx, v.N);
return *v.components[idx];
}
operator []= :: (v: *Vec, $$idx: int, value: v.T) #no_abc {
CheckBounds(idx, v.N);
meta.check_bounds(idx, v.N);
v.components[idx] = value;
}
@ -187,7 +187,7 @@ operator / :: inline (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc {
}
operator + :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc #symmetric
#modify { return 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) = ---;
#if l.N <= 4 {
res.x = l.x + r;
@ -202,7 +202,7 @@ operator + :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc #symmetric
}
operator - :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc
#modify { return 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) = ---;
#if l.N <= 4 {
res.x = l.x - r;
@ -216,7 +216,7 @@ operator - :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc
return res;
}
operator - :: inline (l: $R, r: Vec) -> Vec(l.N, l.T) #no_abc
#modify { return 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) = ---;
#if l.N <= 4 {
res.x = l - r.x;
@ -245,7 +245,7 @@ operator- :: inline(v: Vec) -> Vec(v.N, v.T) #no_abc {
}
operator * :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc #symmetric
#modify { return 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) = ---;
#if l.N <= 4 {
res.x = l.x*r;
@ -260,7 +260,7 @@ operator * :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc #symmetric
}
operator / :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc
#modify { return 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) = ---;
#if l.N <= 4 {
res.x = l.x/r;
@ -486,7 +486,7 @@ reflect :: (v: Vec2, p: Vec2) -> Vec2 #no_abc {
}
round :: (v: Vec($N, $T)) -> Vec(N, T) #no_abc
#modify { return 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) = ---;
n := N - 1;
while n >= 0 {
@ -507,31 +507,31 @@ Vec4 :: Vec(4, float);
Quat :: #type,distinct Vec4; // Note(Jesse): I Had to make this distinct, otherwise operators stomp on eachother
v2f :: (x: $T = 0, y: T = 0) -> Vec2
#modify { return type_is_float(T), "use v2i for integer arguments"; }
#modify { return meta.type_is_float(T), "use v2i for integer arguments"; }
#expand {
return .{ x = x, y = y };
}
v2i :: (x: $T = 0, y: T = 0) -> Vec(2, T)
#modify { return type_is_integer(T), "use v2f for float arguments"; }
#modify { return meta.type_is_integer(T), "use v2f for float arguments"; }
#expand {
return .{ x = x, y = y };
}
v3f :: (x: $T = 0, y: T = 0, z: T = 0) -> Vec3
#modify { return type_is_float(T), "use v3i for integer arguments"; }
#modify { return meta.type_is_float(T), "use v3i for integer arguments"; }
#expand {
return .{ x = x, y = y, z = z };
}
v3i :: (x: $T = 0, y: T = 0, z: T = 0) -> Vec(3, T)
#modify { return type_is_integer(T), "use v3f for float arguments"; }
#modify { return meta.type_is_integer(T), "use v3f for float arguments"; }
#expand {
return .{ x = x, y = y, z = z };
}
v4f :: (x: $T = 0, y: T = 0, z: T = 0, w: T = 0) -> Vec4
#modify { return type_is_float(T), "use v4i for integer arguments"; }
#modify { return meta.type_is_float(T), "use v4i for integer arguments"; }
#expand {
return .{ x = x, y = y, z = z, w = w };
}
@ -541,7 +541,7 @@ v4f :: (v: Vec3, $$w: float) -> Vec4 #expand {
}
v4i :: (x: $T = 0, y: T = 0, z: T = 0, w: T = 0) -> Vec(4, T)
#modify { return type_is_integer(T), "use v4f for float arguments"; }
#modify { return meta.type_is_integer(T), "use v4f for float arguments"; }
#expand {
return .{ x = x, y = y, z = z, w = w };
}
@ -654,74 +654,76 @@ cross :: (a: Vec3, b: Vec3) -> Vec3 {
#scope_file;
#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);
b: Vec2 = v2f(1.0, 2.0);
Expect(a + b == v2f(1.0, 3.0));
Expect(b - a == v2f(1.0, 1.0));
Expect(a*b == v2f(0.0, 2.0));
Expect(a/b == v2f(0.0, 0.5));
test.expect(t, a + b == v2f(1.0, 3.0));
test.expect(t, b - a == v2f(1.0, 1.0));
test.expect(t, a*b == v2f(0.0, 2.0));
test.expect(t, a/b == v2f(0.0, 0.5));
}
{
a: Vec(2, int) = v2i(2, 1);
b: Vec(2, int) = v2i(1, 0);
Expect(a + b == v2i(3, 1));
Expect(b - a == v2i(-1, -1));
Expect(a*b == v2i(2, 0));
Expect(b/a == v2i(0, 0));
test.expect(t, a + b == v2i(3, 1));
test.expect(t, b - a == v2i(-1, -1));
test.expect(t, a*b == v2i(2, 0));
test.expect(t, b/a == v2i(0, 0));
}
{
a: Vec2 = v2f(2.3, -4.1);
b: Vec2 = v2f(1.0, 3.6);
c := min(a, b);
Expect(c == v2f(1.0, -4.1));
test.expect(t, c == v2f(1.0, -4.1));
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);
b: Vec3 = v3f(1.0, 2.0, 3.0);
Expect(a + b == v3f(1.0, 3.0, 5.0));
Expect(b - a == v3f(1.0, 1.0, 1.0));
Expect(a*b == v3f(0.0, 2.0, 6.0));
Expect(a/b == v3f(0.0, 0.5, 0.66666667));
test.expect(t, a + b == v3f(1.0, 3.0, 5.0));
test.expect(t, b - a == v3f(1.0, 1.0, 1.0));
test.expect(t, a*b == v3f(0.0, 2.0, 6.0));
test.expect(t, a/b == v3f(0.0, 0.5, 0.66666667));
a = v3f(1.0, 1.0, 0.0);
b = v3f(1.0, 0.0, 0.0);
Expect(reflect(a, b) == v3f(1.0, -1.0, 0.0));
Expect(round(v3f(1.2, 1.7, 1.5)) == v3f(1.0, 2.0, 2.0));
Expect(round(v3f(-1.2, -1.7, -1.5)) == v3f(-1.0, -2.0, -2.0));
test.expect(t, reflect(a, b) == v3f(1.0, -1.0, 0.0));
test.expect(t, round(v3f(1.2, 1.7, 1.5)) == v3f(1.0, 2.0, 2.0));
test.expect(t, round(v3f(-1.2, -1.7, -1.5)) == v3f(-1.0, -2.0, -2.0));
a = v3f(1.0, 0.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);
b: Vec3 = v3f(1.0, -3.6, 5.0);
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);
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);
b: Vec4 = v4f(4.0, 2.0, 3.0, 1.0);
Expect(a + b == v4f(6.25, 3.0, 5.0, 2.0));
Expect(b - a == v4f(1.75, 1.0, 1.0, 0.0));
Expect(a*b == v4f(9.0, 2.0, 6.0, 1.0));
Expect(a/b == v4f(0.5625, 0.5, 2.0/3.0, 1.0));
test.expect(t, a + b == v4f(6.25, 3.0, 5.0, 2.0));
test.expect(t, b - a == v4f(1.75, 1.0, 1.0, 0.0));
test.expect(t, a*b == v4f(9.0, 2.0, 6.0, 1.0));
test.expect(t, a/b == v4f(0.5625, 0.5, 2.0/3.0, 1.0));
});
Test(basic.tprint("%: VecN", UNITS), t => {
test.run(basic.tprint("%: VecN", UNITS), t => {
a: Vec(16, float);
b: Vec(16, float);
for *a {
@ -730,59 +732,59 @@ cross :: (a: Vec3, b: Vec3) -> Vec3 {
for *b {
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]});
Expect(b - a == Vec(16, float).{.[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]});
Expect(a*b == Vec(16, float).{.[0.0, 2.0, 6.0, 12.0, 20.0, 30.0, 42.0, 56.0, 72.0, 90.0, 110.0, 132.0, 156.0, 182.0, 210.0, 240.0]});
test.expect(t, a + b == Vec(16, float).{.[1.0, 3.0, 5.0, 7.0, 9.0, 11.0, 13.0, 15.0, 17.0, 19.0, 21.0, 23.0, 25.0, 27.0, 29.0, 31.0]});
test.expect(t, b - a == Vec(16, float).{.[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]});
test.expect(t, a*b == Vec(16, float).{.[0.0, 2.0, 6.0, 12.0, 20.0, 30.0, 42.0, 56.0, 72.0, 90.0, 110.0, 132.0, 156.0, 182.0, 210.0, 240.0]});
Expect(min(a, b) == a);
Expect(max(a, b) == b);
Expect(max(a, 12) == Vec(16, float).{.[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 12.0, 12.0, 12.0]});
Expect(min(a, 13.2) == Vec(16, float).{.[13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 14.0, 15.0]});
Expect(clamp(a, 7.25, 12.0) == Vec(16, float).{.[7.25, 7.25, 7.25, 7.25, 7.25, 7.25, 7.25, 7.25, 8, 9, 10, 11, 12, 12, 12, 12]});
test.expect(t, min(a, b) == a);
test.expect(t, max(a, b) == b);
test.expect(t, max(a, 12) == Vec(16, float).{.[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 12.0, 12.0, 12.0]});
test.expect(t, min(a, 13.2) == Vec(16, float).{.[13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 14.0, 15.0]});
test.expect(t, clamp(a, 7.25, 12.0) == Vec(16, float).{.[7.25, 7.25, 7.25, 7.25, 7.25, 7.25, 7.25, 7.25, 8, 9, 10, 11, 12, 12, 12, 12]});
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]});
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, ceil(a1) == Vec(16, float).{.[2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 5.0, -1.0, -1.0, -1.0, 12.0, 14.0, 15.0, 14.0, 15.0, 65537]});
test.expect(t, floor(a1) == Vec(16, float).{.[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 4.0, -2.0, -1.0, -2.0, 11.0, 14.0, 15.0, 14.0, 15.0, 65536]});
Expect(dot(a, b) == 1360.0);
Expect(abs(a) == a);
test.expect(t, dot(a, b) == 1360.0);
test.expect(t, abs(a) == a);
c := a;
for *c { // Check making every other component negative
if it_index%2 == 0 then it.* = -it.*;
}
Expect(abs(c) == a);
Expect(float_eq(length(normalize(a)), 1));
Expect(a + 2 == Vec(16, float).{.[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]});
Expect(a - 2 == Vec(16, float).{.[-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]});
Expect(a*2 == Vec(16, float).{.[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]});
test.expect(t, abs(c) == a);
test.expect(t, float_eq(length(normalize(a)), 1));
test.expect(t, a + 2 == Vec(16, float).{.[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]});
test.expect(t, a - 2 == Vec(16, float).{.[-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]});
test.expect(t, a*2 == Vec(16, float).{.[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]});
});
Test(basic.tprint("%: Quat", UNITS), t => {
test.run(basic.tprint("%: Quat", UNITS), t => {
qi := quat_identity;
q: Quat = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0));
q2: Quat = rotation_quat(from_rad(PI), v3f(1.0, 0.0, 1.0));
qc := conjugate(q);
inv_q := inverse(q);
Expect(q*inv_q == qi);
test.expect(t, q*inv_q == qi);
q1 := quat(2, 0, 0, 0);
q2 = quat(1, 1, -1, 0);
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));
p := v3f(2.0, 0.0, 0.0);
c1 := q*quat(p, 0)*conjugate(q);
c2 := q*p;
Expect(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(c2, v3f(math.sqrt(2.0), math.sqrt(2.0), 0.0)));
test.expect(t, v3_eq(c1.xyz, c2));
q = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0));
m := rotation_mat4(q);
p1 := v4f(2.0, 0.0, 0.0, 1.0);
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));
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
/// constants.
///
/// Additionally, it provides a platform-independant
/// interface for interacting with the target operating
/// system.
#module_parameters(RunTests := false);
/*
Module jc contains procedures for working with memory,
arrays, and strings; as well as helpful macros and
constants.
// @note(judah): this causes some very weird issues in tests so it's ifdef'd out
#if !RunTests #add_context temp_allocator: Allocator;
Additionally, it provides a platform-independant
interface for interacting with the target operating
system.
*/
#load "+internal/builtin.jai";
#load "+internal/array.jai";
#load "+internal/kv.jai";
#load "+internal/hashing.jai";
#load "+internal/memory.jai";
#load "+internal/allocators.jai";
#load "+internal/testing.jai";
#load "+internal/keywords.jai";
#load "+internal/type_info.jai";
#load "internal/builtin.jai";
#load "internal/array.jai";
#load "internal/memory.jai";
#load "internal/testing.jai";
#load "internal/keywords.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;