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
|
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)
|
071 [math] basic shape collision detection (circle, rect, line)
|
||||||
072 [timestep] create drop-in fixed timestep module
|
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 ***
|
*** DONE ***
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@ append :: inline (arr: *[..]$T) -> *T {
|
||||||
return basic.array_add(arr,, allocator = arr.allocator);
|
return basic.array_add(arr,, allocator = arr.allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
resize :: inline (arr: *[..]$T, new_size: int) {
|
resize :: inline (arr: *[..]$T, new_count: int) {
|
||||||
if new_size <= arr.allocated return;
|
if new_count <= arr.allocated return;
|
||||||
basic.array_reserve(arr, new_size,, allocator = arr.allocator);
|
basic.array_reserve(arr, new_count,, allocator = arr.allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
remove_ordered :: inline (arr: *[..]$T, index: int, loc := #caller_location) #no_abc {
|
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 {
|
murmur32 :: inline (x: $T, seed: u32 = MurMur_Seed) -> u32 {
|
||||||
d: []u8 = ---;
|
return murmur32(*x, size_of(T), seed);
|
||||||
d.data = cast(*u8)*x;
|
|
||||||
d.count = size_of(T);
|
|
||||||
return murmur32(d.data, d.count, seed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
murmur32 :: (key: *void, len: int, seed: u32 = MurMur_Seed) -> u32 {
|
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 {
|
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 {
|
if !ok {
|
||||||
return mem.zero_of(kv.Value), false;
|
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) {
|
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);
|
slot, exists := find_slot(kv, hash);
|
||||||
if !exists {
|
if !exists {
|
||||||
slot = create_or_reuse_slot(kv);
|
slot = create_or_reuse_slot(kv);
|
||||||
|
|
@ -45,9 +45,14 @@ set :: (kv: *Kv, key: kv.Key, value: kv.Value) {
|
||||||
slot.value = 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...
|
// @note(judah): we use 'delete' instead of 'remove' because it's a keyword...
|
||||||
delete :: (kv: *Kv, key: kv.Key) -> kv.Value, bool {
|
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;
|
if !ok return mem.zero_of(kv.Value), false;
|
||||||
|
|
||||||
last_value := slot.value;
|
last_value := slot.value;
|
||||||
|
|
@ -74,6 +79,12 @@ for_expansion :: (kv: *Kv, body: Code, flags: For_Flags) #expand {
|
||||||
|
|
||||||
#scope_file;
|
#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 {
|
find_slot :: (kv: *Kv, hash: u32) -> *kv.Slot, bool, int {
|
||||||
for * kv.slots if it.hash == hash {
|
for * kv.slots if it.hash == hash {
|
||||||
return it, true, it_index;
|
return it, true, it_index;
|
||||||
|
|
@ -122,6 +133,8 @@ mem :: #import "jc/memory";
|
||||||
array :: #import "jc/array";
|
array :: #import "jc/array";
|
||||||
hash :: #import "jc/hash";
|
hash :: #import "jc/hash";
|
||||||
|
|
||||||
|
basic :: #import "Basic"; // @future
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------------------------------
|
// ----------------------------------------------------------
|
||||||
// TESTS
|
// TESTS
|
||||||
|
|
|
||||||
81
math/mat.jai
81
math/mat.jai
|
|
@ -400,3 +400,84 @@ inverse :: (m: Mat4) -> Mat4 {
|
||||||
|
|
||||||
return transpose(r);
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
363
math/module.jai
363
math/module.jai
|
|
@ -30,360 +30,15 @@ F64_Min, F64_Max :: #run meta.lo_for(float64), #run meta.hi_for(float64);
|
||||||
|
|
||||||
#scope_module;
|
#scope_module;
|
||||||
|
|
||||||
#if RUN_TESTS #run {
|
meta :: #import "jc/meta";
|
||||||
test :: #import "jc/test";
|
math :: #import "Math"; // @future
|
||||||
|
|
||||||
vec2_tests();
|
|
||||||
vec3_tests();
|
|
||||||
vec4_tests();
|
|
||||||
vecn_tests();
|
|
||||||
mat2_tests();
|
|
||||||
mat4_tests();
|
|
||||||
quat_tests();
|
|
||||||
rect_cut_tests();
|
|
||||||
}
|
|
||||||
|
|
||||||
#scope_file;
|
|
||||||
|
|
||||||
meta :: #import "jc/meta";
|
|
||||||
basic :: #import "Basic"; // @future
|
basic :: #import "Basic"; // @future
|
||||||
|
|
||||||
math :: #import "Math";
|
#if RUN_TESTS {
|
||||||
|
test :: #import "jc/test";
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vec3_tests :: () {
|
// @temp(judah): move these to the right files
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
v2_eq :: (a: Vec2, b: Vec2) -> bool {
|
v2_eq :: (a: Vec2, b: Vec2) -> bool {
|
||||||
return float_eq(a.x, b.x) && float_eq(a.y, b.y);
|
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
|
// Smallest difference where a float is basically that value
|
||||||
EPSILON :: 0.001;
|
EPSILON :: 0.001;
|
||||||
|
|
||||||
float_eq :: (f: float, with: float) -> bool {
|
float_eq :: (f: float, with: float) -> bool {
|
||||||
return f > with - EPSILON && f < with + EPSILON;
|
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
|
#scope_file
|
||||||
|
|
||||||
meta :: #import "jc/meta";
|
#if RUN_TESTS #run,stallable {
|
||||||
math :: #import "Math"; // @future
|
test.run(basic.tprint("%: Vec2", UNITS), t => {
|
||||||
basic :: #import "Basic"; // @future
|
{
|
||||||
|
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_or_zero :: inline (ptr: *$T, custom_init: (*T) = null) {
|
||||||
init :: initializer_of(T);
|
|
||||||
if custom_init != null {
|
if custom_init != null {
|
||||||
custom_init(ptr);
|
custom_init(ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if init != null {
|
initializer :: initializer_of(T);
|
||||||
inline init(ptr);
|
#if initializer {
|
||||||
|
inline initializer(ptr);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
memset(ptr, 0, size_of(T));
|
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