diff --git a/TODO b/TODO index 148eba9..a0f84bb 100644 --- a/TODO +++ b/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 *** diff --git a/array/dynamic_array.jai b/array/dynamic_array.jai index ca2e780..2dc223b 100644 --- a/array/dynamic_array.jai +++ b/array/dynamic_array.jai @@ -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 { diff --git a/bytes/buffer.jai b/bytes/buffer.jai new file mode 100644 index 0000000..a18c672 --- /dev/null +++ b/bytes/buffer.jai @@ -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; + }); +} diff --git a/bytes/module.jai b/bytes/module.jai new file mode 100644 index 0000000..4c3db65 --- /dev/null +++ b/bytes/module.jai @@ -0,0 +1,9 @@ +#module_parameters(RUN_TESTS := false); + +#load "buffer.jai"; + +#scope_module; + +#if RUN_TESTS { + test :: #import "jc/test"; +} diff --git a/hash/murmur.jai b/hash/murmur.jai index 2da4c50..5463131 100644 --- a/hash/murmur.jai +++ b/hash/murmur.jai @@ -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 { diff --git a/jc.jai b/jc.jai new file mode 120000 index 0000000..501958c --- /dev/null +++ b/jc.jai @@ -0,0 +1 @@ +jc.jai/ \ No newline at end of file diff --git a/kv/module.jai b/kv/module.jai index 9dcea6d..d0a8b72 100644 --- a/kv/module.jai +++ b/kv/module.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 diff --git a/math/mat.jai b/math/mat.jai index 66f6022..99b2cf2 100644 --- a/math/mat.jai +++ b/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; + } + }); +} diff --git a/math/module.jai b/math/module.jai index c81a075..8ccdc92 100644 --- a/math/module.jai +++ b/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"; +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); -} diff --git a/math/vec.jai b/math/vec.jai index 0753c63..ba71d26 100644 --- a/math/vec.jai +++ b/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)); + }); +} diff --git a/memory/module.jai b/memory/module.jai index f3af797..6808174 100644 --- a/memory/module.jai +++ b/memory/module.jai @@ -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)); diff --git a/thirdparty/luajit/x/module.jai b/thirdparty/luajit/x/module.jai new file mode 100644 index 0000000..1eb5cda --- /dev/null +++ b/thirdparty/luajit/x/module.jai @@ -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"; diff --git a/vm/interp.jai b/vm/interp.jai new file mode 100644 index 0000000..6c1762a --- /dev/null +++ b/vm/interp.jai @@ -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; +} diff --git a/vm/module.jai b/vm/module.jai new file mode 100644 index 0000000..e99e3f9 --- /dev/null +++ b/vm/module.jai @@ -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"; +// } diff --git a/vm/parser.jai b/vm/parser.jai new file mode 100644 index 0000000..293b178 --- /dev/null +++ b/vm/parser.jai @@ -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; +} +