Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
81ef85e1bf
15 changed files with 1861 additions and 373 deletions
1
TODO
1
TODO
|
|
@ -69,6 +69,7 @@
|
|||
070 [x, bug] figure out why [meta] `for_expansion` procs aren't usable when import is namespaced
|
||||
071 [math] basic shape collision detection (circle, rect, line)
|
||||
072 [timestep] create drop-in fixed timestep module
|
||||
074 [hash] fix murmur32 not properly hashing pointers (it takes a pointer to a local which causes collisions)
|
||||
|
||||
|
||||
*** DONE ***
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ append :: inline (arr: *[..]$T) -> *T {
|
|||
return basic.array_add(arr,, allocator = arr.allocator);
|
||||
}
|
||||
|
||||
resize :: inline (arr: *[..]$T, new_size: int) {
|
||||
if new_size <= arr.allocated return;
|
||||
basic.array_reserve(arr, new_size,, allocator = arr.allocator);
|
||||
resize :: inline (arr: *[..]$T, new_count: int) {
|
||||
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 {
|
||||
|
|
|
|||
45
bytes/buffer.jai
Normal file
45
bytes/buffer.jai
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
Buffer :: struct {
|
||||
allocator: Allocator;
|
||||
|
||||
data: [..]u8;
|
||||
count: int;
|
||||
}
|
||||
|
||||
append :: inline (buf: *Buffer, ptr: *void, count: int) {
|
||||
free_space := ensure_buffer_has_room(buf, count);
|
||||
memcpy(free_space, ptr, count);
|
||||
buf.count += count;
|
||||
}
|
||||
|
||||
append :: inline (buf: *Buffer, data: []u8) {
|
||||
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;
|
||||
|
||||
array :: #import "jc/array";
|
||||
|
||||
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.run("basic operations", t => {
|
||||
buf: Buffer;
|
||||
});
|
||||
}
|
||||
9
bytes/module.jai
Normal file
9
bytes/module.jai
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#module_parameters(RUN_TESTS := false);
|
||||
|
||||
#load "buffer.jai";
|
||||
|
||||
#scope_module;
|
||||
|
||||
#if RUN_TESTS {
|
||||
test :: #import "jc/test";
|
||||
}
|
||||
|
|
@ -8,10 +8,7 @@ murmur32 :: inline (s: string, seed: u32 = MurMur_Seed) -> u32 {
|
|||
}
|
||||
|
||||
murmur32 :: inline (x: $T, seed: u32 = MurMur_Seed) -> u32 {
|
||||
d: []u8 = ---;
|
||||
d.data = cast(*u8)*x;
|
||||
d.count = size_of(T);
|
||||
return murmur32(d.data, d.count, seed);
|
||||
return murmur32(*x, size_of(T), seed);
|
||||
}
|
||||
|
||||
murmur32 :: (key: *void, len: int, seed: u32 = MurMur_Seed) -> u32 {
|
||||
|
|
|
|||
1
jc.jai
Symbolic link
1
jc.jai
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
jc.jai/
|
||||
|
|
@ -25,7 +25,7 @@ init :: (kv: *Kv, allocator: Allocator) {
|
|||
}
|
||||
|
||||
get :: (kv: *Kv, key: kv.Key) -> kv.Value, bool {
|
||||
slot, ok := find_slot(kv, kv.hash_proc(key));
|
||||
slot, ok := find_slot(kv, get_hash(kv, key));
|
||||
if !ok {
|
||||
return mem.zero_of(kv.Value), false;
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ get :: (kv: *Kv, key: kv.Key) -> kv.Value, bool {
|
|||
}
|
||||
|
||||
set :: (kv: *Kv, key: kv.Key, value: kv.Value) {
|
||||
hash := kv.hash_proc(key);
|
||||
hash := get_hash(kv, key);
|
||||
slot, exists := find_slot(kv, hash);
|
||||
if !exists {
|
||||
slot = create_or_reuse_slot(kv);
|
||||
|
|
@ -45,9 +45,14 @@ set :: (kv: *Kv, key: kv.Key, value: kv.Value) {
|
|||
slot.value = value;
|
||||
}
|
||||
|
||||
exists :: (kv: *Kv, key: kv.Key) -> bool {
|
||||
_, exists := find_slot(kv, get_hash(kv, key));
|
||||
return exists;
|
||||
}
|
||||
|
||||
// @note(judah): we use 'delete' instead of 'remove' because it's a keyword...
|
||||
delete :: (kv: *Kv, key: kv.Key) -> kv.Value, bool {
|
||||
slot, ok, idx := find_slot(kv, kv.hash_proc(key));
|
||||
slot, ok, idx := find_slot(kv, get_hash(kv, key));
|
||||
if !ok return mem.zero_of(kv.Value), false;
|
||||
|
||||
last_value := slot.value;
|
||||
|
|
@ -74,6 +79,12 @@ for_expansion :: (kv: *Kv, body: Code, flags: For_Flags) #expand {
|
|||
|
||||
#scope_file;
|
||||
|
||||
get_hash :: inline (kv: *Kv, key: kv.Key) -> u32 {
|
||||
hash := kv.hash_proc(key);
|
||||
basic.assert(hash != kv.invalid_hash, "key % collided with invalid hash marker (%)", key, kv.invalid_hash);
|
||||
return hash;
|
||||
}
|
||||
|
||||
find_slot :: (kv: *Kv, hash: u32) -> *kv.Slot, bool, int {
|
||||
for * kv.slots if it.hash == hash {
|
||||
return it, true, it_index;
|
||||
|
|
@ -122,6 +133,8 @@ mem :: #import "jc/memory";
|
|||
array :: #import "jc/array";
|
||||
hash :: #import "jc/hash";
|
||||
|
||||
basic :: #import "Basic"; // @future
|
||||
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// TESTS
|
||||
|
|
|
|||
81
math/mat.jai
81
math/mat.jai
|
|
@ -400,3 +400,84 @@ inverse :: (m: Mat4) -> Mat4 {
|
|||
|
||||
return transpose(r);
|
||||
}
|
||||
|
||||
|
||||
#scope_file;
|
||||
|
||||
#if RUN_TESTS #run {
|
||||
test.run(basic.tprint("%: Mat2", UNITS), t => {
|
||||
{
|
||||
identity := m2_identity;
|
||||
a: Mat2 = Mat2.{.[1, 2, 3, 4]};
|
||||
test.expect(t, a*identity == a);
|
||||
}
|
||||
{
|
||||
rotator := rotation_mat2(from_rad(PI));
|
||||
v2 := v2f(1.0, 0.5);
|
||||
v2 = rotator*v2;
|
||||
test.expect(t, v2_eq(v2, v2f(-1.0, -0.5)));
|
||||
v2 = rotator*v2;
|
||||
test.expect(t, v2_eq(v2, v2f(1.0, 0.5)));
|
||||
}
|
||||
{
|
||||
m := m2(1.0, 3.0, 2.0, 2.0);
|
||||
det := determinate(m);
|
||||
test.expect(t, det == -4);
|
||||
}
|
||||
{
|
||||
rotator := rotation_mat2(from_rad(PI));
|
||||
inv_rotator := inverse(rotator);
|
||||
v2 := v2f(0.1, 1.0);
|
||||
|
||||
test.expect(t, inv_rotator*rotator == m2_identity);
|
||||
test.expect(t, inv_rotator*(rotator*v2) == v2);
|
||||
v2 = rotator*v2;
|
||||
v2 = inv_rotator*v2;
|
||||
}
|
||||
});
|
||||
|
||||
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]};
|
||||
test.expect(t, a*identity == a);
|
||||
}
|
||||
{
|
||||
UP_VECTOR :: Vec3.{.[0.0, 1.0, 0.0]};
|
||||
camera := v3f(1.0, 0.0, 1.0);
|
||||
looking_at := v3f(0.0, 0.0, 0.0);
|
||||
look_at := make_look_at(camera, looking_at, UP_VECTOR);
|
||||
}
|
||||
{
|
||||
translator := translate(v3f(10.0, 5.0, 2.0));
|
||||
v3 := v3f(1.0, 2.0, 1.0);
|
||||
v4 := v4f(v3, 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;
|
||||
test.expect(t, v4_eq(v4, v4f(-1.0, 0.5, -0.1, 1.0)));
|
||||
v4 = rotator*v4;
|
||||
test.expect(t, v4_eq(v4, v4f(1.0, 0.5, 0.1, 1.0)));
|
||||
}
|
||||
{
|
||||
rotator_x := rotation_mat4(v3f(1.0, 0.0, 0.0), from_rad(0.4*PI));
|
||||
rotator_y := rotation_mat4(v3f(0.0, 1.0, 0.0), from_rad(PI));
|
||||
camera_rotator := rotator_x*rotator_y;
|
||||
det := determinate(camera_rotator);
|
||||
}
|
||||
{
|
||||
rotator := rotation_mat4(v3f(0.0, 0.0, 1.0), from_rad(PI));
|
||||
inv_rotator := inverse(rotator);
|
||||
v4 := v4f(0.1, 1.0, 0.0, 1.0);
|
||||
|
||||
test.expect(t, inv_rotator*rotator == m4_identity);
|
||||
|
||||
v4 = rotator*v4;
|
||||
v4 = inv_rotator*v4;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
361
math/module.jai
361
math/module.jai
|
|
@ -30,360 +30,15 @@ F64_Min, F64_Max :: #run meta.lo_for(float64), #run meta.hi_for(float64);
|
|||
|
||||
#scope_module;
|
||||
|
||||
#if RUN_TESTS #run {
|
||||
test :: #import "jc/test";
|
||||
|
||||
vec2_tests();
|
||||
vec3_tests();
|
||||
vec4_tests();
|
||||
vecn_tests();
|
||||
mat2_tests();
|
||||
mat4_tests();
|
||||
quat_tests();
|
||||
rect_cut_tests();
|
||||
}
|
||||
|
||||
#scope_file;
|
||||
|
||||
meta :: #import "jc/meta";
|
||||
math :: #import "Math"; // @future
|
||||
basic :: #import "Basic"; // @future
|
||||
|
||||
math :: #import "Math";
|
||||
|
||||
test :: #import "jc/test";
|
||||
|
||||
vec2_tests :: () {
|
||||
dot_print("Vec2 % tests", UNITS);
|
||||
t: test.T;
|
||||
{
|
||||
a: Vec2 = v2f(0.0, 1.0);
|
||||
b: Vec2 = v2f(1.0, 2.0);
|
||||
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);
|
||||
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);
|
||||
test.expect(*t, c == v2f(1.0, -4.1));
|
||||
c = max(a, b);
|
||||
test.expect(*t, c == v2f(2.3, 3.6));
|
||||
}
|
||||
#if RUN_TESTS {
|
||||
test :: #import "jc/test";
|
||||
}
|
||||
|
||||
vec3_tests :: () {
|
||||
dot_print("Vec3 % tests", UNITS);
|
||||
t: test.T;
|
||||
{
|
||||
a: Vec3 = v3f(0.0, 1.0, 2.0);
|
||||
b: Vec3 = v3f(1.0, 2.0, 3.0);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
test.expect(*t, c == v3f(1.0, -3.6, 5.0));
|
||||
c = max(a, b);
|
||||
test.expect(*t, c == v3f(2.3, 4.1, 9.0));
|
||||
}
|
||||
}
|
||||
|
||||
vec4_tests :: () {
|
||||
dot_print("Vec4 % tests", UNITS);
|
||||
t: test.T;
|
||||
{
|
||||
a: Vec4 = v4f(2.25, 1.0, 2.0, 1.0);
|
||||
b: Vec4 = v4f(4.0, 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));
|
||||
}
|
||||
}
|
||||
|
||||
vecn_tests :: () {
|
||||
dot_print("VecN % tests", UNITS);
|
||||
t: test.T;
|
||||
{
|
||||
a: Vec(16, float);
|
||||
b: Vec(16, float);
|
||||
for *a {
|
||||
it.* = xx it_index;
|
||||
}
|
||||
for *b {
|
||||
it.* = xx(it_index + 1);
|
||||
}
|
||||
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]});
|
||||
|
||||
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]};
|
||||
basic.print(">> a1: %\n", a1);
|
||||
basic.print(">> ceil(a1): %\n", ceil(a1));
|
||||
basic.print(">> floor(a1): %\n", floor(a1));
|
||||
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]});
|
||||
|
||||
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
|
||||
it.* = -it.*;
|
||||
}
|
||||
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]});
|
||||
}
|
||||
}
|
||||
|
||||
mat2_tests :: () {
|
||||
dot_print("Mat2 % tests", UNITS);
|
||||
t: test.T;
|
||||
{
|
||||
identity := m2_identity;
|
||||
a: Mat2 = Mat2.{.[1, 2, 3, 4]};
|
||||
test.expect(*t, a*identity == a);
|
||||
}
|
||||
{
|
||||
basic.print("rotation matrix\n");
|
||||
rotator := rotation_mat2(from_rad(PI));
|
||||
v2 := v2f(1.0, 0.5);
|
||||
basic.print("rotating: %\n", v2);
|
||||
v2 = rotator*v2;
|
||||
basic.print("after rotate: %\n", v2);
|
||||
test.expect(*t, v2_eq(v2, v2f(-1.0, -0.5)));
|
||||
v2 = rotator*v2;
|
||||
basic.print("rotate back: %\n", v2);
|
||||
test.expect(*t, v2_eq(v2, v2f(1.0, 0.5)));
|
||||
}
|
||||
{
|
||||
basic.print("determinate\n");
|
||||
m := m2(1.0, 3.0, 2.0, 2.0);
|
||||
basic.print("m2: %\n", m);
|
||||
det := determinate(m);
|
||||
basic.print("determinate: %\n", det);
|
||||
test.expect(*t, det == -4);
|
||||
}
|
||||
{
|
||||
basic.print("inverse test\n");
|
||||
rotator := rotation_mat2(from_rad(PI));
|
||||
inv_rotator := inverse(rotator);
|
||||
v2 := v2f(0.1, 1.0);
|
||||
basic.print(" : %\n", rotator);
|
||||
basic.print("inv: %\n", inv_rotator);
|
||||
|
||||
basic.print("inv_mat*mat = %\n", inv_rotator*rotator);
|
||||
test.expect(*t, inv_rotator*rotator == m2_identity);
|
||||
|
||||
test.expect(*t, inv_rotator*(rotator*v2) == v2);
|
||||
basic.print("v2 : %\n", v2);
|
||||
v2 = rotator*v2;
|
||||
basic.print("rot : %\n", v2);
|
||||
v2 = inv_rotator*v2;
|
||||
basic.print("inv : %\n", v2);
|
||||
}
|
||||
}
|
||||
|
||||
mat4_tests :: () {
|
||||
dot_print("Mat4 % tests", UNITS);
|
||||
t: test.T;
|
||||
{
|
||||
identity := m4_identity;
|
||||
a: Mat4 = Mat4.{.[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]};
|
||||
test.expect(*t, a*identity == a);
|
||||
}
|
||||
{
|
||||
UP_VECTOR :: Vec3.{.[0.0, 1.0, 0.0]};
|
||||
camera := v3f(1.0, 0.0, 1.0);
|
||||
looking_at := v3f(0.0, 0.0, 0.0);
|
||||
look_at := make_look_at(camera, looking_at, UP_VECTOR);
|
||||
basic.print("lookat matrix: %\n", look_at);
|
||||
basic.print("lookat from 0,1,0: %\n", look_at*v4f(0.0, 1.0, 0.0, 1.0));
|
||||
}
|
||||
{
|
||||
translator := translate(v3f(10.0, 5.0, 2.0));
|
||||
v3 := v3f(1.0, 2.0, 1.0);
|
||||
v4 := v4f(v3, 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));
|
||||
}
|
||||
{
|
||||
basic.print("rotation matrix\n");
|
||||
rotator := rotation_mat4(v3f(0.0, 1.0, 0.0), from_rad(PI));
|
||||
v4 := v4f(1.0, 0.5, 0.1, 1.0);
|
||||
basic.print("rotating: %\n", v4);
|
||||
v4 = rotator*v4;
|
||||
basic.print("after rotate: %\n", v4);
|
||||
test.expect(*t, v4_eq(v4, v4f(-1.0, 0.5, -0.1, 1.0)));
|
||||
v4 = rotator*v4;
|
||||
basic.print("rotate back: %\n", v4);
|
||||
test.expect(*t, v4_eq(v4, v4f(1.0, 0.5, 0.1, 1.0)));
|
||||
}
|
||||
{
|
||||
rotator_x := rotation_mat4(v3f(1.0, 0.0, 0.0), from_rad(0.4*PI));
|
||||
rotator_y := rotation_mat4(v3f(0.0, 1.0, 0.0), from_rad(PI));
|
||||
camera_rotator := rotator_x*rotator_y;
|
||||
basic.print("big rotator: %\n", camera_rotator);
|
||||
det := determinate(camera_rotator);
|
||||
basic.print("determinate: %\n", det);
|
||||
}
|
||||
{
|
||||
basic.print("inverse test\n");
|
||||
rotator := rotation_mat4(v3f(0.0, 0.0, 1.0), from_rad(PI));
|
||||
inv_rotator := inverse(rotator);
|
||||
v4 := v4f(0.1, 1.0, 0.0, 1.0);
|
||||
basic.print(" : %\n", rotator);
|
||||
basic.print("inv: %\n", inv_rotator);
|
||||
|
||||
basic.print("inv_mat*mat = %\n", inv_rotator*rotator);
|
||||
test.expect(*t, inv_rotator*rotator == m4_identity);
|
||||
|
||||
basic.print("v4 : %\n", v4);
|
||||
v4 = rotator*v4;
|
||||
basic.print("rot : %\n", v4);
|
||||
v4 = inv_rotator*v4;
|
||||
basic.print("inv : %\n", v4);
|
||||
}
|
||||
}
|
||||
|
||||
quat_tests :: () {
|
||||
dot_print("Quaternion % tests", UNITS);
|
||||
t: test.T;
|
||||
{
|
||||
qi := quat_identity;
|
||||
basic.print("qi lensq: %\n", length_squared(qi));
|
||||
basic.print("qi len : %\n", length(qi));
|
||||
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));
|
||||
basic.print("q : %\n", q);
|
||||
basic.print("q2 : %\n", q2);
|
||||
basic.print("dot : %\n", dot(q, q2));
|
||||
basic.print("mul : %\n", q*q2);
|
||||
qc := conjugate(q);
|
||||
basic.print("conjugate : %\n", qc);
|
||||
inv_q := inverse(q);
|
||||
basic.print("inverse : %\n", inv_q);
|
||||
test.expect(*t, q*inv_q == qi);
|
||||
|
||||
q1 := quat(2, 0, 0, 0);
|
||||
q2 = quat(1, 1, -1, 0);
|
||||
basic.print("q1: %\n", q1);
|
||||
basic.print("q2: %\n", q2);
|
||||
basic.print("q1*q2: %\n", q1*q2);
|
||||
basic.print("dot(q2,q2): %\n", dot(q2, q2));
|
||||
basic.print("dot(q1,q2): %\n", dot(q1, q2));
|
||||
basic.print("q1*q2*conj(q1): %\n", q1*q2*conjugate(q1));
|
||||
c := q1*q2*conjugate(q1);
|
||||
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);
|
||||
basic.print("q: %\n", q);
|
||||
basic.print("p: %\n", p);
|
||||
c1 := q*quat(p, 0)*conjugate(q);
|
||||
basic.print("q*quat(p, 0)*conjugate(q): %\n", c1);
|
||||
c2 := q*p;
|
||||
basic.print("q*p : %\n", 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));
|
||||
|
||||
basic.print("quaternion to matrix rotation\n");
|
||||
q = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0));
|
||||
basic.print("q: %\n", q);
|
||||
m := rotation_mat4(q);
|
||||
print_mat4(m);
|
||||
p1 := v4f(2.0, 0.0, 0.0, 1.0);
|
||||
basic.print("p : %\n", p1);
|
||||
basic.print("p': %\n", m*p1);
|
||||
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));
|
||||
perc := 0.5;
|
||||
q = slerp(q1, q2, perc);
|
||||
basic.print("q1: %\n", q1);
|
||||
basic.print("q2: %\n", q2);
|
||||
basic.print("slerp q1, q2, %: %\n", perc, q);
|
||||
|
||||
q = rotation_quat(from_rad(PI/4.0), v3f(0.0, 0.0, 1.0));
|
||||
print_mat4(rotation_mat4(q));
|
||||
}
|
||||
}
|
||||
|
||||
rect_cut_tests :: () {
|
||||
r := make_rect(0, 0, 1920, 1080);
|
||||
menu_ribbon := cut_top(*r, 28);
|
||||
basic.print("menu_ribbon: %\n", menu_ribbon);
|
||||
basic.print("r': %\n", r);
|
||||
|
||||
status_bar := cut_bottom(*r, 16);
|
||||
basic.print("status_bar: %\n", status_bar);
|
||||
basic.print("r': %\n", r);
|
||||
|
||||
character_preview_panel := cut_right(*r, r.width/3);
|
||||
basic.print("character_preview_panel: %\n", character_preview_panel);
|
||||
basic.print("r': %\n", r);
|
||||
|
||||
text_box := cut_top(*r, r.height/2);
|
||||
basic.print("text_box: %\n", text_box);
|
||||
basic.print("r': %\n", r);
|
||||
|
||||
dialogue_choices := cut_left(*r, r.width/2);
|
||||
basic.print("dialogue_choices: %\n", dialogue_choices);
|
||||
basic.print("r': %\n", r);
|
||||
|
||||
dialogue_history := r;
|
||||
basic.print("dialogue_history: %\n", dialogue_history);
|
||||
}
|
||||
|
||||
dot_print :: (fmt: string, args: ..Any, width: int = 30) {
|
||||
dots := ".................................................................";
|
||||
str := basic.tprint(fmt, args);
|
||||
dots.count = width - str.count;
|
||||
basic.print("%0%", str, dots);
|
||||
basic.print("\n");
|
||||
}
|
||||
|
||||
check :: (cond: bool, loc := #caller_location) -> bool {
|
||||
if !cond {
|
||||
basic.print("\tTest failed at %:%\n", loc.fully_pathed_filename, loc.line_number);
|
||||
}
|
||||
return cond;
|
||||
}
|
||||
// @temp(judah): move these to the right files
|
||||
|
||||
v2_eq :: (a: Vec2, b: Vec2) -> bool {
|
||||
return float_eq(a.x, b.x) && float_eq(a.y, b.y);
|
||||
|
|
@ -399,13 +54,7 @@ v4_eq :: (a: Vec4, b: Vec4) -> bool {
|
|||
|
||||
// Smallest difference where a float is basically that value
|
||||
EPSILON :: 0.001;
|
||||
|
||||
float_eq :: (f: float, with: float) -> bool {
|
||||
return f > with - EPSILON && f < with + EPSILON;
|
||||
}
|
||||
|
||||
print_mat4 :: (m: Mat4) {
|
||||
basic.print("| % % % % |\n", m._00, m._01, m._02, m._03);
|
||||
basic.print("| % % % % |\n", m._10, m._11, m._12, m._13);
|
||||
basic.print("| % % % % |\n", m._20, m._21, m._22, m._23);
|
||||
basic.print("| % % % % |\n", m._30, m._31, m._32, m._33);
|
||||
}
|
||||
|
|
|
|||
141
math/vec.jai
141
math/vec.jai
|
|
@ -640,6 +640,141 @@ cross :: (a: Vec3, b: Vec3) -> Vec3 {
|
|||
|
||||
#scope_file
|
||||
|
||||
meta :: #import "jc/meta";
|
||||
math :: #import "Math"; // @future
|
||||
basic :: #import "Basic"; // @future
|
||||
#if RUN_TESTS #run,stallable {
|
||||
test.run(basic.tprint("%: Vec2", UNITS), t => {
|
||||
{
|
||||
a: Vec2 = v2f(0.0, 1.0);
|
||||
b: Vec2 = v2f(1.0, 2.0);
|
||||
|
||||
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);
|
||||
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);
|
||||
test.expect(t, c == v2f(1.0, -4.1));
|
||||
c = max(a, b);
|
||||
test.expect(t, c == v2f(2.3, 3.6));
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
test.expect(t, c == v3f(1.0, -3.6, 5.0));
|
||||
c = max(a, b);
|
||||
test.expect(t, c == v3f(2.3, 4.1, 9.0));
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
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.run(basic.tprint("%: VecN", UNITS), t => {
|
||||
a: Vec(16, float);
|
||||
b: Vec(16, float);
|
||||
for *a {
|
||||
it.* = xx it_index;
|
||||
}
|
||||
for *b {
|
||||
it.* = xx(it_index + 1);
|
||||
}
|
||||
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]});
|
||||
|
||||
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]};
|
||||
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]});
|
||||
|
||||
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.*;
|
||||
}
|
||||
|
||||
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.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);
|
||||
test.expect(t, q*inv_q == qi);
|
||||
|
||||
q1 := quat(2, 0, 0, 0);
|
||||
q2 = quat(1, 1, -1, 0);
|
||||
c := q1*q2*conjugate(q1);
|
||||
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;
|
||||
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);
|
||||
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));
|
||||
perc := 0.5;
|
||||
q = slerp(q1, q2, perc);
|
||||
q = rotation_quat(from_rad(PI/4.0), v3f(0.0, 0.0, 1.0));
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,13 +63,13 @@ align_to :: (ptr: int, align: int = Default_Align) -> int {
|
|||
}
|
||||
|
||||
init_or_zero :: inline (ptr: *$T, custom_init: (*T) = null) {
|
||||
init :: initializer_of(T);
|
||||
if custom_init != null {
|
||||
custom_init(ptr);
|
||||
}
|
||||
|
||||
#if init != null {
|
||||
inline init(ptr);
|
||||
initializer :: initializer_of(T);
|
||||
#if initializer {
|
||||
inline initializer(ptr);
|
||||
}
|
||||
else {
|
||||
memset(ptr, 0, size_of(T));
|
||||
|
|
|
|||
190
thirdparty/luajit/x/module.jai
vendored
Normal file
190
thirdparty/luajit/x/module.jai
vendored
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
#module_parameters(STATIC := true);
|
||||
|
||||
c_context: #Context;
|
||||
|
||||
#scope_export;
|
||||
|
||||
using #import,file "../module.jai"(STATIC = STATIC);
|
||||
|
||||
expose :: (L: *State, proc: $T, $caller_code := #caller_code, $loc := #caller_location)
|
||||
#modify {
|
||||
return T.(*Type_Info).type == .PROCEDURE, "expose must take a procedure";
|
||||
} #expand {
|
||||
#insert -> string {
|
||||
info := T.(*Type_Info_Procedure);
|
||||
code := compiler_get_nodes(caller_code);
|
||||
call := code.(*Code_Procedure_Call);
|
||||
|
||||
proc := call.arguments_sorted[1].(*Code_Ident);
|
||||
basic.assert(proc.kind == .IDENT, "must be an identifier", loc = loc);
|
||||
|
||||
body_builder: basic.String_Builder;
|
||||
args_builder: basic.String_Builder;
|
||||
call_builder: basic.String_Builder;
|
||||
rets_builder: basic.String_Builder;
|
||||
|
||||
if info.return_types.count != 0 {
|
||||
basic.append(*call_builder, "\t\t\t\t");
|
||||
|
||||
for info.return_types {
|
||||
basic.print_to_builder(*call_builder, "r%", it_index);
|
||||
if it_index < info.return_types.count -1 {
|
||||
basic.append(*call_builder, ", ");
|
||||
}
|
||||
}
|
||||
|
||||
basic.append(*call_builder, " := ");
|
||||
}
|
||||
|
||||
basic.append(*call_builder, "proc(");
|
||||
|
||||
for < info.argument_types {
|
||||
index := -(it_index + 1);
|
||||
basic.print_to_builder(*args_builder, "\t\t\ta% := %;\n", it_index, gen_stack_pull(it, index));
|
||||
|
||||
basic.print_to_builder(*call_builder, "a%", it_index);
|
||||
if it_index > 0 {
|
||||
basic.append(*call_builder, ", ");
|
||||
}
|
||||
}
|
||||
|
||||
basic.append(*call_builder, ");");
|
||||
|
||||
for info.return_types {
|
||||
basic.print_to_builder(*rets_builder, "\t\t\t\t%;", gen_stack_push(it, it_index));
|
||||
if it_index < info.return_types.count - 1 {
|
||||
basic.append(*rets_builder, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
return basic.sprint(#string END
|
||||
lua_name :: "%1";
|
||||
lua_wrapper :: (L: *State) -> s32 #c_call {
|
||||
%2
|
||||
push_context c_context {
|
||||
%3
|
||||
%4
|
||||
}
|
||||
|
||||
return %5;
|
||||
}
|
||||
END,
|
||||
proc.name,
|
||||
basic.builder_to_string(*args_builder),
|
||||
basic.builder_to_string(*call_builder),
|
||||
basic.builder_to_string(*rets_builder),
|
||||
info.return_types.count,
|
||||
);
|
||||
}
|
||||
|
||||
info := T.(*Type_Info_Procedure);
|
||||
pushcclosure(L, lua_wrapper, info.argument_types.count.(s32));
|
||||
setfield(L, LUA_GLOBALSINDEX, lua_name);
|
||||
}
|
||||
|
||||
|
||||
#scope_file;
|
||||
|
||||
gen_stack_pull :: (info: *Type_Info, index: int) -> string {
|
||||
if info.type == {
|
||||
case .BOOL;
|
||||
return basic.tprint("get_lua_bool(L, %)", index);
|
||||
case .INTEGER;
|
||||
return basic.tprint("tointeger(L, %)", index);
|
||||
case .FLOAT;
|
||||
return basic.tprint("tonumber(L, %)", index);
|
||||
case .STRING;
|
||||
return basic.tprint("get_lua_string(L, %)", index);
|
||||
case .STRUCT;
|
||||
return basic.tprint("get_lua_table(L, %, %)", info.(*Type_Info_Struct).name, index);
|
||||
|
||||
case;
|
||||
basic.assert(false, "% (%)", info.type, index);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
gen_stack_push :: (info: *Type_Info, index: int) -> string {
|
||||
if info.type == {
|
||||
case .BOOL;
|
||||
return basic.tprint("pushboolean(L, r%.(s32))", index);
|
||||
case .INTEGER;
|
||||
return basic.tprint("pushinteger(L, r%)", index);
|
||||
case;
|
||||
basic.assert(false, "% (%)", info.type, index);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
get_lua_string :: inline (L: *State, index: s32) -> string #c_call {
|
||||
len: u64;
|
||||
ptr := tolstring(L, index, *len);
|
||||
return string.{ data = ptr, count = xx len };
|
||||
}
|
||||
|
||||
get_lua_bool :: inline (L: *State, index: s32) -> bool #c_call {
|
||||
return toboolean(L, index) == 1;
|
||||
}
|
||||
|
||||
get_lua_table :: (L: *State, $T: Type, index: s32) -> T #c_call {
|
||||
push_context c_context {
|
||||
return get_lua_table(L, T.(*Type_Info_Struct), index).(*T).*;
|
||||
}
|
||||
}
|
||||
|
||||
temp_storage: [4096]u8;
|
||||
temp_offset: int;
|
||||
|
||||
get_lua_table :: (L: *State, info: *Type_Info_Struct, index: s32) -> *void {
|
||||
res := temp_storage.data + temp_offset;
|
||||
memset(res, 0, info.runtime_size);
|
||||
|
||||
temp_offset = (temp_offset + info.runtime_size) % temp_storage.count;
|
||||
|
||||
for info.members {
|
||||
defer settop(L, -2);
|
||||
pushlstring(L, it.name.data, xx it.name.count);
|
||||
rawget(L, -2);
|
||||
|
||||
vp: *void;
|
||||
if it.type.type == {
|
||||
case .BOOL;
|
||||
v := get_lua_bool(L, -1);
|
||||
vp = *v;
|
||||
case .INTEGER;
|
||||
v := tointeger(L, -1);
|
||||
if it.type.runtime_size == {
|
||||
case 1; vp = *(v.(u8, no_check));
|
||||
case 2; vp = *(v.(u16, no_check));
|
||||
case 4; vp = *(v.(u32, no_check));
|
||||
case 8; vp = *(v.(u64, no_check));
|
||||
}
|
||||
case .FLOAT;
|
||||
v := tonumber(L, -1);
|
||||
if it.type.runtime_size == {
|
||||
case 4; vp = *(v.(float32, no_check));
|
||||
case 8; vp = *(v.(float64, no_check));
|
||||
}
|
||||
case .STRING;
|
||||
v := get_lua_string(L, -1);
|
||||
vp = *v;
|
||||
case .STRUCT;
|
||||
v := get_lua_table(L, it.type.(*Type_Info_Struct), -1);
|
||||
vp = *v;
|
||||
|
||||
case;
|
||||
basic.assert(false, "% (%)", info.type, index);
|
||||
}
|
||||
|
||||
memcpy(res + it.offset_in_bytes, vp, it.type.runtime_size);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
basic :: #import "Basic"; // @future
|
||||
|
||||
#import "Compiler";
|
||||
320
vm/interp.jai
Normal file
320
vm/interp.jai
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
Interp :: struct {
|
||||
allocator: Allocator;
|
||||
|
||||
toplevel: []*Node;
|
||||
global: *Interp_Scope;
|
||||
}
|
||||
|
||||
Interp_Scope :: struct {
|
||||
parent: *Interp_Scope;
|
||||
bindings: kv.Kv(string, *Interp_Value);
|
||||
}
|
||||
|
||||
Interp_Value :: struct {
|
||||
kind: Kind;
|
||||
union {
|
||||
b: bool;
|
||||
i: s64;
|
||||
u: u64;
|
||||
f: float64;
|
||||
s: string;
|
||||
p: *void;
|
||||
proc: *Node_Procedure;
|
||||
val: *Interp_Value;
|
||||
}
|
||||
|
||||
Kind :: enum {
|
||||
none;
|
||||
nil;
|
||||
bool;
|
||||
int;
|
||||
float;
|
||||
string;
|
||||
pointer;
|
||||
procedure;
|
||||
value;
|
||||
}
|
||||
}
|
||||
|
||||
init :: (i: *Interp, allocator: Allocator) {
|
||||
value_nil = make_interp_value(i, .nil);
|
||||
|
||||
value_true = make_interp_value(i, .bool);
|
||||
value_true.b = true;
|
||||
|
||||
value_false = make_interp_value(i, .bool);
|
||||
value_false.b = false;
|
||||
|
||||
i.global = make_scope(null,, allocator = allocator);
|
||||
}
|
||||
|
||||
interp_program :: (i: *Interp) {
|
||||
for i.toplevel {
|
||||
interp_statement(i, it, i.global);
|
||||
}
|
||||
}
|
||||
|
||||
interp_statement :: (i: *Interp, stmt: *Node, scope: *Interp_Scope) {
|
||||
if stmt.kind == {
|
||||
case .variable;
|
||||
var := stmt.(*Node_Var);
|
||||
sym := var.symbol;
|
||||
basic.assert(!kv.exists(*scope.bindings, sym.str), "redeclaring symbol '%'", sym.str); // @errors
|
||||
|
||||
value := value_nil;
|
||||
if var.value_expr != null {
|
||||
value = interp_expression(i, var.value_expr, scope);
|
||||
basic.assert(value != null); // @errors
|
||||
}
|
||||
|
||||
kv.set(*scope.bindings, sym.str, value);
|
||||
|
||||
case .assign;
|
||||
assign := stmt.(*Node_Assign);
|
||||
|
||||
src := interp_expression(i, assign.src, scope);
|
||||
basic.assert(src != null); // @errors
|
||||
|
||||
dst := interp_lvalue(i, assign.dst, scope);
|
||||
basic.assert(dst != null); // @errors
|
||||
basic.assert(dst.kind == .value); // @errors
|
||||
|
||||
// @todo: typechecking
|
||||
basic.assert(assign.op.kind == .equal, "unexpected assign op: %", assign.op.kind);
|
||||
dst.val.* = src.*;
|
||||
|
||||
case .procedure;
|
||||
proc := stmt.(*Node_Procedure);
|
||||
sym := proc.symbol;
|
||||
basic.assert(!kv.exists(*scope.bindings, sym.str), "redeclaring procedure '%'", sym.str);
|
||||
|
||||
value := make_interp_value(i, .procedure);
|
||||
value.proc = proc;
|
||||
kv.set(*scope.bindings, sym.str, value);
|
||||
|
||||
case .print;
|
||||
print := stmt.(*Node_Print);
|
||||
expr := interp_expression(i, print.expr, scope);
|
||||
if expr == null return;
|
||||
|
||||
if expr.kind == {
|
||||
case .none; // do nothing
|
||||
case .nil; basic.print("nil");
|
||||
case .bool; basic.print("%", expr.b);
|
||||
case .int; basic.print("%", expr.i);
|
||||
case .float; basic.print("%", expr.f);
|
||||
case .string; basic.print("%", expr.s);
|
||||
case; basic.assert(false, "unhandled interp value kind: %", expr.kind);
|
||||
}
|
||||
|
||||
basic.print("\n");
|
||||
|
||||
case .assert;
|
||||
assert := stmt.(*Node_Assert);
|
||||
expr := interp_expression(i, assert.expr, scope);
|
||||
basic.assert(expr != null, "runtime assertion failed");
|
||||
basic.assert(expr.kind == .bool, "non-boolean expression given to assert");
|
||||
basic.assert(expr.b, "runtime assertion failed");
|
||||
|
||||
case;
|
||||
interp_expression(i, stmt, scope);
|
||||
// basic.assert(false, "unhandled node kind: %", stmt.kind); // @errors
|
||||
}
|
||||
}
|
||||
|
||||
interp_lvalue :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Value {
|
||||
if expr.kind == {
|
||||
case .symbol;
|
||||
sym := expr.(*Node_Symbol);
|
||||
lval := find_symbol(scope, sym.str);
|
||||
oval := make_interp_value(i, .value);
|
||||
oval.val = lval;
|
||||
return oval;
|
||||
|
||||
case;
|
||||
basic.assert(false, "unable to get lvalue from %", expr.kind);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
interp_expression :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Value {
|
||||
if expr.kind == {
|
||||
case .procedure_call;
|
||||
call := expr.(*Node_Procedure_Call);
|
||||
args := call.all_arguments;
|
||||
|
||||
// @temp
|
||||
sym := call.call_expr.(*Node_Symbol);
|
||||
basic.assert(sym.kind == .symbol, "%", sym.kind);
|
||||
|
||||
value := find_symbol(scope, sym.str);
|
||||
basic.assert(value != null, "procedure didn't exists '%'", sym.str);
|
||||
basic.assert(value.kind == .procedure, "attempt to call non procedure '%'", sym.str);
|
||||
|
||||
result := value_nil;
|
||||
proc := value.proc;
|
||||
basic.assert(proc.arguments.count == args.count, "argument mismatch. expected %, given %", proc.arguments.count, args.count); // @errors
|
||||
|
||||
proc_scope := make_scope(scope);
|
||||
for proc.arguments {
|
||||
kv.set(*proc_scope.bindings, it.symbol.str, interp_expression(i, args[it_index], scope));
|
||||
}
|
||||
|
||||
for expr: proc.block.body {
|
||||
if expr.kind == .return_ {
|
||||
ret := expr.(*Node_Return);
|
||||
if ret.values.count != 0 {
|
||||
result = interp_expression(i, ret.values[0], proc_scope);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
else {
|
||||
interp_statement(i, expr, proc_scope);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
case .unary;
|
||||
do_unop :: (code: Code) #expand {
|
||||
if rhs.kind == {
|
||||
case .int;
|
||||
right := rhs.i;
|
||||
res.i = #insert,scope() code;
|
||||
case .float;
|
||||
right := rhs.f;
|
||||
res.f = #insert,scope() code;
|
||||
case;
|
||||
basic.assert(false, "cannot use unary operator '%' on values of type '%'", un.op, rhs.kind);
|
||||
}
|
||||
}
|
||||
|
||||
un := expr.(*Node_Unary);
|
||||
rhs := interp_expression(i, un.right, scope);
|
||||
res := make_interp_value(i, rhs.kind);
|
||||
if un.op.kind == {
|
||||
case .plus; do_unop(#code ifx right < 0 then -right else right);
|
||||
case .minus; do_unop(#code -right);
|
||||
case; basic.assert(false, "unhandled unary operator '%'", un.op.str); // @errors
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
case .binary;
|
||||
bin := expr.(*Node_Binary);
|
||||
lhs := interp_expression(i, bin.left, scope);
|
||||
rhs := interp_expression(i, bin.right, scope);
|
||||
basic.assert(lhs.kind == rhs.kind, "type mismatch % vs. %", lhs.kind, rhs.kind); // @errors
|
||||
|
||||
do_binop :: (code: Code) #expand {
|
||||
if lhs.kind == {
|
||||
case .int;
|
||||
left := lhs.i;
|
||||
right := rhs.i;
|
||||
res.i = #insert,scope() code;
|
||||
case .float;
|
||||
left := lhs.f;
|
||||
right := rhs.f;
|
||||
res.f = #insert,scope() code;
|
||||
case;
|
||||
basic.assert(false, "cannot use binary operator '%' on values of type '%'", bin.op, lhs.kind);
|
||||
}
|
||||
}
|
||||
|
||||
res := make_interp_value(i, lhs.kind);
|
||||
if bin.op.kind == {
|
||||
case .plus; do_binop(#code left + right);
|
||||
case .minus; do_binop(#code left - right);
|
||||
case .star; do_binop(#code left * right);
|
||||
|
||||
case .f_slash;
|
||||
basic.assert(rhs.i != 0, "divide by zero"); // @errors
|
||||
do_binop(#code left / right);
|
||||
|
||||
case .percent;
|
||||
basic.assert(lhs.kind == .int, "cannot use binary operator '%%' on values of type '%'", lhs.kind);
|
||||
res.i = lhs.i % rhs.i;
|
||||
|
||||
// @todo: typechecking
|
||||
case .equal_equal;
|
||||
res.kind = .bool;
|
||||
res.b = lhs.i == rhs.i;
|
||||
case .bang_equal;
|
||||
res.kind = .bool;
|
||||
res.b = lhs.i != rhs.i;
|
||||
case .less;
|
||||
res.kind = .bool;
|
||||
res.b = lhs.i < rhs.i;
|
||||
case .less_equal;
|
||||
res.kind = .bool;
|
||||
res.b = lhs.i <= rhs.i;
|
||||
case .more;
|
||||
res.kind = .bool;
|
||||
res.b = lhs.i > rhs.i;
|
||||
case .more_equal;
|
||||
res.kind = .bool;
|
||||
res.b = lhs.i >= rhs.i;
|
||||
|
||||
case; basic.assert(false, "unhandled binary operator '%'", bin.op.str);
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
case .symbol;
|
||||
sym := expr.(*Node_Symbol);
|
||||
|
||||
value := find_symbol(scope, sym.str);
|
||||
basic.assert(value != null, "use of undeclared symbol '%'", sym.str); // @errors
|
||||
return value;
|
||||
|
||||
case .literal;
|
||||
lit := expr.(*Node_Literal);
|
||||
if lit.value_kind == {
|
||||
case .int;
|
||||
value := make_interp_value(i, .int);
|
||||
value.i = lit.i;
|
||||
return value;
|
||||
|
||||
case .float;
|
||||
value := make_interp_value(i, .float);
|
||||
value.f = lit.f;
|
||||
return value;
|
||||
|
||||
case; basic.assert(false, "unhandled literal kind: %", lit.value_kind); // @errors
|
||||
}
|
||||
|
||||
case; basic.assert(false, "unhandled node kind: %", expr.kind); // @errors
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#scope_file;
|
||||
|
||||
value_nil: *Interp_Value;
|
||||
value_true: *Interp_Value;
|
||||
value_false: *Interp_Value;
|
||||
|
||||
find_symbol :: (scope: *Interp_Scope, symbol: string, all_the_way_up := true) -> *Interp_Value {
|
||||
value, ok := kv.get(*scope.bindings, symbol);
|
||||
if !ok && (all_the_way_up && scope.parent != null) {
|
||||
return find_symbol(scope.parent, symbol, true);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
make_scope :: (parent: *Interp_Scope) -> *Interp_Scope {
|
||||
scope := mem.request_memory(Interp_Scope);
|
||||
scope.parent = parent;
|
||||
kv.init(*scope.bindings, context.allocator);
|
||||
return scope;
|
||||
}
|
||||
|
||||
make_interp_value :: (i: *Interp, kind: Interp_Value.Kind) -> *Interp_Value {
|
||||
value := mem.request_memory(Interp_Value,, allocator = i.allocator);
|
||||
value.kind = kind;
|
||||
return value;
|
||||
}
|
||||
97
vm/module.jai
Normal file
97
vm/module.jai
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
// #module_parameters(RUN_TESTS := false);
|
||||
|
||||
#load "parser.jai";
|
||||
#load "interp.jai";
|
||||
|
||||
#scope_module;
|
||||
|
||||
// exported to the entire module since we want these everywhere
|
||||
|
||||
mem :: #import "jc/memory";
|
||||
array :: #import "jc/array";
|
||||
kv :: #import "jc/kv";
|
||||
|
||||
basic :: #import "Basic"; // @future
|
||||
strings :: #import "String"; // @future
|
||||
|
||||
#scope_file;
|
||||
|
||||
#run {
|
||||
parser: Parser;
|
||||
init(*parser, context.allocator);
|
||||
|
||||
ok := parse_string(*parser, #string END
|
||||
fn add(l, r) do return l + r end
|
||||
fn sub(l, r) do return l - r end
|
||||
fn mul(l, r) do return l * r end
|
||||
fn div(l, r) do return l / r end
|
||||
|
||||
var x = 21.0
|
||||
var y = 22.0
|
||||
var z = x + y
|
||||
|
||||
x = x + 1.0 / 2.0
|
||||
print x
|
||||
|
||||
x = add(x, div(1.0, 2.0))
|
||||
print x
|
||||
|
||||
print x == x
|
||||
print x == y
|
||||
print x == z
|
||||
|
||||
assert x == y
|
||||
assert x != z
|
||||
|
||||
// def (
|
||||
// Add = poly[T] proc(x T, y T) T do return x + y end
|
||||
// Sub = poly[T] proc(x T, y T) T do return x - y end
|
||||
// Mul = poly[T] proc(x T, y T) T do return x * y end
|
||||
// Div = poly[T] proc(x T, y T) T do return x / y end
|
||||
// )
|
||||
|
||||
// def (
|
||||
// Addi = Add[int]
|
||||
// Addf = Add[float]
|
||||
// Subi = Sub[int]
|
||||
// Subf = Sub[float]
|
||||
// Muli = Mul[int]
|
||||
// Mulf = Mul[float]
|
||||
// Divi = Div[int]
|
||||
// Divf = Div[float]
|
||||
// )
|
||||
|
||||
// def Foo = struct {
|
||||
// x int = 1
|
||||
// y int = 2
|
||||
// z int = 3
|
||||
// }
|
||||
|
||||
// def Value = union {
|
||||
// i int
|
||||
// f float
|
||||
// b bool
|
||||
// }
|
||||
|
||||
// def Kind = enum {
|
||||
// a
|
||||
// b
|
||||
// c
|
||||
// d
|
||||
// }
|
||||
|
||||
// var foo = Foo{ x = 10, y = 20, z = 30 }
|
||||
// var val = Value{ f = 3.14 }
|
||||
// var kind = Kind.a
|
||||
END);
|
||||
|
||||
interp: Interp;
|
||||
interp.toplevel = parser.toplevel;
|
||||
init(*interp, context.allocator);
|
||||
|
||||
interp_program(*interp);
|
||||
}
|
||||
|
||||
// #if RUN_TESTS {
|
||||
// test :: #import "jc/test";
|
||||
// }
|
||||
950
vm/parser.jai
Normal file
950
vm/parser.jai
Normal file
|
|
@ -0,0 +1,950 @@
|
|||
Token :: struct {
|
||||
kind: Kind;
|
||||
str: string;
|
||||
|
||||
Kind :: enum {
|
||||
invalid;
|
||||
end_of_file;
|
||||
|
||||
symbol;
|
||||
number;
|
||||
string;
|
||||
|
||||
kw_var;
|
||||
kw_def;
|
||||
kw_type;
|
||||
kw_fn;
|
||||
kw_do;
|
||||
kw_end;
|
||||
kw_if;
|
||||
kw_else;
|
||||
kw_switch;
|
||||
kw_case;
|
||||
kw_for;
|
||||
kw_in;
|
||||
kw_loop;
|
||||
kw_return;
|
||||
kw_break;
|
||||
kw_continue;
|
||||
kw_goto;
|
||||
kw_true;
|
||||
kw_false;
|
||||
|
||||
kw_print;
|
||||
kw_assert;
|
||||
|
||||
equal_equal; // ==
|
||||
bang_equal; // !=
|
||||
and_equal; // &=
|
||||
or_equal; // |=
|
||||
less_equal; // <=
|
||||
more_equal; // >=
|
||||
|
||||
plus_equal; // +=
|
||||
minus_equal; // -=
|
||||
star_equal; // *=
|
||||
f_slash_equal; // /=
|
||||
percent_equal; // %=
|
||||
|
||||
and_and_equal; // &&=
|
||||
or_or_equal; // ||=
|
||||
|
||||
equal :: #char "=";
|
||||
plus :: #char "+";
|
||||
minus :: #char "-";
|
||||
star :: #char "*";
|
||||
percent :: #char "%";
|
||||
bang :: #char "!";
|
||||
and :: #char "&";
|
||||
or :: #char "|";
|
||||
f_slash :: #char "/";
|
||||
b_slash :: #char "\\";
|
||||
less :: #char "<";
|
||||
more :: #char ">";
|
||||
|
||||
l_paren :: #char "(";
|
||||
r_paren :: #char ")";
|
||||
l_square :: #char "[";
|
||||
r_square :: #char "]";
|
||||
l_brace :: #char "{";
|
||||
r_brace :: #char "}";
|
||||
comma :: #char ",";
|
||||
dot :: #char ".";
|
||||
colon :: #char ":";
|
||||
semicolon :: #char ";";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Node :: struct {
|
||||
kind: Kind;
|
||||
type: *Type_Info;
|
||||
|
||||
Kind :: enum {
|
||||
invalid;
|
||||
|
||||
block;
|
||||
|
||||
stmt_start;
|
||||
print;
|
||||
assert;
|
||||
return_;
|
||||
assign;
|
||||
stmt_end;
|
||||
|
||||
decl_start;
|
||||
variable;
|
||||
procedure;
|
||||
decl_end;
|
||||
|
||||
expr_start;
|
||||
type;
|
||||
unary;
|
||||
binary;
|
||||
procedure_call;
|
||||
symbol;
|
||||
literal;
|
||||
expr_end;
|
||||
}
|
||||
}
|
||||
|
||||
Node_Var :: struct {
|
||||
#as using n: Node;
|
||||
n.kind = .variable;
|
||||
|
||||
symbol: *Node_Symbol;
|
||||
type_expr: *Node_Type;
|
||||
value_expr: *Node;
|
||||
var_flags: Var_Flag;
|
||||
|
||||
Var_Flag :: enum_flags {
|
||||
immutable; // def
|
||||
}
|
||||
}
|
||||
|
||||
Node_Assign :: struct {
|
||||
#as using n: Node;
|
||||
n.kind = .assign;
|
||||
|
||||
op: Token;
|
||||
dst: *Node;
|
||||
src: *Node;
|
||||
}
|
||||
|
||||
Node_Unary :: struct {
|
||||
#as using n: Node;
|
||||
n.kind = .unary;
|
||||
|
||||
op: Token;
|
||||
right: *Node;
|
||||
}
|
||||
|
||||
Node_Binary :: struct {
|
||||
#as using n: Node;
|
||||
n.kind = .binary;
|
||||
|
||||
op: Token;
|
||||
left: *Node;
|
||||
right: *Node;
|
||||
}
|
||||
|
||||
Node_Symbol :: struct {
|
||||
#as using n: Node;
|
||||
n.kind = .symbol;
|
||||
|
||||
str: string;
|
||||
}
|
||||
|
||||
Node_Literal :: struct {
|
||||
#as using n: Node;
|
||||
n.kind = .literal;
|
||||
|
||||
value_kind: Value_Kind;
|
||||
value_flags: Value_Flag;
|
||||
|
||||
union {
|
||||
i: s64;
|
||||
u: u64;
|
||||
f: float64;
|
||||
b: bool;
|
||||
s: string;
|
||||
}
|
||||
|
||||
Value_Kind :: enum {
|
||||
int;
|
||||
float;
|
||||
bool;
|
||||
string;
|
||||
}
|
||||
|
||||
Value_Flag :: enum_flags {
|
||||
can_be_unsigned;
|
||||
}
|
||||
}
|
||||
|
||||
Node_Type :: struct {
|
||||
#as using n: Node;
|
||||
n.kind = .type;
|
||||
|
||||
resolved_type: *Type_Info;
|
||||
type_kind: Type_Kind;
|
||||
|
||||
union {
|
||||
name_target: *Node_Symbol;
|
||||
pointer_target: *Node_Type;
|
||||
struct {
|
||||
array_element: *Node_Type;
|
||||
array_count: *Node; // can be null
|
||||
};
|
||||
struct {
|
||||
procedure_arguments: [..]*Node_Type;
|
||||
procedure_returns: [..]*Node_Type;
|
||||
};
|
||||
}
|
||||
|
||||
Type_Kind :: enum {
|
||||
named;
|
||||
pointer;
|
||||
array;
|
||||
procedure;
|
||||
}
|
||||
}
|
||||
|
||||
Node_Procedure :: struct {
|
||||
#as using n: Node;
|
||||
n.kind = .procedure;
|
||||
|
||||
arguments: [..]Parameter;
|
||||
returns: [..]Parameter;
|
||||
|
||||
symbol: *Node_Symbol; // can be null
|
||||
head: *Node_Type; // will always be of type .procedure
|
||||
block: *Node_Block;
|
||||
flags: Flag;
|
||||
|
||||
Flag :: enum_flags {
|
||||
must_inline;
|
||||
}
|
||||
}
|
||||
|
||||
Parameter :: struct {
|
||||
symbol: *Node_Symbol;
|
||||
type: *Node_Type;
|
||||
value: *Node; // always an expression, can be null
|
||||
}
|
||||
|
||||
Node_Print :: struct {
|
||||
#as using n: Node;
|
||||
n.kind = .print;
|
||||
|
||||
expr: *Node;
|
||||
}
|
||||
|
||||
Node_Assert :: struct {
|
||||
#as using n: Node;
|
||||
n.kind = .assert;
|
||||
|
||||
expr: *Node;
|
||||
}
|
||||
|
||||
Node_Procedure_Call :: struct {
|
||||
#as using n: Node;
|
||||
n.kind = .procedure_call;
|
||||
|
||||
call_expr: *Node;
|
||||
named_arguments: kv.Kv(*Node, *Node);
|
||||
all_arguments: [..]*Node;
|
||||
}
|
||||
|
||||
Node_Block :: struct {
|
||||
#as using n: Node;
|
||||
n.kind = .block;
|
||||
|
||||
body: [..]*Node;
|
||||
}
|
||||
|
||||
Node_Return :: struct {
|
||||
#as using n: Node;
|
||||
n.kind = .return_;
|
||||
|
||||
values: [..]*Node;
|
||||
}
|
||||
|
||||
Parser :: struct {
|
||||
allocator: Allocator;
|
||||
toplevel: [..]*Node;
|
||||
|
||||
previous: Token;
|
||||
filename: string;
|
||||
source: string;
|
||||
offset: int;
|
||||
}
|
||||
|
||||
init :: (p: *Parser, allocator: Allocator) {
|
||||
p.allocator = allocator;
|
||||
p.toplevel.allocator = allocator;
|
||||
}
|
||||
|
||||
parse_string :: (p: *Parser, source: string) -> bool {
|
||||
p.source = source;
|
||||
p.offset = 0;
|
||||
|
||||
while !at_end(p) {
|
||||
t := peek_token(p);
|
||||
if t.kind == .invalid || t.kind == .end_of_file {
|
||||
break;
|
||||
}
|
||||
|
||||
node := parse_statement(p);
|
||||
if node != null array.append(*p.toplevel, node);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
#scope_file;
|
||||
|
||||
parse_statement :: (p: *Parser) -> *Node {
|
||||
t := peek_token(p);
|
||||
if t.kind == {
|
||||
// var sym type_expr
|
||||
// var sym type_expr = expr
|
||||
// var sym = expr
|
||||
case .kw_var; #through;
|
||||
case .kw_def;
|
||||
consume_token(p);
|
||||
|
||||
s, ok := expect_token(p, .symbol);
|
||||
basic.assert(ok, "symbol"); // @errors
|
||||
|
||||
type_expr: *Node_Type;
|
||||
value_expr: *Node;
|
||||
is_const := t.kind == .kw_def;
|
||||
|
||||
t = peek_token(p);
|
||||
if t.kind == .equal {
|
||||
consume_token(p);
|
||||
value_expr = parse_expression(p);
|
||||
basic.assert(value_expr != null, "value expr"); // @errors
|
||||
}
|
||||
else {
|
||||
type_expr = parse_type_expression(p);
|
||||
basic.assert(type_expr != null, "type expr"); // @errors
|
||||
|
||||
if peek_token(p).kind == .equal {
|
||||
consume_token(p);
|
||||
value_expr = parse_expression(p);
|
||||
basic.assert(value_expr != null, "value expr"); // @errors
|
||||
}
|
||||
}
|
||||
|
||||
symbol := make_node(p, Node_Symbol);
|
||||
symbol.str = s.str;
|
||||
|
||||
node := make_node(p, Node_Var);
|
||||
node.symbol = symbol;
|
||||
node.type_expr = type_expr;
|
||||
node.value_expr = value_expr;
|
||||
|
||||
if is_const {
|
||||
node.var_flags |= .immutable;
|
||||
}
|
||||
|
||||
return node;
|
||||
|
||||
// return
|
||||
// return expr0, ..exprN
|
||||
case .kw_return;
|
||||
consume_token(p);
|
||||
|
||||
node := make_node(p, Node_Return);
|
||||
|
||||
prev_offset := p.offset;
|
||||
expr := parse_expression(p);
|
||||
if expr == null {
|
||||
p.offset = prev_offset;
|
||||
return node;
|
||||
}
|
||||
|
||||
array.append(*node.values, expr);
|
||||
return node;
|
||||
|
||||
// print(expr)
|
||||
// print expr
|
||||
case .kw_print;
|
||||
consume_token(p);
|
||||
|
||||
expr := parse_expression(p);
|
||||
basic.assert(expr != null, "expected expression"); // @errors
|
||||
|
||||
node := make_node(p, Node_Print);
|
||||
node.expr = expr;
|
||||
return node;
|
||||
|
||||
// assert(cond)
|
||||
// assert cond
|
||||
case .kw_assert;
|
||||
consume_token(p);
|
||||
|
||||
expr := parse_expression(p);
|
||||
basic.assert(expr != null, "expected expression"); // @errors
|
||||
|
||||
node := make_node(p, Node_Assert);
|
||||
node.expr = expr;
|
||||
return node;
|
||||
|
||||
// fn symbol(arg0, ..argN) do ... end
|
||||
case .kw_fn;
|
||||
consume_token(p);
|
||||
|
||||
symbol, ok := expect_token(p, .symbol);
|
||||
basic.assert(ok, "expected name for procedure"); // @errors @todo(judah): lambdas
|
||||
|
||||
t, ok = expect_token(p, .l_paren);
|
||||
basic.assert(ok, "expected '(' but found '%'", t.str); // @errors
|
||||
|
||||
node := make_node(p, Node_Procedure);
|
||||
node.symbol = make_node(p, Node_Symbol);
|
||||
node.symbol.str = symbol.str;
|
||||
|
||||
while !at_end(p) {
|
||||
t = peek_token(p);
|
||||
if t.kind == .r_paren break;
|
||||
|
||||
sym, ok := expect_token(p, .symbol);
|
||||
basic.assert(ok, "expected symbol"); // @errors
|
||||
|
||||
arg := array.append(*node.arguments);
|
||||
arg.symbol = make_node(p, Node_Symbol);
|
||||
arg.symbol.str = sym.str;
|
||||
|
||||
t = peek_token(p);
|
||||
if t.kind == {
|
||||
case .comma;
|
||||
consume_token(p);
|
||||
continue;
|
||||
case .r_paren;
|
||||
break;
|
||||
case;
|
||||
basic.assert(false, "expected ',' or ')' but found '%'", t.str);
|
||||
}
|
||||
}
|
||||
|
||||
_, ok = expect_token(p, .r_paren);
|
||||
basic.assert(ok, "expected ')'"); // @errors
|
||||
|
||||
node.block = parse_block(p);
|
||||
basic.assert(node.block != null, "expected block"); // @errors
|
||||
|
||||
return node;
|
||||
|
||||
// do ... end
|
||||
case .kw_do;
|
||||
return parse_block(p);
|
||||
}
|
||||
|
||||
return parse_simple_statement(p);
|
||||
}
|
||||
|
||||
parse_simple_statement :: (p: *Parser) -> *Node {
|
||||
dst := parse_expression(p);
|
||||
basic.assert(dst != null, "expected expression for simple statement");
|
||||
|
||||
t := peek_token(p);
|
||||
if t.kind == {
|
||||
case .equal; #through;
|
||||
case .plus_equal; #through;
|
||||
case .minus_equal; #through;
|
||||
case .star_equal; #through;
|
||||
case .f_slash_equal; #through;
|
||||
case .percent_equal;
|
||||
consume_token(p);
|
||||
|
||||
src := parse_expression(p);
|
||||
basic.assert(src != null, "expected right-hand side of assignment");
|
||||
|
||||
node := make_node(p, Node_Assign);
|
||||
node.op = t;
|
||||
node.dst = dst;
|
||||
|
||||
if t.kind == .equal {
|
||||
node.src = src;
|
||||
}
|
||||
// transform these into 'dst = dst op src'
|
||||
else {
|
||||
bin := make_node(p, Node_Binary);
|
||||
bin.left = dst;
|
||||
bin.right = src;
|
||||
bin.op = t;
|
||||
|
||||
if t.kind == {
|
||||
case .plus_equal; bin.op.kind = .plus;
|
||||
case .minus_equal; bin.op.kind = .minus;
|
||||
case .star_equal; bin.op.kind = .star;
|
||||
case .f_slash_equal; bin.op.kind = .f_slash;
|
||||
case .percent_equal; bin.op.kind = .percent;
|
||||
}
|
||||
|
||||
node.op.kind = .equal;
|
||||
node.src = bin;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
parse_block :: (p: *Parser) -> *Node_Block {
|
||||
t, ok := expect_token(p, .kw_do);
|
||||
basic.assert(ok, "expected 'do' found '%'", t.str); // @errors
|
||||
|
||||
block := make_node(p, Node_Block);
|
||||
|
||||
while !at_end(p) {
|
||||
t = peek_token(p);
|
||||
if t.kind == .kw_end break;
|
||||
|
||||
node := parse_statement(p);
|
||||
basic.assert(node != null); // @errors
|
||||
|
||||
array.append(*block.body, node);
|
||||
}
|
||||
|
||||
t, ok = expect_token(p, .kw_end);
|
||||
basic.assert(ok, "expected 'end' found '%'", t.str); // @errors
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
parse_type_expression :: (p: *Parser) -> *Node_Type {
|
||||
t, ok := expect_token(p, .symbol, .star, .l_square);
|
||||
basic.assert(ok, "type expression"); // @errors
|
||||
|
||||
if t.kind == {
|
||||
case .star;
|
||||
target := parse_type_expression(p);
|
||||
basic.assert(target != null, "pointer target"); // @errors
|
||||
|
||||
node := make_node(p, .pointer);
|
||||
node.pointer_target = target;
|
||||
return node;
|
||||
|
||||
case .l_square;
|
||||
node := make_node(p, .array);
|
||||
|
||||
// slice
|
||||
if peek_token(p).kind == .r_square {
|
||||
consume_token(p);
|
||||
|
||||
element := parse_type_expression(p);
|
||||
basic.assert(element != null, "array element"); // @errors
|
||||
|
||||
node.array_element = element;
|
||||
}
|
||||
else {
|
||||
count := parse_expression(p);
|
||||
basic.assert(count != null, "array count"); // @errors
|
||||
|
||||
_, ok := expect_token(p, .r_square);
|
||||
basic.assert(ok, "end of array type");
|
||||
|
||||
element := parse_type_expression(p);
|
||||
basic.assert(element != null, "array element"); // @errors
|
||||
|
||||
node.array_count = count;
|
||||
node.array_element = element;
|
||||
}
|
||||
|
||||
return node;
|
||||
|
||||
case .symbol;
|
||||
symbol := make_node(p, Node_Symbol);
|
||||
symbol.str = t.str;
|
||||
|
||||
node := make_node(p, .named);
|
||||
node.name_target = symbol;
|
||||
return node;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
parse_expression :: (p: *Parser, min_precedence := 1) -> *Node {
|
||||
get_precedence :: inline (t: Token) -> int {
|
||||
if t.kind == {
|
||||
case .star; #through;
|
||||
case .f_slash; #through;
|
||||
case .percent; #through;
|
||||
case .and;
|
||||
return 4;
|
||||
|
||||
case .plus; #through;
|
||||
case .minus;
|
||||
return 3;
|
||||
|
||||
case .equal_equal; #through;
|
||||
case .bang_equal; #through;
|
||||
case .less; #through;
|
||||
case .less_equal; #through;
|
||||
case .more; #through;
|
||||
case .more_equal;
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
node := parse_expression_unary(p);
|
||||
basic.assert(node != null, "expected expression"); // @errors
|
||||
|
||||
while !at_end(p) {
|
||||
op := peek_token(p);
|
||||
prec := get_precedence(op);
|
||||
if prec <= min_precedence break;
|
||||
|
||||
op = consume_token(p);
|
||||
|
||||
lhs := node;
|
||||
rhs := parse_expression(p, prec);
|
||||
basic.assert(rhs != null, "expected rhs"); // @errors
|
||||
|
||||
new := make_node(p, Node_Binary);
|
||||
new.op = op;
|
||||
new.left = lhs;
|
||||
new.right = rhs;
|
||||
|
||||
node = new;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
parse_expression_unary :: (p: *Parser) -> *Node {
|
||||
op := peek_token(p);
|
||||
if op.kind == {
|
||||
case .plus; #through;
|
||||
case .minus;
|
||||
op = consume_token(p);
|
||||
|
||||
node := parse_expression_unary(p);
|
||||
basic.assert(node != null, "expected expr"); // @errors
|
||||
|
||||
unary := make_node(p, Node_Unary);
|
||||
unary.op = op;
|
||||
unary.right = node;
|
||||
return unary;
|
||||
}
|
||||
|
||||
return parse_expression_postfix(p);
|
||||
}
|
||||
|
||||
parse_expression_postfix :: (p: *Parser) -> *Node {
|
||||
// @TODO
|
||||
base := parse_expression_base(p);
|
||||
basic.assert(base != null, "expected expression"); // @errors
|
||||
|
||||
t := peek_token(p);
|
||||
if t.kind == {
|
||||
case .l_paren; // procedure calls
|
||||
consume_token(p);
|
||||
|
||||
node := make_node(p, Node_Procedure_Call);
|
||||
node.call_expr = base;
|
||||
|
||||
while !at_end(p) {
|
||||
t = peek_token(p);
|
||||
if t.kind == .r_paren break;
|
||||
|
||||
arg_or_name := parse_expression(p);
|
||||
basic.assert(arg_or_name != null, "expected expression in procedure call"); // @errors
|
||||
|
||||
if peek_token(p).kind == .colon {
|
||||
consume_token(p);
|
||||
basic.assert(arg_or_name.kind == .symbol, "expected symbol for named argument"); // @errors
|
||||
basic.assert(!kv.exists(*node.named_arguments, arg_or_name), "duplicate named argument '%'", arg_or_name.(*Node_Symbol).str); // @errors
|
||||
|
||||
value := parse_expression(p);
|
||||
basic.assert(value != null, "expected expression after ':'"); // @errors
|
||||
array.append(*node.all_arguments, value);
|
||||
kv.set(*node.named_arguments, arg_or_name, value);
|
||||
}
|
||||
else {
|
||||
array.append(*node.all_arguments, arg_or_name);
|
||||
}
|
||||
|
||||
t = peek_token(p);
|
||||
if t.kind == {
|
||||
case .comma;
|
||||
consume_token(p);
|
||||
continue;
|
||||
case .r_paren;
|
||||
break;
|
||||
case;
|
||||
basic.assert(false, "expected ',' or ')' but found '%'", t.str);
|
||||
}
|
||||
}
|
||||
|
||||
_, ok := expect_token(p, .r_paren);
|
||||
basic.assert(ok, "expected ')'"); // @errors
|
||||
return node;
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
parse_expression_base :: (p: *Parser) -> *Node {
|
||||
t, ok := expect_token(p, .kw_true, .kw_false, .number, .symbol, .l_paren);
|
||||
basic.assert(ok, "expected expression, found '%'", t.str); // @errors
|
||||
|
||||
if t.kind == {
|
||||
case .kw_true; #through;
|
||||
case .kw_false;
|
||||
node := make_node(p, Node_Literal);
|
||||
node.b = t.kind == .kw_true;
|
||||
node.value_kind = .bool;
|
||||
return node;
|
||||
|
||||
case .symbol;
|
||||
node := make_node(p, Node_Symbol);
|
||||
node.str = t.str;
|
||||
return node;
|
||||
|
||||
case .number;
|
||||
node := make_node(p, Node_Literal);
|
||||
copy := t.str;
|
||||
|
||||
if strings.contains(t.str, ".") {
|
||||
node.value_kind = .float;
|
||||
|
||||
value, ok := strings.parse_float(*copy);
|
||||
basic.assert(ok, "malformed float '%'", t.str); // @errors
|
||||
|
||||
node.f = value;
|
||||
}
|
||||
else {
|
||||
node.value_kind = .int;
|
||||
if t.str[0] == "-" {
|
||||
node.value_flags |= .can_be_unsigned;
|
||||
}
|
||||
|
||||
value, ok := strings.parse_int(*copy);
|
||||
basic.assert(ok, "malformed integer '%'", t.str); // @errors
|
||||
|
||||
node.i = value;
|
||||
}
|
||||
|
||||
return node;
|
||||
|
||||
case .l_paren;
|
||||
node := parse_expression(p);
|
||||
basic.assert(node != null, "expected expression"); // @errors
|
||||
|
||||
_, ok := expect_token(p, .r_paren);
|
||||
basic.assert(ok, "expected ')'"); // @errors
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
make_node :: (p: *Parser, $T: Type) -> *T {
|
||||
node := mem.request_memory(T,, allocator = p.allocator);
|
||||
|
||||
#if T == { // nodes that require initialization
|
||||
case Node_Block;
|
||||
array.init(*node.body, p.allocator);
|
||||
case Node_Return;
|
||||
array.init(*node.values, p.allocator);
|
||||
case Node_Procedure_Call;
|
||||
array.init(*node.all_arguments, p.allocator);
|
||||
kv.init(*node.named_arguments, p.allocator);
|
||||
case Node_Procedure;
|
||||
array.init(*node.arguments, p.allocator);
|
||||
array.init(*node.returns, p.allocator);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
make_node :: (p: *Parser, $type_kind: Node_Type.Type_Kind) -> *Node_Type {
|
||||
type := mem.request_memory(Node_Type,, allocator = p.allocator);
|
||||
type.type_kind = type_kind;
|
||||
|
||||
#if type_kind == {
|
||||
case .procedure;
|
||||
array.init(*type.procedure_arguments, p.allocator);
|
||||
array.init(*type.procedure_returns, p.allocator);
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
peek_token :: (p: *Parser) -> Token {
|
||||
copy := p.*;
|
||||
return consume_token(*copy);
|
||||
}
|
||||
|
||||
at_end :: (p: *Parser) -> bool {
|
||||
return p.offset >= p.source.count;
|
||||
}
|
||||
|
||||
starts_symbol :: (c: u8) -> bool {
|
||||
return (c >= "a" && c <= "z") ||
|
||||
(c >= "A" && c <= "Z") ||
|
||||
(c == "_");
|
||||
}
|
||||
continues_symbol :: (c: u8) -> bool {
|
||||
return starts_symbol(c) || (c >= "0" && c <= "9");
|
||||
}
|
||||
|
||||
starts_number :: (c: u8) -> bool {
|
||||
return (c >= "0" && c <= "9");
|
||||
}
|
||||
continues_number :: (c: u8) -> bool {
|
||||
return starts_number(c) || c == ".";
|
||||
}
|
||||
|
||||
consume_token :: (p: *Parser) -> Token {
|
||||
if at_end(p) return .{ kind = .end_of_file };
|
||||
|
||||
c := p.source[p.offset];
|
||||
|
||||
// skip whitespace
|
||||
while !at_end(p) {
|
||||
c = p.source[p.offset];
|
||||
if c == {
|
||||
case " "; #through;
|
||||
case "\n"; #through;
|
||||
case "\t";
|
||||
p.offset += 1;
|
||||
case;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// line comments
|
||||
// @todo(judah): don't ignore these
|
||||
if c == "/" && p.offset + 1 < p.source.count && p.source[p.offset + 1] == "/" {
|
||||
p.offset += 2;
|
||||
|
||||
while !at_end(p) {
|
||||
c = p.source[p.offset];
|
||||
if c == "\n" break;
|
||||
p.offset += 1;
|
||||
}
|
||||
|
||||
// @todo(judah): don't recurse
|
||||
return consume_token(p);
|
||||
}
|
||||
|
||||
if starts_symbol(c) {
|
||||
t := Token.{ str = .{ data = p.source.data + p.offset } };
|
||||
while !at_end(p) {
|
||||
c = p.source[p.offset];
|
||||
if !continues_symbol(c) break;
|
||||
p.offset += 1;
|
||||
}
|
||||
|
||||
t.str.count = (p.source.data + p.offset) - t.str.data;
|
||||
if t.str == {
|
||||
case "var"; t.kind = .kw_var;
|
||||
case "def"; t.kind = .kw_def;
|
||||
case "type"; t.kind = .kw_type;
|
||||
case "fn"; t.kind = .kw_fn;
|
||||
case "do"; t.kind = .kw_do;
|
||||
case "end"; t.kind = .kw_end;
|
||||
case "if"; t.kind = .kw_if;
|
||||
case "else"; t.kind = .kw_else;
|
||||
case "switch"; t.kind = .kw_switch;
|
||||
case "case"; t.kind = .kw_case;
|
||||
case "for"; t.kind = .kw_for;
|
||||
case "in"; t.kind = .kw_in;
|
||||
case "loop"; t.kind = .kw_loop;
|
||||
case "return"; t.kind = .kw_return;
|
||||
case "break"; t.kind = .kw_break;
|
||||
case "continue"; t.kind = .kw_continue;
|
||||
case "goto"; t.kind = .kw_goto;
|
||||
case "true"; t.kind = .kw_true;
|
||||
case "false"; t.kind = .kw_false;
|
||||
|
||||
case "print"; t.kind = .kw_print;
|
||||
case "assert"; t.kind = .kw_assert;
|
||||
case; t.kind = .symbol;
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
if starts_number(c) {
|
||||
t := Token.{ kind = .number, str = .{ data = p.source.data + p.offset } };
|
||||
while !at_end(p) {
|
||||
c = p.source[p.offset];
|
||||
if !continues_number(c) break;
|
||||
p.offset += 1;
|
||||
}
|
||||
|
||||
t.str.count = (p.source.data + p.offset) - t.str.data;
|
||||
return t;
|
||||
}
|
||||
|
||||
with_optional_equal :: (current_kind: Token.Kind, equal_kind: Token.Kind) -> Token #expand {
|
||||
token := Token.{
|
||||
kind = current_kind,
|
||||
str = string.{ data = `p.source.data + `p.offset, count = 1 },
|
||||
};
|
||||
|
||||
`p.offset += 1;
|
||||
if `p.offset < `p.source.count && `p.source[`p.offset] == #char "=" {
|
||||
`p.offset += 1;
|
||||
|
||||
token.kind = equal_kind;
|
||||
token.str.count = 2;
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
if c == {
|
||||
case "+"; return with_optional_equal(c.(Token.Kind), .plus_equal);
|
||||
case "-"; return with_optional_equal(c.(Token.Kind), .minus_equal);
|
||||
case "*"; return with_optional_equal(c.(Token.Kind), .star_equal);
|
||||
case "/"; return with_optional_equal(c.(Token.Kind), .f_slash_equal);
|
||||
case "="; return with_optional_equal(c.(Token.Kind), .equal_equal);
|
||||
case "%"; return with_optional_equal(c.(Token.Kind), .percent_equal);
|
||||
case "!"; return with_optional_equal(c.(Token.Kind), .bang_equal);
|
||||
case "&"; return with_optional_equal(c.(Token.Kind), .and_equal);
|
||||
case "|"; return with_optional_equal(c.(Token.Kind), .or_equal);
|
||||
case "<"; return with_optional_equal(c.(Token.Kind), .less_equal);
|
||||
case ">"; return with_optional_equal(c.(Token.Kind), .more_equal);
|
||||
|
||||
case ","; #through;
|
||||
case "."; #through;
|
||||
case ":"; #through;
|
||||
case ";"; #through;
|
||||
case "("; #through;
|
||||
case ")"; #through;
|
||||
case "["; #through;
|
||||
case "]"; #through;
|
||||
case "{"; #through;
|
||||
case "}";
|
||||
s := string.{ data = p.source.data + p.offset, count = 1 };
|
||||
p.offset += 1;
|
||||
return .{ kind = xx c, str = s };
|
||||
}
|
||||
|
||||
s := string.{ data = p.source.data + p.offset, count = 1 };
|
||||
return .{ kind = .invalid, str = s };
|
||||
}
|
||||
|
||||
expect_token :: (p: *Parser, kinds: ..Token.Kind) -> Token, bool {
|
||||
t := consume_token(p);
|
||||
for kinds if it == t.kind {
|
||||
return t, true;
|
||||
}
|
||||
|
||||
return t, false;
|
||||
}
|
||||
|
||||
Loading…
Reference in a new issue