Author:ptrace
Comitter:ptrace
Date:2026-08-16 03:55:27 UTC
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7a80058
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
.build
/tests/run_tests
diff --git a/0x2_Bitpack.jai b/0x2_Bitpack.jai
new file mode 100644
index 0000000..5a5f2e6
--- /dev/null
+++ b/0x2_Bitpack.jai
@@ -0,0 +1,97 @@
Bits_Packed :: struct {
count: int;
width: int;
value: int;
top: int;
}
bits_pack :: ($type: Type, values: ..type) -> Bits_Packed #modify {
if !array_find(My_Allowed_Types, type) return false, "Invalid type";
return true;
} {
result: int;
bit_count := size_of(type) * 8;
for < values {
result = (result << bit_count) | it;
}
packed: Bits_Packed;
packed.count = values.count;
packed.width = bit_count;
packed.value = result;
packed.top = (1 << bit_count) - 1;
return packed;
}
/** Reminder: You must free the array */
bits_unpack :: (packed: Bits_Packed) -> []int {
arr := NewArray(packed.count, int);
for i: 0..packed.count-1 {
unpacked := packed.value >> (i * packed.width) & packed.top;
arr[i] = unpacked;
}
return arr;
}
bits_unpack_by_index :: (packed: Bits_Packed, index: int) -> int {
return packed.value >> (index * packed.width) & packed.top;
}
#scope_file
My_Allowed_Types :: Type.[u8, u16, u32, u64];
using,only(array_find, NewArray) Basic :: #import "Basic";
/*
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2026 Adam Blazeowsky
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------
*/
diff --git a/0x2_Colored.jai b/0x2_Colored.jai
new file mode 100644
index 0000000..254006c
--- /dev/null
+++ b/0x2_Colored.jai
@@ -0,0 +1,367 @@
#module_parameters(ASSERTION_ENABLED := true);
HSL :: struct {
h: u16;
s: u8;
l: u8;
}
RGB :: struct {
r, g, b: u8;
}
/* --------- *
* HSL procs *
* --------- */
hsl_to_integers :: (using hsl: HSL) -> (h: int, s: int, l: int) {
return xx h, xx s, xx l;
}
hsl_to_rgb_hex :: (bg_color_hsl: string) -> string {
hsl := string_to_hsl(bg_color_hsl, disable_checks=true);
rgb := hsl_to_rgb(hsl);
hex := rgb_to_hex(rgb);
return hex;
}
/** https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB_alternative */
hsl_to_rgb :: (hsl: HSL) -> RGB {
MAGIC_RED :: 0;
MAGIC_GREEN :: 8;
MAGIC_BLUE :: 4;
convert :: (n: float, h: float, s: float, l: float) -> int {
k := fmod_cycling(n + (h / 30.0), 12.0);
a := s * min(l, 1.0 - l);
value := l - a * max(-1.0, min(min(k - 3.0, 9.0 - k), 1.0));
value *= RGB_MAX;
return round(value);
}
h := cast(float, hsl.h);
s := hsl.s / 100.0;
l := hsl.l / 100.0;
r := convert(MAGIC_RED, h, s, l);
g := convert(MAGIC_GREEN, h, s, l);
b := convert(MAGIC_BLUE, h, s, l);
#if ASSERTION_ENABLED then for int.[r, g, b] {
assert(it >= 0 && it <= U8_MAX, "Does not fit inside U8");
}
rgb: RGB;
rgb.r = xx r;
rgb.g = xx g;
rgb.b = xx b;
return rgb;
}
/* --------- *
* RGB procs *
* --------- */
rgb_to_integers :: (using rgb: RGB) -> (r: int, g: int, b: int) {
return xx r, xx g, xx b;
}
/** Idk if people really need a runtime lower case option. We'll see. */
rgb_to_hex :: (using rgb: RGB, $lower_case := false) -> string {
#if lower_case {
CHARS :: #run to_lower_copy(HEX_ALPHA);
} else {
CHARS :: HEX_ALPHA;
}
result := alloc_string(RGB_HEX_STRING_LENGTH);
result.data[0] = #char "#";
for u8.[r, g, b] {
result.data[it_index * 2 + 1] = CHARS[it >> 4];
result.data[it_index * 2 + 2] = CHARS[it & 0xF];
}
return result;
}
/** https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB */
rgb_to_hsl :: (rgb: RGB) -> HSL {
MAGIC_RED :: 6.0;
MAGIC_GREEN :: 2.0;
MAGIC_BLUE :: 4.0;
r := rgb.r / RGB_FMAX;
g := rgb.g / RGB_FMAX;
b := rgb.b / RGB_FMAX;
#if ASSERTION_ENABLED then for float.[r, g, b] {
assert(it >= 0.0 && it <= 1.0, "RGB value is not within bounds");
}
max_c := max(r, max(g, b));
min_c := min(r, min(g, b));
delta := max_c - min_c;
l := (max_c + min_c) / 2.0;
s: float;
if delta != 0.0 {
s = delta / (1.0 - abs(2.0 * l - 1.0));
}
h: float;
if delta != 0.0 {
if max_c == r {
h = fmod_cycling((g - b) / delta, MAGIC_RED);
}
else if max_c == g {
h = (b - r) / delta + MAGIC_GREEN;
}
else {
h = (r - g) / delta + MAGIC_BLUE;
}
h *= 60.0;
if h < 0.0 then h += 360.0;
}
h = roundf(h);
s = roundf(s * 100.0);
l = roundf(l * 100.0);
#if ASSERTION_ENABLED assert(h >= 0.0 && h <= 360.0, "Hue value is not within expected bounds");
#if ASSERTION_ENABLED then for float.[s, l] {
assert(it >= 0.0 && it <= 100, "SL value is not within expected bounds");
}
hsl: HSL;
hsl.h = xx h;
hsl.s = xx s;
hsl.l = xx l;
return hsl;
}
/* -------------- *
* int_to_* procs *
* -------------- */
integers_to_hsl :: (h: int, s: int, l: int, loc := #caller_location) -> HSL {
#if ASSERTION_ENABLED {
assert(h >= 0 && h <= 360, "%: H value is not within expected bounds", loc);
assert(s >= 0 && s <= 100, "%: S value is not within expected bounds", loc);
assert(l >= 0 && l <= 100, "%: L value is not within expected bounds", loc);
}
hsl: HSL = ---;
hsl.h = xx h;
hsl.s = xx s;
hsl.l = xx l;
return hsl;
}
integers_to_rgb :: (r: int, g: int, b: int, loc := #caller_location) -> RGB {
#if ASSERTION_ENABLED {
assert(r >= 0 && r <= RGB_MAX, "%: R value is not within expected bounds", loc);
assert(g >= 0 && g <= RGB_MAX, "%: G value is not within expected bounds", loc);
assert(b >= 0 && b <= RGB_MAX, "%: B value is not within expected bounds", loc);
}
rgb: RGB = ---;
rgb.r = xx r;
rgb.g = xx g;
rgb.b = xx b;
return rgb;
}
/* ----------------- *
* string_to_* procs *
* ----------------- */
string_to_hsl :: (maybe_hsl: string, $disable_checks := false, $do_logging := true, loc := #caller_location) -> (h: HSL = {}, ok: bool = false) {
hsl_arr := split(maybe_hsl, " ",, temp);
#if !disable_checks {
if hsl_arr.count != 3 {
#if do_logging then log_error(
"%: Could not convert HSL string to HSL values. Your string does not have three value pairs: '%'",
loc,
maybe_hsl,
flags=.ERROR,
);
return;
}
for digit: hsl_arr for digit {
if !is_digit(it) {
#if do_logging then log_error("%: '%' is not a digit", loc, it);
return;
}
}
}
h := to_integer(hsl_arr[0]);
s := to_integer(hsl_arr[1]);
l := to_integer(hsl_arr[2]);
hsl := integers_to_hsl(h, s, l, loc=loc);
return hsl, true;
}
string_to_rgb :: (maybe_rgb: string, $disable_checks := false, $do_logging := true, loc := #caller_location) -> (r: RGB = {}, ok: bool = false) {
parse_byte :: (s: string) -> (ok: bool, b: u8) #expand {
hi_ok, hi := char_to_hex(s[0]);
lo_ok, lo := char_to_hex(s[1]);
if !hi_ok || !lo_ok {
#if `do_logging log_error("%: RGB value '%' is not in hex range", `loc, s);
return false, 0;
}
byte := hi << 4 | lo;
return true, byte;
}
char_to_hex :: (c: u8) -> (ok: bool, u8) {
if c >= #char "0" && c <= #char "9" return true, c - #char "0";
if c >= #char "A" && c <= #char "F" return true, c - #char "A" + 10;
return false, 0;
}
possibly_rgb: string;
#if !disable_checks {
if maybe_rgb.count != RGB_HEX_STRING_LENGTH {
#if do_logging then log_error(
"%: % Does not equal % characters in '%'",
loc,
RGB_ERROR_MSG,
RGB_HEX_STRING_LENGTH,
maybe_rgb
);
return;
}
if maybe_rgb.count > 0 && maybe_rgb[0] != "#" {
#if do_logging then log_error(
"%: % Does not start with '#' in '%'",
loc,
RGB_ERROR_MSG,
maybe_rgb
);
return;
}
for maybe_rgb if (it >= #char "a" && it <= #char "z") {
#if do_logging then log_error(
"%: % Is not upper case in '%'",
loc,
RGB_ERROR_MSG,
maybe_rgb
);
return;
}
possibly_rgb = advance(maybe_rgb);
for possibly_rgb {
if !contains(HEX_ALPHA, it) {
#if do_logging then log_error(
"%: '%' Invalid character: '%' in '%'",
loc,
RGB_ERROR_MSG,
it,
maybe_rgb
);
return;
}
}
}
ok: bool;
rgb: RGB;
ok, rgb.r = parse_byte(slice(maybe_rgb, 1, 2)); if !ok return;
ok, rgb.g = parse_byte(slice(maybe_rgb, 3, 2)); if !ok return;
ok, rgb.b = parse_byte(slice(maybe_rgb, 5, 2)); if !ok return;
return rgb, true;
}
#scope_file
HEX_ALPHA :: "0123456789ABCDEF";
RGB_MAX :: 255;
RGB_FMAX :: 255.0;
RGB_HEX_STRING_LENGTH :: 7;
RGB_ERROR_MSG :: "Invalid RGB string.";
using,only(
log_error, min, max, temp, assert, alloc_string, to_integer, advance, is_digit
) Basic :: #import "Basic";
using,only(slice, split, contains) String :: #import "String";
using,only(fmod_cycling, U8_MAX, abs) Math :: #import "Math";
#import,file "0x2_Math.jai";
/*
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2026 Adam Blazeowsky
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------
*/
diff --git a/0x2_Crashed.jai b/0x2_Crashed.jai
new file mode 100644
index 0000000..764d92c
--- /dev/null
+++ b/0x2_Crashed.jai
@@ -0,0 +1,132 @@
/** Currently (26.07.2026) Jai's crash handler does not print location data.
This modification from Cephon @ solarium.technology fixes this.
There are two ways to use this:
1. Importing necessary modules and initializing it
2. Using a macro with defaults that you might not like
Option 1:
```
#import "Basic";
Debug :: #import "Debug";
```
and
```
Debug.init();
Debug.crash_handler = crash_handler;
```
Importing the Debug module will enable the module parameter `USE_GRAPHICS=true`.
This opens a window whenever an assertion hits.
To turn off this behavior, you must add:
```
Debug.set_report_mode(interactive = false);
```
Which leads us to ...
Option 2:
```
opinionated_init();
```
Which does the things described above.
You can test a crash with:
```
t: *s32 = cast(*s32, 0);
log("t=%", t.*);
```
*/
opinionated_init :: () #expand {
using,only(init) Debug :: #import "Debug";
Debug.init();
Debug.crash_handler = crash_handler;
Debug.set_report_mode(interactive = false);
}
crash_handler :: (trace_symbols: []string) {
using _ :: #import "String";
using _ :: #import "Process";
using _ :: #import "System";
for trace_symbols {
found, _, right := split_from_right(it, " ");
if !found continue;
right.count -= 2;
right.data += 1;
process_result, output_string, _, _ := run_command(
"addr2line", "-e", get_path_of_running_executable(), "-f", "-C", "-p", right,
working_directory="", capture_and_return_output=true, timeout_ms = -1
);
if !(process_result.type == .EXITED && process_result.exit_code == 0) continue;
log("%", output_string);
}
}
#scope_file
using,only(log) Basic :: #import "Basic";
/*
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2026 Adam Blazeowsky
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------
*/
diff --git a/0x2_Datefmt.jai b/0x2_Datefmt.jai
new file mode 100644
index 0000000..7d39c07
--- /dev/null
+++ b/0x2_Datefmt.jai
@@ -0,0 +1,129 @@
/** Formatting similar to Python:
date := format_date(time, "%Y-%M-%D %h:%m:%s.%f");
If you insert `%g` for example, it won't eat it up.
So no escaping necessary.
*/
format_date :: (time: Apollo_Time, fmt: string) -> string {
is_at_end :: () -> bool #expand {
return `idx > `fmt.count-1;
}
pop :: () -> u8 #expand {
value := `fmt[`idx];
idx += 1;
return value;
}
peek :: () -> u8 #expand {
return `fmt[`idx];
}
pad :: (dt: int) -> string {
if dt > 9 return sprint("%", dt);
return sprint("0%", dt);
}
parse_fmt :: (idx: int, cal: Calendar_Time, fmt: string) -> string {
current := peek();
out: string;
if current == {
case "Y"; out = sprint("%", cal.year);
case "M"; out = pad(xx cal.month_starting_at_0 + 1);
case "D"; out = pad(xx cal.day_of_month_starting_at_0 + 1);
case "h"; out = pad(xx cal.hour);
case "m"; out = pad(xx cal.minute);
case "s"; out = pad(xx cal.second);
case "f"; out = sprint("%", cal.millisecond);
case; out = "";
}
return out;
}
cal := to_calendar(time);
buf: String_Builder;
idx: int;
while !is_at_end() {
char := pop();
if char == {
case "%";
date := parse_fmt(idx, cal, fmt,, temp);
if !date {
append(*buf, "%");
continue;
}
append(*buf, date);
pop();
case;
append(*buf, char);
}
}
return builder_to_string(*buf);
}
#scope_file
using,only(
append, builder_to_string, String_Builder,
Apollo_Time, Calendar_Time, to_calendar,
sprint,
temp,
) Basic :: #import "Basic";
/*
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2026 Adam Blazeowsky
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------
*/
diff --git a/0x2_Lexer.jai b/0x2_Lexer.jai
new file mode 100644
index 0000000..d035496
--- /dev/null
+++ b/0x2_Lexer.jai
@@ -0,0 +1,369 @@
/** TODO: Better test coverage */
#add_context lib0x2_lexer_error_template := PARSER_ERROR_TEMPLATE;
Lexer_State :: struct {
index: int;
source: string;
}
/* ------------ *
* Lexer Macros *
* ------------ */
is_at_end :: () -> bool #expand {
return is_at_end(`index, `source);
}
peek :: () -> u8 #expand {
return peek(`index, `source);
}
peek_back :: () -> u8 #expand {
return peek_back(`index, `source);
}
peek_at :: (offset: int) -> u8 #expand {
return peek_back(`index + offset, `source);
}
peek_forward :: (offset: int) -> u8 #expand {
return peek_forward(`index, `source, offset);
}
advance :: () -> u8 #expand {
return advance(*`index, `source);
}
advance_by :: inline (offset: int) #expand {
advance_by(*`index, offset);
}
advance_by :: inline (offset: string) #expand {
advance_by(*`index, offset.count);
}
advance_by :: inline (offset: ..string) #expand {
count: int;
for offset count += it.count;
advance_by(*`index, count);
}
match :: (substring: string, offset := 0) -> bool #expand {
return match(`source, substring, offset);
}
match_grammar :: (
substrings: []string, offset := 0
)
-> (match: bool, advanced: int) #expand
{
a, b := match_grammar(`source, substrings, offset);
return a, b;
}
consume_including :: (token: $T/Type.[ string, u8 ]) -> string #expand {
return consume_including(`index, `source, token);
}
consume_till :: (token: $T/Type.[ string, u8 ]) -> string #expand {
return consume_till(`index, `source, token);
}
/* ---------------------------- *
* Procs, assuming Lexer_State *
* ---------------------------- */
count_newlines_till_now :: (using x2lexer_state: $T/interface Lexer_State) -> int {
count := 1;
for i: 0..index {
if source[i] == {
case "\r"; #through;
case "\n"; count += 1;
}
}
return count;
}
count_characters :: (s: string, char: u8) -> int {
count: int;
for s if it == char then count += 1;
return count;
}
count_any_to_char :: (
using x2lexer_state: $T/interface Lexer_State, end_char: u8
)
-> (ok: Result, count: int)
{
count: int;
for i: index..source.count-1 {
current := source[i];
if current == end_char return .OK, count;
count += 1;
}
return .EOF, count;
}
count_characters_from_now_to_char :: (
using x2lexer_state: $T/interface Lexer_State, char: u8, end_char: u8
)
-> (ok: Result, count: int)
{
count: int;
for i: index..source.count-1 {
current := source[i];
if current == end_char return .OK, count;
if current == char then count += 1;
}
return .EOF, count;
}
count_backwards_any_till :: (
using x2lexer_state: $T/interface Lexer_State, end_char: u8
)
-> (ok: Result, count: int)
{
count: int;
for < i: 0..index {
current := source[i];
if current == end_char return .OK, count;
count += 1;
}
return .EOF, count;
}
/* --------------- *
* Procs Stateless *
* --------------- */
is_at_end :: inline (idx: int, s: string) -> bool {
return idx > s.count-1;
}
peek :: inline (idx: int, s: string) -> u8 {
if idx > s.count-1 return #char "\0";
return s[idx];
}
peek_back :: inline (idx: int, s: string) -> u8 {
if idx-1 > s.count-1 return #char "\0";
return s[idx-1];
}
peek_at :: (new_index: int, s: string) -> u8 {
if new_index >= 0 && new_index < s.count
then return s[new_index];
else return #char "\0";
}
peek_forward :: inline (idx: int, s: string, offset: int) -> u8 {
if idx+offset > s.count-1 return #char "\0";
return s[idx+offset];
}
advance :: inline (idx: *int, s: string) -> u8 {
value := s[idx.*];
idx.* += 1;
return value;
}
advance_by :: inline (idx: *int, offset: int) {
idx.* += offset;
}
advance_by :: inline (idx: *int, s: string) {
idx.* += s.count;
}
match :: (s: string, substring: string, offset := 0) -> bool {
if substring.count + offset > s.count return false;
for substring if it != s[it_index + offset] return false;
return true;
}
match_grammar :: (
s: string, substrings: []string, offset := 0
)
-> (match: bool, advanced: int)
{
advanced: int;
for substrings {
if !match(s, it, offset + advanced) return false, advanced;
advanced += it.count;
}
return true, advanced;
}
consume_including :: inline (
match_index: int,
data: string,
char: u8
)
-> string
{
return consume_till(match_index, data, char, true);
}
consume_including :: inline (
match_index: int,
data: string,
substring: string
)
-> string
{
return consume_till(match_index, data, substring, true);
}
consume_till :: (
pos: int,
data: string,
char: u8,
include := false,
)
-> string
{
keyword_count := 1;
idx := pos;
offset := ifx include then keyword_count else 0;
while idx < data.count {
window := data[idx];
if window == char {
consumed := slice(data, pos, idx - pos + offset);
return copy_string(consumed);
}
idx += 1;
}
return "";
}
consume_till :: (
pos: int,
data: string,
substring: string,
include := false
)
-> string
{
keyword_count := substring.count;
count := pos;
offset := ifx include then keyword_count else 0;
while count < data.count {
window := slice(data, count, keyword_count);
if window == substring {
consumed := slice(data, pos, count - pos + offset);
return copy_string(consumed);
}
count += 1;
}
return "";
}
report_error :: (
x2lexer_state: $T/interface Lexer_State,
fp: string,
message: string,
args: ..Any
) {
lines := count_newlines_till_now(x2lexer_state);
_, chars := count_backwards_any_till(x2lexer_state, "\n");
log_error(
context.lib0x2_lexer_error_template,
tprint(message, ..args),
tprint("%:%:%", fp, lines, chars)
);
}
u8_to_str :: (c: *u8, $do_print := false) -> string {
s := string.{ 1, c };
#if do_print then log(" CHAR: »%«", s);
return s;
}
#scope_file
PARSER_ERROR_TEMPLATE :: #string STR_END
-- ERROR ---------------------------------
%1
------------------------------------------
Parsing error at: %2
STR_END;
Result :: enum {
EOF;
OK;
}
using,only(copy_string, log, log_error, tprint) Basic :: #import "Basic";
using,only(slice) String :: #import "String";
/*
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2026 Adam Blazeowsky
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------
*/
diff --git a/0x2_Math.jai b/0x2_Math.jai
new file mode 100644
index 0000000..7469a26
--- /dev/null
+++ b/0x2_Math.jai
@@ -0,0 +1,60 @@
roundf :: inline (x: float) -> float {
return floor(x + 0.5);
}
round :: inline (x: float) -> int {
return xx floor(x + 0.5);
}
#scope_file
using, only(floor) Math :: #import "Math";
/*
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2026 Adam Blazeowsky
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------
*/
diff --git a/0x2_Qtrace.jai b/0x2_Qtrace.jai
new file mode 100644
index 0000000..b8b7e41
--- /dev/null
+++ b/0x2_Qtrace.jai
@@ -0,0 +1,141 @@
#module_parameters(ENABLE_TRACING := true);
qtrace :: (
$label: string = "", color: Console_Color = COLOR_DEFAULT, loc := #caller_location
) #expand {
#if ENABLE_TRACING {
ts_start := current_time_monotonic();
`defer delta_report(label, ts_start, #procedure_name(), color, loc);
}
}
/** TODO: Include
CPU
- Cache misses (L1/L2/L3)
- IPC (instructions per cycle)
- Branch prediction hits/misses
- Stall cycles
- SIMD utilization
Memory
- TLB misses
- Memory bandwidth (bytes read/written)
Threading
- Lock contention / wait time
- Context switches
- Core migration frequency
Timing
- Frame time per system
- Hitches / frame time variance
- Function call frequency vs cost (hot paths)
Other Things:
Regarding hitches/spikes, instead for going towards p95/p99, catch outliers over a certain
time frame.
Maybe logging/printing in a separate thread?
*/
#scope_file
COLOR_DEFAULT :: Console_Color.YELLOW;
delta_report :: (
$label: string,
ts_start: Apollo_Time,
proc_name: string,
color: Console_Color,
loc: Source_Code_Location
) {
ts_now := current_time_monotonic();
delta := ts_now - ts_start;
us := to_microseconds(delta);
ms := to_milliseconds(delta);
s := to_seconds(delta);
using loc;
print_color("PROCEDURE: %", proc_name, color=color, style=.BOLD);
print("\n");
#if label {
print_color("Label: %", label, color=color);
print("\n");
}
print_color("Delta: % us % ms % s", us, ms, s, color=color);
print("\n");
print_color(
"Location: %: %:%",
fully_pathed_filename,
line_number,
character_number,
color=color
);
print("\n\n");
}
using,only(
print, Source_Code_Location,
Apollo_Time, current_time_monotonic, to_seconds, to_microseconds, to_milliseconds,
operator -,
) Basic :: #import "Basic";
using,only(print_color, Console_Color) PC :: #import "Print_Color";
/*
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2026 Adam Blazeowsky
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------
*/
diff --git a/0x2_Rectcut.jai b/0x2_Rectcut.jai
new file mode 100644
index 0000000..431ef5d
--- /dev/null
+++ b/0x2_Rectcut.jai
@@ -0,0 +1,126 @@
/** C R E D I T
Idea from: https://halt.software/p/rectcut-for-dead-simple-ui-layouts
Code from: https://solarium.technology/ (with some modifications by me)
*/
#module_parameters(ASSERTION_ENABLED := true);
rcut_from_right :: inline (using rect: *$T/interface Rectangle, percentage: float) -> T {
#if ASSERTION_ENABLED then assert_percent();
return rcut_from_right_fixed(rect, rect.width * percentage);
}
rcut_from_left :: inline (using rect: *$T/interface Rectangle, percentage: float) -> T {
#if ASSERTION_ENABLED then assert_percent();
return rcut_from_left_fixed(rect, rect.width * percentage);
}
rcut_from_top :: inline (using rect: *$T/interface Rectangle, percentage: float) -> T {
#if ASSERTION_ENABLED then assert_percent();
return rcut_from_top_fixed (rect, rect.height * percentage);
}
rcut_from_bottom :: inline (using rect: *$T/interface Rectangle, percentage: float) -> T {
#if ASSERTION_ENABLED then assert_percent();
return rcut_from_bottom_fixed(rect, rect.height * percentage);
}
rcut_from_right_fixed :: inline (using rect: *$T/interface Rectangle, n: float) -> T {
result := T.{ x + width - n, y, n, height };
width = max(width - n, 0);
return result;
}
rcut_from_left_fixed :: inline (using rect: *$T/interface Rectangle, n: float) -> T {
result := T.{ x, y, n, height };
x += n;
width = max(width - n, 0);
return result;
}
rcut_from_top_fixed :: inline (using rect: *$T/interface Rectangle, n: float) -> T {
result := T.{ x, y, width, n };
y += n;
height = max(height - n, 0);
return result;
}
rcut_from_bottom_fixed :: inline (using rect: *$T/interface Rectangle, n: float) -> T {
result := T.{ x, y + height - n, width, n };
height = max(height - n, 0);
return result;
}
rcut_shrink :: inline (using rect: $T/interface Rectangle, n: float) -> T {
return { x + n, y + n, width - n*2, height - n*2 };
}
#scope_file
/** From Raylib https://raylib.com */
Rectangle :: struct {
x: float; /** top-left corner position x */
y: float; /** top-left corner position y */
width: float;
height: float;
}
assert_percent :: () #expand {
assert(
`percentage >= 0.0 && `percentage <= 1.0,
"Only values between 0.0 and 1.0 allowed. Your value: %",
`percentage
);
}
using,only(max, assert) Basic :: #import "Basic";
/*
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2026 Adam Blazeowsky
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------
*/
diff --git a/0x2_Stringpad.jai b/0x2_Stringpad.jai
new file mode 100644
index 0000000..0548774
--- /dev/null
+++ b/0x2_Stringpad.jai
@@ -0,0 +1,100 @@
string_pad_left :: (s: string, padding: int, char: string = " ") -> string {
code :: #string DONE
for 1..length {
append(*sb, `char);
}
append(*sb, `s);
DONE;
__string_pad(code);
}
string_pad_right :: (s: string, padding: int, char: string = " ") -> string {
code :: #string DONE
append(*sb, `s);
for 1..length {
append(*sb, `char);
}
DONE;
__string_pad(code);
}
string_pad_lr :: (s: string, padding: int, char: string = " ") -> string {
code :: #string DONE
half: int = xx floor(xx length / 2.0);
for 1..half {
append(*sb, `char);
}
append(*sb, `s);
for 1..half {
append(*sb, `char);
}
DONE;
__string_pad(code);
}
#scope_file
__string_pad :: ($code: string) #expand {
sb: String_Builder;
length := `padding - `s.count;
#insert code;
`return builder_to_string(*sb);
}
using,only(String_Builder, builder_to_string, append) Basic :: #import "Basic";
using,only(floor) Math :: #import "Math";
/*
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2026 Adam Blazeowsky
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------
*/
diff --git a/0x2_Termcolors.jai b/0x2_Termcolors.jai
new file mode 100644
index 0000000..b9fb31b
--- /dev/null
+++ b/0x2_Termcolors.jai
@@ -0,0 +1,615 @@
/*
--------------------
--- [ Reminder ] ---
--------------------
Jai already provides a module for console color codes: `modules/Print_Color.jai`.
There are small differences between the builtin lib and this lib.
The builtin lib:
- prints the characters directly to stdout/stderr, instead of returning them
- uses a fixed default color palette, instead of supporting arbitrary values
If you don't need the features provided by this lib, consider using the builtin variant.
----------------------------------
--- [ paint() / General Info ] ---
----------------------------------
paint() uses the 4bit terminal color palette. The structure of the proc signature
through every API is the same:
`text, font style, foreground color, background color`
Only `text` is mandatory. Other params can be omitted since they default to `.NONE`.
[Note]: You can overload each proc for more flexibility.
This prints text without any formatting.
log(paint("Foo Bar"));
Applies a font weight, foreground and background colors
log(paint("Foo Bar", .BOLD, .BLACK, .WHITE));
Applies only foreground and background colors
log(paint("Foo Bar", .RESET, .BLACK, .WHITE));
Applies multiple text decorations
log(paint("Foo Bar", .[.BOLD, .UNDERLINE, .ITALIC], .BLACK, .WHITE));
--- [ Buffering Text ]
If you want to buffer a lot of text, you can provide `no_termination = true` to the
APIs. Then they won't append the "reset" terminal code.
When you're done with your string buffer, you can call the proc `paint_reset()`, which
returns the reset code.
--- [ Visual Width / Real Width ]
If you do `paint("foo", fg = .RED).count`, it will return way more then three characters.
This can be quite annoying if you want to build an CLI program, that is aware of its
width.
Because of that, every API returns additionally an integer, which describes the count of
used characters by the terminal codes.
```
my_str, vcount := paint("foo", fg = .RED);
```
Using `vcount` you can now account for the shift in your application.
------------------
--- [ Memory ] ---
------------------
All strings returned must be freed by the caller. Internal procs are using the TS as
scratch buffer.
----------------------
--- [ paint_ex() ] ---
----------------------
paint_ex() uses the 256bit color palette. There are some predefined colors you can use.
log(paint_ex("Foo Bar", .UNDERLINE, .GREEN_DARK, .ORANGE_LIGHT));
It also allows multiple font styles.
log(paint_ex("Foo Bar", .[.BOLD, .UNDERLINE, .ITALIC], .GREEN_DARK, .ORANGE_LIGHT));
If you want to have more control over the colors, you can use integers.
log(paint_ex("Foo Bar", .UNDERLINE, 84, 124));
log(paint_ex("Foo Bar", .[.BOLD, .UNDERLINE, .ITALIC], 84, 124));
You could even create a own color palette. Just create a enum with this signature:
My_Colors :: enum #specified {
NONE :: -1;
COL1 :: 84;
COL2 :: 124;
}
And use `paint_ex_custom()` like this:
log(paint_ex_custom("Foo Bar", .UNDERLINE, My_Colors.COL1, My_Colors.COL2));
The downside is, you cannot omit the fore- and background color. If you want that
feature, you have to wrap this proc in a custom proc.
-----------------------
--- [ paint_rgb() ] ---
-----------------------
If you want to use RGB values you can to it like that:
log(paint_rgb("Foo Bar", .UNDERLINE, .{ 255, 0, 0 }, .{ 0, 0, 255 }));
log(paint_rgb("Foo Bar", .[.BOLD, .UNDERLINE, .ITALIC], .{ 255, 0, 0 }, .{ 0, 0, 255 }));
Consult the enums below for more colors.
---------------------------------------------------
--- [ Using String Literals as Terminal Codes ] ---
---------------------------------------------------
If you need "raw" access because you want to build more complex stuff:
paint_raw();
Example:
log(paint_raw("1;3;32;45", "Foo Bar"));
--------------------------------
--- [ Notes on Performance ] ---
--------------------------------
Since those APIs providing flexibility, they have to branch a few times.
Which won't be a negative hit on most programs. But if you're developing
something hyper-fast, those APIs could be a perf hit.
To bypass this, you can just use this proc:
- paint_raw()
Which basically only fprints this string: `"\u001b[%m%\u001b[0m"`.
*/
// leaving those constants public, maybe someone wants to use them
TERM_ESCAPE_START :: "\e[%m%";
TERM_ESCPAE_RESET :: "\e[0m";
TERM_FOREGROUND_COLOR_FROM_EXT_TABLE :: "38;5;";
TERM_BACKGROUND_COLOR_FROM_EXT_TABLE :: "48;5;";
Term_Rgb :: struct {
r, g, b: u8 = 255, 255, 255;
}
Term_Text_Style :: enum #specified {
RESET :: 0;
BOLD :: 1;
FAINT :: 2; // not widely supported
ITALIC :: 3; // not widely supported
UNDERLINE :: 4;
SLOW_BLINK :: 5; // less than 150 bpm
RAPID_BLINK :: 6; // not widely supported
SWAP_FG_BG :: 7;
CONCEAL :: 8; // not widely supported
CROSSED_OUT :: 9; // not widely supported
PRIMARY_FONT :: 10;
// omitting alternate fonts (11-19) since they aren't really supported anymore
FRAKTUR :: 20; // not widely supported
BOLD_OFF_OR_DOUBLE_UNDERLINE :: 21; // not widely supported
NORMAL_COLOR_OR_INTENSITY :: 22;
ITALIC_OFF_FRAKTUR_OFF :: 23;
UNDERLINE_OFF :: 24;
BLINK_OFF :: 25;
// Code 26 does nothing (https://vt100.net/docs/vt510-rm/SGR.html)
INVERSE_OFF :: 27;
CONCEAL_OFF :: 28;
CROSSED_OUT_OFF :: 29;
FRAMED :: 51;
ENCIRCLED :: 52;
OVERLINED :: 53;
ENCIRCLED_OFF_FRAMED_OFF :: 54;
OVERLINED_OFF :: 55;
// 60 - 65 Ideograms hardly ever supported
// 90 - 107 Bright fg and bg color is non standard
}
Term_Color_Foreground :: enum #specified {
NONE :: -1;
BLACK :: 30;
RED :: 31;
GREEN :: 32;
YELLOW :: 33;
BLUE :: 34;
MAGENTA :: 35;
CYAN :: 36;
WHITE :: 37;
EXTEND :: 38; // 5;<Term_Color_Table> OR 2;<r>;<g>;<b>
DEFAULT :: 39;
}
Term_Color_Background :: enum #specified {
NONE :: -1;
BLACK :: 40;
RED :: 41;
GREEN :: 42;
YELLOW :: 43;
BLUE :: 44;
MAGENTA :: 45;
CYAN :: 46;
WHITE :: 47;
EXTEND :: 48; // 5;<Term_Color_Table> OR 2;<r>;<g>;<b>
DEFAULT :: 49;
}
Term_Color_Table :: enum #specified {
NONE :: -1;
// Standard
ST_BLACK :: 0;
ST_RED :: 1;
ST_GREEN :: 2;
ST_YELLOW :: 3;
ST_BLUE :: 4;
ST_PURPLE :: 5;
ST_TEAL :: 6;
ST_GRAY :: 7;
// High Intensity
HI_GRAY :: 8;
HI_RED :: 9;
HI_GREEN :: 10;
HI_YELLOW :: 11;
HI_BLUE :: 12;
HI_PURPLE :: 13;
HI_TEAL :: 14;
HI_WHITE :: 15;
// Selected Subset
BLACK :: 16;
WHITE :: 231;
GRAY_DARK :: 234;
GRAY_MID :: 243;
GRAY_LIGHT :: 250;
BLUE_DARK :: 17;
BLUE_MID :: 21;
BLUE_LIGHT :: 45;
GREEN_DARK :: 22;
GREEN_MID :: 34;
GREEN_LIGHT :: 46;
RED_DARK :: 52;
RED_MID :: 124;
RED_LIGHT :: 196;
ROSE_DARK :: 163;
ROSE_MID :: 201;
ROSE_LIGHT :: 213;
MINT_DARK :: 35;
MINT_MID :: 78;
MINT_LIGHT :: 84;
VIOLET_DARK :: 53;
VIOLET_MID :: 93;
VIOLET_LIGHT :: 141;
ORANGE_DARK :: 166;
ORANGE_MID :: 202;
ORANGE_LIGHT :: 214;
YELLOW_DARK :: 220;
YELLOW_MID :: 226;
YELLOW_LIGHT :: 228;
}
paint :: (
str: string,
style: Term_Text_Style = .RESET,
fg: Term_Color_Foreground = .NONE,
bg: Term_Color_Background = .NONE,
no_termination := false
)
-> string, int
{
a, b := base_paint(str, .[style], fg, bg, "", "", no_termination);
return a, b;
}
paint :: (
str: string,
style: []Term_Text_Style = .[],
fg: Term_Color_Foreground = .NONE,
bg: Term_Color_Background = .NONE,
no_termination := false
)
-> string, int
{
a, b := base_paint(str, style, fg, bg, "", "", no_termination);
return a, b;
}
paint_ex :: (
str: string,
style: Term_Text_Style = .RESET,
fg_color: Term_Color_Table = .NONE,
bg_color: Term_Color_Table = .NONE,
no_termination := false
)
-> string, int
{
a, b := base_paint(str, .[style], fg_color, bg_color,
TERM_FOREGROUND_COLOR_FROM_EXT_TABLE,
TERM_BACKGROUND_COLOR_FROM_EXT_TABLE,
no_termination
);
return a, b;
}
paint_ex :: (
str: string,
style: Term_Text_Style = .RESET,
fg_color: int = -1,
bg_color: int = -1,
no_termination := false
)
-> string, int
{
a, b := base_paint(str, .[style], fg_color, bg_color,
TERM_FOREGROUND_COLOR_FROM_EXT_TABLE,
TERM_BACKGROUND_COLOR_FROM_EXT_TABLE,
no_termination
);
return a, b;
}
paint_ex :: (
str: string,
style: []Term_Text_Style = .[],
fg_color: int = -1,
bg_color: int = -1,
no_termination := false
)
-> string, int
{
a, b := base_paint(str, style, fg_color, bg_color,
TERM_FOREGROUND_COLOR_FROM_EXT_TABLE,
TERM_BACKGROUND_COLOR_FROM_EXT_TABLE,
no_termination
);
return a, b;
}
paint_ex :: (
str: string,
style: []Term_Text_Style = .[],
fg_color: Term_Color_Table = .NONE,
bg_color: Term_Color_Table = .NONE,
no_termination := false
)
-> string, int
{
a, b := base_paint(str, style, fg_color, bg_color,
TERM_FOREGROUND_COLOR_FROM_EXT_TABLE,
TERM_BACKGROUND_COLOR_FROM_EXT_TABLE,
no_termination
);
return a, b;
}
paint_ex_custom :: (
str: string,
style: Term_Text_Style = .RESET,
fg_color: $A, // TODO: type??
bg_color: $B, // TODO: type??
no_termination := false
)
-> string, int
{
a, b := base_paint(str, .[style], fg_color, bg_color,
TERM_FOREGROUND_COLOR_FROM_EXT_TABLE,
TERM_BACKGROUND_COLOR_FROM_EXT_TABLE,
no_termination
);
return a, b;
}
paint_rgb :: (
str: string,
style: Term_Text_Style = .RESET,
fg_rgb: Term_Rgb,
bg_rgb: Term_Rgb,
no_termination := false
)
-> string, int
{
a, b := base_paint_rgb(str, .[style], fg_rgb, bg_rgb, no_termination);
return a, b;
}
paint_rgb :: (
str: string,
style: []Term_Text_Style = .[],
fg_rgb: Term_Rgb,
bg_rgb: Term_Rgb,
no_termination := false
)
-> string, int
{
a, b := base_paint_rgb(str, style, fg_rgb, bg_rgb, no_termination);
return a, b;
}
paint_raw :: (codes: string, str: string, no_termination: bool) -> string, int {
out: string;
if no_termination {
out = sprint(
TERM_ESCAPE_START,
codes,
str
);
} else {
out = sprint(
#run -> string { return tprint("%%", TERM_ESCAPE_START, TERM_ESCPAE_RESET); },
codes,
str
);
}
return out, abs(out.count - str.count);
}
paint_reset :: () -> string, int {
out := TERM_ESCPAE_RESET;
return out, out.count;
}
#scope_file;
build_style_str :: (style: []Term_Text_Style) -> string {
buf_style: [..]string;
for style array_add(*buf_style, sprint("%", cast(int)it));
s_style := join(.. buf_style, ";");
return trim_right(s_style, ";");
}
to_term_code_args :: () -> string, int #expand {
s := join(.. `buf, ";");
s = trim_right(s, ";");
a, b := paint_raw(s, `str, `no_termination);
return a, b;
}
buffer_add_term_codes :: (color_type: string, term_color_code: int) #expand {
`buf[`count] = sprint("%0%", color_type, term_color_code); `count += 1;
}
buffer_add_style :: () #expand {
if `style.count > 0 {
s_style := build_style_str(`style);
`buf[`count] = s_style;
`count += 1;
}
}
base_paint :: (
str: string,
style: []Term_Text_Style,
fg: int,
bg: int,
fg_code: string,
bg_code: string,
no_termination := false
)
-> string, int
{
push_allocator(temp);
buf: [3]string;
count: int;
buffer_add_style();
if fg != -1 { buffer_add_term_codes(fg_code, fg); }
if bg != -1 { buffer_add_term_codes(bg_code, bg); }
if count == 0 {
a, b := paint_raw("", str, no_termination);
return copy_string(a,, context.default_allocator), b;
}
a, b := to_term_code_args();
return copy_string(a,, context.default_allocator), b;
}
base_paint :: (
str: string,
style: []Term_Text_Style,
fg: $A, // TODO(adam, 5): This is garbage!
bg: $B, // TODO(adam, 5): This is garbage!
fg_code: string,
bg_code: string,
no_termination := false
)
-> string, int
{
push_allocator(temp);
buf: [3]string;
count: int;
buffer_add_style();
if fg != .NONE { buffer_add_term_codes(fg_code, cast(int)fg); }
if bg != .NONE { buffer_add_term_codes(bg_code, cast(int)bg); }
if count == 0 {
a, b := paint_raw("", str, no_termination);
return copy_string(a,, context.default_allocator), b;
}
a, b := to_term_code_args();
return copy_string(a,, context.default_allocator), b;
}
base_paint_rgb :: (
str: string,
style: []Term_Text_Style,
fg_rgb: Term_Rgb,
bg_rgb: Term_Rgb,
no_termination := false
)
-> string, int
{
push_allocator(temp);
buf: [3]string;
if style.count > 0 {
buf[0] = build_style_str(style);
}
buf[1] = sprint("38;2;%;%;%", fg_rgb.r, fg_rgb.g, fg_rgb.b);
buf[2] = sprint("48;2;%;%;%", bg_rgb.r, bg_rgb.g, bg_rgb.b);
s := join(.. buf, ";");
s = trim_right(s, ";");
a, b := paint_raw(s, str, no_termination);
return copy_string(a,, context.default_allocator), b;
}
using,only(array_add, copy_string, sprint, tprint, temp, push_allocator) Basic :: #import "Basic";
using,only(join, trim_right) String :: #import "String";
using,only(abs) Math :: #import "Math";
/*
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2026 Adam Blazeowsky
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------
*/
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c5bb2a3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,72 @@
# Extension for the Jai Standard Library
A collection of high-level APIs.
Note: This library reached not 1.0.0 yet. APIs will change.
## Targets
Currently, only Linux support.
## Modules
```
| Module Name | Description | Dependency |
+----------------+--------------------------------------------+------------+
| 0x2_Bitpack | Packing multiple numeric values into one | - |
| 0x2_Colored | HSL <> RGB <> Hex conversion | 0x2_Math |
| 0x2_Crashed | Crash handler which provides location info | addr2line |
| 0x2_Datefmt | Python-like date formatter | - |
| 0x2_Lexer | It lexes bytes | - |
| 0x2_Math | Things I missed in the stdlib | - |
| 0x2_Qtrace | Very small and simple profiler | - |
| 0x2_Rectcut | Building UI layouts by dividing rectangles | - |
| 0x2_Stringpad | Pads a string with any character | - |
| 0x2_Termcolors | Colors for your terminal | - |
```
## Usage
Copy what you need into your projects `modules` directory. Be aware, that some `0x2` modules
might depend on other `0x2` modules.
For understanding how those modules work, you can visit the `test` directory and peek at the
individual files.
## About
### Crashed
Currently Jai does not provide location information in their stack trace.
This module by [solarium.technology](https://solarium.technology) provides it.
Note: You need `addr2line` from `binutils`.
### Rectcut
Instead of doing some common layout voodoo, you "cut" your layout into rectangles which depend
on each other and are responsive to changes.
I found the idea at [halt.software](https://halt.software/p/rectcut-for-dead-simple-ui-layouts),
[solarium.technology](https://solarium.technology) implemented it in Jai,
and I modified it further.
## Compiler Version
```
beta 0.2.030, built on 2 July 2026.
```
## License
All modules provide two licenses (always at the end of the file), MIT or Public Domain you can
choose from.
This is inspired from [stb](https://github.com/nothings/stb/blob/master/docs/why_public_domain.md).
## LLM
All bugs are made by a human.
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..27e31c1
--- /dev/null
+++ b/VERSION
@@ -0,0 +1,13 @@
+Version: 0.0.1
Type: Semver
The first line will never change in shape, so you can safely parse it.
It will be always structured as:
[PLUS SIGN] "Version" [COLON] [SPACE] [DIGITS] [DOT] [DIGITS] [DOT] [DIGITS] [UNIX NEWLINE]
If it ever breaks send me an angry email to `dev [at] ptrace [dot] dev`
Well, expect you're on Windows and it decides to convert newlines to `\r\n` ¯\_(ツ)_/¯
diff --git a/desc.gt b/desc.gt
new file mode 100644
index 0000000..16bfc24
--- /dev/null
+++ b/desc.gt
@@ -0,0 +1 @@
Library extension for Jai
diff --git a/tests/assertion.jai b/tests/assertion.jai
new file mode 100644
index 0000000..b5b32e2
--- /dev/null
+++ b/tests/assertion.jai
@@ -0,0 +1,42 @@
ASS_CODE :: #code {
log_error("\n%: Test failed", loc);
log_error("-------------------");
log_error(":Subject A\n%\n\n", a);
log_error(":Subject B\n%\n\n", b);
`return false;
}
ass :: (comp: bool, loc := #caller_location) #expand {
if !comp {
log_error("\n%: Test failed", loc);
`return false;
}
}
ass :: (a: $T, b: T, loc := #caller_location) #expand {
if a != b {
#insert,scope() ASS_CODE;
}
}
/** Cannot use `[]$T, []T` here, because Jai cannot resolve the overloads then */
ass :: (a: []int, b: []int, loc := #caller_location) #expand {
if a.count != b.count {
#insert,scope() ASS_CODE;
}
for a if it != b[it_index] {
#insert,scope() ASS_CODE;
}
}
#scope_file
using,only(log_error) Basic :: #import "Basic";
diff --git a/tests/bitpack.jai b/tests/bitpack.jai
new file mode 100644
index 0000000..8e7e5e4
--- /dev/null
+++ b/tests/bitpack.jai
@@ -0,0 +1,21 @@
run :: () -> bool {
packed := bits_pack(u8, 1, 2, 3, 4);
unpacked := bits_unpack(packed);
ass(unpacked, int.[1, 2, 3, 4]);
unpacked_by_index := bits_unpack_by_index(packed, 2);
ass(unpacked_by_index, 3);
return true;
}
#import "Basic";
#import,file "../0x2_Bitpack.jai";
#load "assertion.jai";
diff --git a/tests/colored.jai b/tests/colored.jai
new file mode 100644
index 0000000..d4e22f2
--- /dev/null
+++ b/tests/colored.jai
@@ -0,0 +1,65 @@
run :: () -> bool {
/** Test: HSL routines */
hsl_string := "154 87 53";
hsl, ok := string_to_hsl(hsl_string);
ass(ok);
rgb_hex := hsl_to_rgb_hex(hsl_string);
ass(rgb_hex, "#1FEF95");
rgb := hsl_to_rgb(hsl);
ass(rgb, RGB.{ 31, 239, 149 });
h, s, l := hsl_to_integers(hsl);
hsl = integers_to_hsl(h, s, l);
ass(hsl, HSL.{ 154, 87, 53 });
/** Test: RGB routines */
rgb_string := "#C0FFEE";
rgb, ok = string_to_rgb(rgb_string);
ass(ok);
hsl = rgb_to_hsl(rgb);
ass(hsl, HSL.{ 164, 100, 88 });
r, g, b := rgb_to_integers(rgb);
rgb = integers_to_rgb(r, g, b);
ass(rgb, RGB.{ 192, 255, 238 });
return true;
}
#scope_file
ass :: (a: RGB, b: RGB, loc := #caller_location) -> bool #expand {
ok := true;
ok &= a.r == b.r;
ok &= a.g == b.g;
ok &= a.b == b.b;
if !ok then #insert,scope() ASS_CODE;
}
ass :: (a: HSL, b: HSL, loc := #caller_location) -> bool #expand {
ok := true;
ok &= a.h == b.h;
ok &= a.s == b.s;
ok &= a.l == b.l;
if !ok then #insert,scope() ASS_CODE;
}
#import "Basic";
#import "Print_Vars";
#import,file "../0x2_Colored.jai";
#load "assertion.jai";
diff --git a/tests/datefmt.jai b/tests/datefmt.jai
new file mode 100644
index 0000000..2b092ec
--- /dev/null
+++ b/tests/datefmt.jai
@@ -0,0 +1,25 @@
run :: () -> bool {
/** https://apollo.ptrace.dev */
ts := seconds_to_apollo(1801023812);
t := format_date(ts, "%Y-%M-%D %h:%m:%s.%f");
ass(t, "2026-08-16 00:41:11.0");
t = format_date(ts, "%Y-%M-%D %%g \\ --asdjnkld12G%hD3o4fjmp2150 %h_%_%mR8889%s?%f");
ass(t, "2026-08-16 %%g \\ --asdjnkld12G00D3o4fjmp2150 00_%_41R888911?0");
return true;
}
#scope_file
#import "Basic";
#import,file "../0x2_Datefmt.jai";
#load "assertion.jai";
diff --git a/tests/lexer.jai b/tests/lexer.jai
new file mode 100644
index 0000000..0aa86ee
--- /dev/null
+++ b/tests/lexer.jai
@@ -0,0 +1,106 @@
run :: () -> bool {
using lexer_state: My_Lexer_State;
source = TEST;
while !is_at_end() {
char := advance();
if char == {
case "[";
state = .OPTION;
inner := consume_till("]");
if !inner continue;
advance_by(inner);
// Do stuff
current_char := u8_to_str(*peek());
ass(current_char, "]");
ass(inner, "Thing1:Something");
case "{";
state = .WEIRD;
inner := consume_till("}");
if !inner continue;
advance_by(inner);
// Do stuff
current_char := u8_to_str(*peek());
ass(current_char, "}");
ass(inner, " Something ");
case "(";
state = .VAL;
inner := consume_till("+");
if !inner then report_error(
lexer_state,
"lexer.jai",
"If you can see this, the test was %\n%",
"successful!",
1234567890
);
current_char := u8_to_str(*peek());
ass(current_char, "V");
}
}
return true;
}
#scope_file
TOKEN_ERROR :: "ERROR";
TEST :: #string STR_END
[Thing1:Something]
key1 = "123"
key2 = "abc"
AAAAAAAAAAAAAAAAAAAAAAAAA \ BBBBBBBBBBBBBBBBBBBBBBB
{ Something }:{ Things
&&
(Val: ERROR)
STR_END;
State :: enum {
ERROR;
NONE;
OPTION;
WEIRD;
VAL;
EOF;
}
My_Lexer_State :: struct {
index: int;
source: string;
state: State;
}
#import "Basic";
#import "Print_Vars";
#import "String";
#import,file "../0x2_Lexer.jai";
#load "assertion.jai";
diff --git a/tests/rectcut.jai b/tests/rectcut.jai
new file mode 100644
index 0000000..0d43001
--- /dev/null
+++ b/tests/rectcut.jai
@@ -0,0 +1,55 @@
run :: () -> bool {
root := Rectangle.{
0.0,
0.0,
1000.0,
1000.0,
};
t := rcut_from_right(*root, 0.5);
ass(t, Rectangle.{ 500.0, 0.0, 500.0, 1000.0 });
t = rcut_from_left(*root, 0.1);
ass(t, Rectangle.{ 0, 0, 50, 1000 });
t = rcut_from_top(*root, 0.8);
ass(t, Rectangle.{ 50, 0, 450, 800 });
t = rcut_from_bottom(*root, 0.34);
ass(t, Rectangle.{ 50, 932, 450, 68 });
return true;
}
#scope_file
Rectangle :: struct {
x: float; /** top-left corner position x */
y: float; /** top-left corner position y */
width: float;
height: float;
}
ass :: (a: Rectangle, b: Rectangle, loc := #caller_location) #expand {
ok := true;
ok &= a.x == b.x;
ok &= a.y == b.y;
ok &= a.width == b.width;
ok &= a.height == b.height;
if !ok then #insert,scope() ASS_CODE;
}
#import "Basic";
#import "Print_Vars";
#import,file "../0x2_Rectcut.jai";
#load "assertion.jai";
diff --git a/tests/run_tests.jai b/tests/run_tests.jai
new file mode 100644
index 0000000..91bd816
--- /dev/null
+++ b/tests/run_tests.jai
@@ -0,0 +1,59 @@
MEMORY_DEBUGGER :: false;
HELP :: #string STR_END
Commands:
-tc Run visual test for `Termcolors`
STR_END;
main :: () {
#if MEMORY_DEBUGGER defer report_memory_leaks();
crash.opinionated_init();
args := get_command_line_arguments();
if array_find(args, "help") || array_find(args, "-help") || array_find(args, "-h") {
log("%", HELP);
return;
}
if array_find(args, "-tc") {
tc.tests_visual();
return;
}
ok := true;
{
qtrace("Test Suite", color=.BLUE);
ok &= bp.run();
ok &= rc.run();
ok &= cl.run();
ok &= df.run();
ok &= sp.run();
ok &= lx.run();
}
log("\n---------------------------------------------------------");
if ok then log("All tests passed"); else log_error("Tests failed");
log("\n");
}
#import "Basic"()(MEMORY_DEBUGGER = MEMORY_DEBUGGER);
tc :: #import,file "termcolors.jai";
bp :: #import,file "bitpack.jai";
rc :: #import,file "rectcut.jai";
cl :: #import,file "colored.jai";
df :: #import,file "datefmt.jai";
sp :: #import,file "stringpad.jai";
lx :: #import,file "lexer.jai";
#import,file "../0x2_Qtrace.jai";
crash :: #import,file "../0x2_Crashed.jai";
diff --git a/tests/stringpad.jai b/tests/stringpad.jai
new file mode 100644
index 0000000..49a12f5
--- /dev/null
+++ b/tests/stringpad.jai
@@ -0,0 +1,30 @@
run :: () -> bool {
foo := "FooBar";
padding := 20;
char := ".";
t := string_pad_left(foo, padding, char);
ass(t, "..............FooBar");
t = string_pad_right(foo, padding, char);
ass(t, "FooBar..............");
t = string_pad_lr(foo, padding, char);
ass(t, ".......FooBar.......");
return true;
}
#scope_file
#import,file "../0x2_Stringpad.jai";
#load "assertion.jai";
diff --git a/tests/termcolors.jai b/tests/termcolors.jai
new file mode 100644
index 0000000..781ca0e
--- /dev/null
+++ b/tests/termcolors.jai
@@ -0,0 +1,65 @@
tests_visual :: () {
log("--- paint() -----------------------------");
log(paint("1 Plain"));
print("\n");
log(paint("2 Bold, fg Black", .BOLD, .BLACK));
print("\n");
log(paint("3 fg Black", .RESET, .BLACK));
print("\n");
log(paint("4 Bold, fg Black, bg White", .BOLD, .BLACK, .WHITE));
print("\n");
log(paint("5 Bold, Underline, Italic, fg Black, bg White", .[.BOLD, .UNDERLINE, .ITALIC], .BLACK, .WHITE));
print("\n\n");
log("--- paint_ex() -----------------------------");
log(paint_ex("6 Underline, fg GreenDark, bg OrangeLight", .UNDERLINE, .GREEN_DARK, .ORANGE_LIGHT));
print("\n");
log(paint_ex("7 Underline, fg GreenDark", .UNDERLINE, .GREEN_DARK));
print("\n");
log(paint_ex("8 Bold, Underline, Italic, fg GreenDark, bg OrangeLight", .[.BOLD, .UNDERLINE, .ITALIC], .GREEN_DARK, .ORANGE_LIGHT));
print("\n");
log(paint_ex("9 Underline, fg 84, bg 124", .UNDERLINE, 84, 124));
print("\n");
log(paint_ex("10 Bold, Underline, Italic, 84, 124", .[.BOLD, .UNDERLINE, .ITALIC], 84, 124));
print("\n\n");
log("--- paint_ex_custom() -----------------------------");
My_Colors :: enum #specified {
NONE :: -1;
COL1 :: 95;
COL2 :: 201;
}
log(paint_ex_custom("11 Underline, fg Custom, bg Custom", .UNDERLINE, My_Colors.COL1, My_Colors.COL2));
print("\n\n");
log("--- paint_rgb() -----------------------------");
log(paint_rgb("12 Underline, fg rgb(255,0,0), bg rgb(0,0,255)", .UNDERLINE, .{ 255, 0, 0 }, .{ 0, 0, 255 }));
print("\n");
log(paint_rgb("13 Bold, Underline, Italic, fg rgb(255,0,0), bg rgb(0,0,255)", .[.BOLD, .UNDERLINE, .ITALIC], .{ 255, 0, 0 }, .{ 0, 0, 255 }));
print("\n\n");
log("--- paint_raw() -----------------------------");
log(paint_raw("1;3;32;45", "14 tbd tbd tbd", no_termination = false));
print("\n\n");
}
#scope_file
#import "Basic";
#import,file "../0x2_Termcolors.jai";