diff --git a/apps/interpreter/source/main.cpp b/apps/interpreter/source/main.cpp index c34861a..d989029 100644 --- a/apps/interpreter/source/main.cpp +++ b/apps/interpreter/source/main.cpp @@ -187,7 +187,7 @@ int main(int argc, char ** argv) if (dump_ast) { std::cout << "Input file " << filenames.back() << " AST dump:\n\n"; - if (auto function_definition = std::get_if(parsed.back().get())) + if (auto function_definition = std::get_if(root.get())) ast::print(std::cout, *function_definition->statements); std::cout << "\n" << std::flush; } diff --git a/examples/example.psl b/examples/example.psl index 5471670..b3ab643 100644 --- a/examples/example.psl +++ b/examples/example.psl @@ -4,81 +4,92 @@ import components import ecs const x = 10s // deduced type i16 -var y = 14u // deduced type u32 -var z: f64 = 3.14l +mut y = 14u // deduced type u32 +mut z: f64 = 3.14l -func fma(x: f32, y: f32, z: f32) -> f32: - return x * y + z +func fma(x: f32, y: f32, z: f32) -> f32 { + return x * y + z +} -struct vec2: - x: f32 - y: f32 +struct vec2 { + x: f32 + y: f32 +} // pass by value -func length(v: vec2) -> f32: - return math.sqrt(v.x * v.x + v.y * v.y) +func length(v: vec2) -> f32 { + return math.sqrt(v.x * v.x + v.y * v.y) +} -// return type deduced as u64 -func merge(x: u32, y: u32): - return (x as u64) or ((y as u64) << 32) +func merge(x: u32, y: u32) -> u64 { + return (x as u64) or ((y as u64) << 32) +} -var v = vec2(10, 20) +mut v = vec2(10, 20) length(v) // can be called using method syntax v.length() // function pointers -var my_func = fma // deduced type (f32, f32, f32) -> f32 +mut my_func = fma // deduced type (f32, f32, f32) -> f32 // pass by reference/pointer with * // TODO: const pointer? -func my_system(event: events.update, position: *components.position, velocity: *components.velocity): - position += event.dt * velocity +func my_system(event: events.update, position: components.position mut*, velocity: components.velocity*) { + position += event.dt * velocity +} -func attach(dispatcher: *ecs.dispatcher): - // TODO: how does it work? C++-style variadic templates? Oh no... - dispatcher.system(my_system) +func attach(dispatcher: ecs.dispatcher*) { + // TODO: how does it work? C++-style variadic templates? Oh no... + dispatcher.system(my_system) +} // objects with methods -struct rectangle: - width: i32 - height: i32 +struct rectangle { + width: i32 + height: i32 +} -func extend(r: &rectangle, size: i32): - r.width += size - r.height += size +func extend(r: rectangle mut*, size: i32) { + r.width += size + r.height += size +} -var r = rectangle(10, 12) +mut r = rectangle(10, 12) r.extend(5) // named initializers -var r2 = rectangle(width = 20, height = 30) +mut r2 = rectangle(width = 20, height = 30) // regular pointers -var ptr: *i32 = null -var x = 15 +mut ptr: i32* = null +mut x = 15 ptr = &x // field/method access using pointers is the same as with values -var sptr: *rectangle = &r +mut sptr: rectangle mut* = &r r.width *= 2 // simple generics -struct array(T): - data: *T - size: u64 +struct array(T) { + data: *T + size: u64 +} // TODO: constructors? destructors? -func new(self: array(T), size: u64): - return array(T)(data = mem.alloc(size * sizeof(T)), size = size) +func new(self: array(T), size: u64) { + return array(T)(data = mem.alloc(size * sizeof(T)), size = size) +} // TODO: static arrays? // TODO: move-only types? alloc returns smth like unique ptr? -struct kvpair(K, V): - key: K - value: V +struct kvpair(K, V) { + key: K + value: V +} -struct arraymap(K, V): - values: array(kvpair(K, V)) +struct arraymap(K, V) { + values: array(kvpair(K, V)) +} diff --git a/examples/foreign.psl b/examples/foreign.psl index 7e7793a..b3c6dfa 100644 --- a/examples/foreign.psl +++ b/examples/foreign.psl @@ -1,7 +1,8 @@ foreign func sin(x: f64) -> f64 foreign func cos(x: f64) -> f64 -func test(x: f64) -> f64: +func test(x: f64) -> f64 { let s = sin(x) let c = cos(x) return s * s + c * c +} \ No newline at end of file diff --git a/examples/ir_test.psl b/examples/ir_test.psl index f813b2a..f5a37a7 100644 --- a/examples/ir_test.psl +++ b/examples/ir_test.psl @@ -1,40 +1,48 @@ -func print(c: u8): +func print(c: u8) { foreign func putchar(c: i32) -> i32 putchar(c as i32) +} -func print_i32(x: i32): - if x < 0: +func print_i32(x: i32) { + if x < 0 { print('-') print_i32(-x) return - if x >= 10: + } + if x >= 10 { print_i32(x / 10) + } print('0' + (x % 10 as u8)) +} -func print_f32(x: f32): - if x < 0.0: +func print_f32(x: f32) { + if x < 0.0 { print('-') print_f32(-x) return + } foreign func floorf(x: f32) -> f32 let floor = floorf(x) as i32 print_i32(floor) print('.') mut y = x - (floor as f32) mut i = 0 - while i < 5: + while i < 5 { y = y * 10.0 let yfloor = floorf(y) as i32 print('0' + (yfloor as u8)) y = y - (yfloor as f32) i = i + 1 + } +} -struct vec3: +struct vec3 { x: f32 y: f32 z: f32 +} -func print_vec3(v: vec3): +func print_vec3(v: vec3) { print('(') print_f32(v.x) print(',') @@ -42,31 +50,39 @@ func print_vec3(v: vec3): print(',') print_f32(v.z) print(')') +} -func dot(a: vec3, b: vec3) -> f32: +func dot(a: vec3, b: vec3) -> f32 { return a.x * b.x + a.y * b.y + a.z * b.z +} -func add(a: vec3, b: vec3) -> vec3: +func add(a: vec3, b: vec3) -> vec3 { return vec3(a.x + b.x, a.y + b.y, a.z + b.z) +} -func mult(a: vec3, b: f32) -> vec3: +func mult(a: vec3, b: f32) -> vec3 { return vec3(a.x * b, a.y * b, a.z * b) +} -func normalized(v: vec3) -> vec3: +func normalized(v: vec3) -> vec3 { foreign func sqrtf(x: f32) -> f32 return mult(v, 1.0 / sqrtf(dot(v, v))) +} -struct ray: +struct ray { origin: vec3 direction: vec3 +} -func intersect_plane(ray: ray, normal: vec3, value: f32) -> f32: +func intersect_plane(ray: ray, normal: vec3, value: f32) -> f32 { return (value - dot(ray.origin, normal)) / dot(ray.direction, normal) +} -func test() -> i32[3]: +func test() -> i32[3] { return [70, 60, 50] +} -func print_i32_3(a: i32[3]): +func print_i32_3(a: i32[3]) { print('[') print_i32(a[0]) print(',') @@ -74,6 +90,7 @@ func print_i32_3(a: i32[3]): print(',') print_i32(a[2]) print(']') +} mut a = [1, 2, 3] a = test() diff --git a/examples/jit_test.psl b/examples/jit_test.psl index ccae7a2..f8419ff 100644 --- a/examples/jit_test.psl +++ b/examples/jit_test.psl @@ -1,11 +1,13 @@ -func print(c: u8): +func print(c: u8) { foreign func putchar(c: i32) -> i32 putchar(c as i32) +} -func test() -> i32: +func test() -> i32 { global mut x = 0 x += 1 return x +} print('0' + (test() as u8)) print('0' + (test() as u8)) diff --git a/examples/mandelbrot.psl b/examples/mandelbrot.psl index 78569bb..8fb7ec1 100644 --- a/examples/mandelbrot.psl +++ b/examples/mandelbrot.psl @@ -1,32 +1,37 @@ -foreign func putchar(c: i32) -> i32 - -func print(c: u8): +func print(c: u8) { + foreign func putchar(c: i32) -> i32 putchar(c as i32) +} -func mandelbrot(): +func mandelbrot() { let width = 120 let height = 40 mut y = 0 - while y < height: + while y < height { mut x = 0 - while x < width: + while x < width { let cx = (x as f32 + 0.5) / (width as f32) * 2.5 - 2.0 let cy = (y as f32 + 0.5) / (height as f32) * 2.0 - 1.0 mut tx = 0.0 mut ty = 0.0 mut i = 0 - while i < 100 && (tx * tx + ty * ty < 4.0): + while i < 100 && (tx * tx + ty * ty < 4.0) { let newx = tx * tx - ty * ty + cx ty = 2.0 * tx * ty + cy tx = newx i = i + 1 - if i == 100: + } + if i == 100 { print('X') - else: + } else { print(' ') + } x = x + 1 + } y = y + 1 print('\n') + } +} mandelbrot() diff --git a/examples/raytracer.psl b/examples/raytracer.psl index ca53a4c..64c5502 100644 --- a/examples/raytracer.psl +++ b/examples/raytracer.psl @@ -1,44 +1,54 @@ // ===== Dynamic memory ===== -func allocate(size: u64) -> unit mut*: +func allocate(size: u64) -> unit mut* { foreign func malloc(size: u64) -> unit mut * return malloc(size) +} -func deallocate(ptr: unit*): +func deallocate(ptr: unit*) { foreign func free(ptr: unit*) free(ptr) +} // ===== Debug IO ===== -func floor(x: f32) -> i32: +func floor(x: f32) -> i32 { foreign func floorf(x: f32) -> f32 return floorf(x) as i32 +} // No function overloading yet... -func print_byte(c: u8): +func print_byte(c: u8) { foreign func putchar(c: i32) -> i32 putchar(c as i32) +} -func print_str(mut str: u8*): +func print_str(mut str: u8*) { // Don't use puts() because it adds a newline - while *str != 0ub: + while *str != 0ub { print_byte(*str) str += 1 + } +} -func print_i32(x: i32): - if x < 0: +func print_i32(x: i32) { + if x < 0 { print_byte('-') print_i32(-x) return - if x >= 10: + } + if x >= 10 { print_i32(x / 10) + } print_byte('0' + (x % 10 as u8)) +} -func print_f32(mut x: f32): - if x < 0.0: +func print_f32(mut x: f32) { + if x < 0.0 { print_byte('-') print_f32(-x) return + } // Print integer part let xfloor = floor(x) print_i32(xfloor) @@ -46,14 +56,16 @@ func print_f32(mut x: f32): x -= (xfloor as f32) // Print fractional part mut i = 0 - while i < 3: + while i < 3 { x = x * 10.0 let xfloor = floor(x) print_byte('0' + (xfloor as u8)) x -= (xfloor as f32) i = i + 1 + } +} -func print_vec3(v: vec3): +func print_vec3(v: vec3) { print_byte('(') print_f32(v.x) print_byte(',') @@ -61,9 +73,10 @@ func print_vec3(v: vec3): print_byte(',') print_f32(v.z) print_byte(')') +} -func print_time(t: f32): - if t >= 3600.0: +func print_time(t: f32) { + if t >= 3600.0 { let hours = floor(t / 3600.0) print_i32(hours) print_byte('h') @@ -71,7 +84,7 @@ func print_time(t: f32): let minutes = floor((t - (hours as f32) * 3600.0) / 60.0) print_i32(minutes) print_byte('m') - else if t >= 60.0: + } else if t >= 60.0 { let minutes = floor(t / 60.0) print_i32(minutes) print_byte('m') @@ -79,89 +92,105 @@ func print_time(t: f32): let seconds = t - (minutes as f32) * 60.0 print_f32(seconds) print_byte('s') - else if t >= 1.0: + } else if t >= 1.0 { print_f32(t) print_byte('s') - else if t >= 0.001: + } else if t >= 0.001 { print_f32(t * 1000.0) print_byte('m') print_byte('s') - else if t >= 0.000001: + } else if t >= 0.000001 { print_f32(t * 1000000.0) print_byte('u') print_byte('s') - else: + } else { print_f32(t * 1000000000.0) print_byte('n') print_byte('s') + } +} -func flush(): +func flush() { foreign func fflush(stream: unit*) -> i32 fflush(0ul as unit*) +} // ===== File IO ===== // Opaque empty struct for type safety -struct file: +struct file { +} -func open(path: u8*, mode: u8) -> file*: +func open(path: u8*, mode: u8) -> file* { foreign func fopen(path: u8*, mode: u8*) -> file* // A hack to turn a single u8 into a zero-terminated array let mode_wide = [mode, 0ub] return fopen(path, &mode_wide as u8*) +} -func close(file: file*): +func close(file: file*) { foreign func fclose(file: file*) -> i32 fclose(file) +} -func write(file: file*, data: u8*, size: u64): +func write(file: file*, data: u8*, size: u64) { foreign func fwrite(data: u8*, size: u64, count: u64, file: file*) -> u64 fwrite(data, size, 1ul, file) +} -func write_byte(file: file*, value: u8): +func write_byte(file: file*, value: u8) { foreign func fputc(ch: i32, file: file*) -> i32 fputc(value as i32, file) +} -func write_u64(file: file*, value: u64): - if value >= 10ul: +func write_u64(file: file*, value: u64) { + if value >= 10ul { write_u64(file, value / 10ul) + } write_byte(file, '0' + (value % 10ul as u8)) +} // ===== Time ===== // Platform-dependent! -struct timespec: +struct timespec { seconds: i64 nanoseconds: i64 +} const TIME_UTC = 1 -func get_time() -> timespec: +func get_time() -> timespec { foreign func timespec_get(ts: timespec mut*, base: i32) -> i32 mut result = timespec() timespec_get(&mut result, TIME_UTC) return result +} -func time_delta(x: timespec, y: timespec) -> f32: +func time_delta(x: timespec, y: timespec) -> f32 { return ((x.seconds - y.seconds) as f32) + ((x.nanoseconds - y.nanoseconds) as f32) / 1000000000.0 +} -func sleep(time: timespec): +func sleep(time: timespec) { foreign func nanosleep(time: timespec*, rem: unit*) -> i32 nanosleep(&time, 0ul as unit*) +} // ===== Image ===== -struct image: +struct image { width: u64 height: u64 data: u8[3] mut* +} -func create_image(width: u64, height: u64) -> image: +func create_image(width: u64, height: u64) -> image { return image(width, height, allocate(width * height * 3ul) as u8[3] mut *) +} // ===== PPM format helpers ===== -func write_ppm(path: u8*, image: image): +func write_ppm(path: u8*, image: image) { let file = open(path, 'w') write_byte(file, 'P') write_byte(file, '6') @@ -174,162 +203,200 @@ func write_ppm(path: u8*, image: image): write_byte(file, '\n') write(file, image.data as u8*, image.width * image.height * 3ul) close(file) +} // ===== Math ===== const pi = 3.141592653589793 -func sin(x: f32) -> f32: +func sin(x: f32) -> f32 { foreign func sinf(x: f32) -> f32 return sinf(x) +} -func cos(x: f32) -> f32: +func cos(x: f32) -> f32 { foreign func cosf(x: f32) -> f32 return cosf(x) +} -func sqrt(x: f32) -> f32: +func sqrt(x: f32) -> f32 { foreign func sqrtf(x: f32) -> f32 return sqrtf(x) +} -func tan(x: f32) -> f32: +func tan(x: f32) -> f32 { foreign func tanf(x: f32) -> f32 return tanf(x) +} -func atan(x: f32) -> f32: +func atan(x: f32) -> f32 { foreign func atanf(x: f32) -> f32 return atanf(x) +} -func pow(x: f32, y: f32) -> f32: +func pow(x: f32, y: f32) -> f32 { foreign func powf(x: f32, y: f32) -> f32 return powf(x, y) +} -func log(x: f32) -> f32: +func log(x: f32) -> f32 { foreign func logf(x: f32) -> f32 return logf(x) +} -func abs(x: f32) -> f32: +func abs(x: f32) -> f32 { return if x >= 0.0 then x else -x +} -func sqr(x: f32) -> f32: +func sqr(x: f32) -> f32 { return x * x +} -func min(x: f32, y: f32) -> f32: +func min(x: f32, y: f32) -> f32 { return if x <= y then x else y +} -func max(x: f32, y: f32) -> f32: +func max(x: f32, y: f32) -> f32 { return if x >= y then x else y +} // I want function overloading so badly -func min_u32(x: u32, y: u32) -> u32: +func min_u32(x: u32, y: u32) -> u32 { return if x <= y then x else y +} -func clamp(x: f32) -> f32: +func clamp(x: f32) -> f32 { return max(0.0, min(1.0, x)) +} -struct vec3: +struct vec3 { x: f32 y: f32 z: f32 +} // No operator overloading yet... -func add(v: vec3, u: vec3) -> vec3: +func add(v: vec3, u: vec3) -> vec3 { return vec3(v.x + u.x, v.y + u.y, v.z + u.z) +} -func sub(v: vec3, u: vec3) -> vec3: +func sub(v: vec3, u: vec3) -> vec3 { return vec3(v.x - u.x, v.y - u.y, v.z - u.z) +} // Multiply scalar // No function overloading yet... -func mults(v: vec3, s: f32) -> vec3: +func mults(v: vec3, s: f32) -> vec3 { return vec3(v.x * s, v.y * s, v.z * s) +} // Multiply vector (component-wise) -func multv(v: vec3, u: vec3) -> vec3: +func multv(v: vec3, u: vec3) -> vec3 { return vec3(v.x * u.x, v.y * u.y, v.z * u.z) +} // Divide vector (component-wise) -func divv(v: vec3, u: vec3) -> vec3: +func divv(v: vec3, u: vec3) -> vec3 { return vec3(v.x / u.x, v.y / u.y, v.z / u.z) +} -func lerp(v: vec3, u: vec3, t: f32) -> vec3: +func lerp(v: vec3, u: vec3, t: f32) -> vec3 { return add(v, mults(sub(u, v), t)) +} -func dot(v: vec3, u: vec3) -> f32: +func dot(v: vec3, u: vec3) -> f32 { return v.x * u.x + v.y * u.y + v.z * u.z +} -func cross(v: vec3, u: vec3) -> vec3: +func cross(v: vec3, u: vec3) -> vec3 { return vec3(v.y * u.z - v.z * u.y, v.z * u.x - v.x * u.z, v.x * u.y - v.y * u.x) +} -func length(v: vec3) -> f32: +func length(v: vec3) -> f32 { return sqrt(dot(v, v)) +} -func normalized(v: vec3) -> vec3: +func normalized(v: vec3) -> vec3 { return mults(v, 1.0 / length(v)) +} -struct ray: +struct ray { origin: vec3 direction: vec3 +} -struct quaternion: +struct quaternion { x: f32 y: f32 z: f32 w: f32 +} const identity = quaternion(0.0, 0.0, 0.0, 1.0) -func inverse(q: quaternion) -> quaternion: +func inverse(q: quaternion) -> quaternion { return quaternion(-q.x, -q.y, -q.z, q.w) +} -func rotation(axis: vec3, angle: f32) -> quaternion: +func rotation(axis: vec3, angle: f32) -> quaternion { let s = sin(angle * 0.5) let c = cos(angle * 0.5) return quaternion(axis.x * s, axis.y * s, axis.z * s, c) +} -func rotate(q: quaternion, v: vec3) -> vec3: +func rotate(q: quaternion, v: vec3) -> vec3 { let qv = vec3(q.x, q.y, q.z) let t = mults(cross(qv, v), 2.0) return add(add(v, mults(t, q.w)), cross(qv, t)) +} // ===== Random number generation ===== -struct rng: +struct rng { state: u64[2] +} -func splitmix64(x: u64) -> u64: +func splitmix64(x: u64) -> u64 { mut result = x result += 11400714819323198485ul result = (result ^ (result >> 30u)) * 13787848793156543929ul result = (result ^ (result >> 27u)) * 10723151780598845931ul return result ^ (result >> 31u) +} -func next_u64(rng: rng mut*) -> u64: - func rotate_left(x: u64, k: u32) -> u64: +func next_u64(rng: rng mut*) -> u64 { + func rotate_left(x: u64, k: u32) -> u64 { return (x << k) | (x >> (64u - k)) + } let result = rotate_left(rng.state[0] * 5ul, 7u) * 9ul rng.state[1] ^= rng.state[0] rng.state[0] = rotate_left(rng.state[0], 24u) ^ rng.state[1] ^ (rng.state[1] << 16u) rng.state[1] = rotate_left(rng.state[1], 37u) return result +} // Uniform in [0..1) -func next_f32(rng: rng mut*) -> f32: +func next_f32(rng: rng mut*) -> f32 { return (next_u64(rng) as f32) / 18446744073709551615.0 +} -func next_normal(rng: rng mut*) -> f32: +func next_normal(rng: rng mut*) -> f32 { let u = next_f32(rng) let v = next_f32(rng) return sqrt(- 2.0 * log(u)) * cos(2.0 * pi * v) +} // Uniform on unit sphere -func next_vec3(rng: rng mut*) -> vec3: +func next_vec3(rng: rng mut*) -> vec3 { let theta = 2.0 * pi * next_f32(rng) let cosphi = 2.0 * next_f32(rng) - 1.0 let sinphi = sqrt(max(0.0, 1.0 - cosphi * cosphi)) return vec3(cos(theta) * sinphi, sin(theta) * sinphi, cosphi) +} -func next_vec3_normal(rng: rng mut*) -> vec3: +func next_vec3_normal(rng: rng mut*) -> vec3 { return vec3(next_normal(rng), next_normal(rng), next_normal(rng)) +} // ===== Camera ===== @@ -337,22 +404,26 @@ func next_vec3_normal(rng: rng mut*) -> vec3: // X right // Y up // -Z forward -struct camera: +struct camera { position: vec3 rotation: quaternion fovx: f32 fovy: f32 +} -func compute_fovx(fovy: f32, aspect_ratio: f32) -> f32: +func compute_fovx(fovy: f32, aspect_ratio: f32) -> f32 { return 2.0 * atan(tan(fovy * 0.5) * aspect_ratio) +} // x, y are in [-1..1] -func camera_direction(camera: camera, x: f32, y: f32) -> vec3: +func camera_direction(camera: camera, x: f32, y: f32) -> vec3 { let dir = vec3(x * tan(camera.fovx * 0.5), y * tan(camera.fovy * 0.5), -1.0) return rotate(camera.rotation, normalized(dir)) +} -func camera_ray(camera: camera, x: f32, y: f32) -> ray: +func camera_ray(camera: camera, x: f32, y: f32) -> ray { return ray(camera.position, camera_direction(camera, x, y)) +} // ===== Scene description ===== @@ -360,67 +431,79 @@ const diffuse_tag = 1u const metallic_tag = 2u const glass_tag = 3u -struct material: +struct material { // albedo for diffuse, reflection color for metallic color: vec3 emission: vec3 type: u32 roughness: f32 ior: f32 +} -struct plane: +struct plane { // Plane normal is defined as vec3(0,0,1) rotated by object rotation // Plane origin is defined as object position +} -struct sphere: +struct sphere { radius: f32 +} -struct box: +struct box { // NB: box size is 2 * extent // I.e. box boundary is center +/- extent extent: vec3 +} const plane_tag = 1u const sphere_tag = 2u const box_tag = 3u // Poor man's union -struct shape: +struct shape { tag: u32 data: f32[3] +} -struct object: +struct object { position: vec3 rotation: quaternion shape: shape material: material +} -func set_plane(object: object mut*, shape: plane): +func set_plane(object: object mut*, shape: plane) { object.shape.tag = plane_tag +} -func set_sphere(object: object mut*, shape: sphere): +func set_sphere(object: object mut*, shape: sphere) { object.shape.tag = sphere_tag *(object.shape.data as f32 mut* as sphere mut*) = shape +} -func set_box(object: object mut*, shape: box): +func set_box(object: object mut*, shape: box) { object.shape.tag = box_tag *(object.shape.data as f32 mut* as box mut*) = shape +} -struct object_array: +struct object_array { size: u64 data: object mut* +} -struct scene: +struct scene { background: vec3 objects: object_array +} // ===== Color utilities ===== -func gamma_correct(v: vec3) -> vec3: +func gamma_correct(v: vec3) -> vec3 { const gamma = 1.0 / 2.2 return vec3(pow(v.x, gamma), pow(v.y, gamma), pow(v.z, gamma)) +} -func aces(x: vec3) -> vec3: +func aces(x: vec3) -> vec3 { let a = 2.51 let b = 0.03 let c = 2.43 @@ -430,32 +513,37 @@ func aces(x: vec3) -> vec3: let nom = multv(x, add(mults(x, a), vec3(b, b, b))) let den = add(multv(x, add(mults(x, c), vec3(d, d, d))), vec3(e, e, e)) return divv(nom, den) +} -func to_bytes(v: vec3) -> u8[3]: - func to_byte(x: f32) -> u8: +func to_bytes(v: vec3) -> u8[3] { + func to_byte(x: f32) -> u8 { return clamp(x) * 255.0 as u8 + } return [to_byte(v.x), to_byte(v.y), to_byte(v.z)] +} // ===== Raytracing core ===== -struct intersection: +struct intersection { intersected: bool distance: f32 normal: vec3 material: material* +} const max_distance = 1000000.0 const no_intersection = intersection(false, max_distance, vec3(0.0, 0.0, 1.0), 0ul as material*) -func intersect_plane(ray: ray, object: object*) -> intersection: +func intersect_plane(ray: ray, object: object*) -> intersection { let normal = rotate(object.rotation, vec3(0.0, 0.0, 1.0)) // dot(o + t * d - p, n) = 0 // dot(o - p, n) + t * dot(d, n) = 0 // t = - dot(o - p, n) / dot(d, n) let t = - dot(sub(ray.origin, object.position), normal) / dot(ray.direction, normal) return intersection(t > 0.0, t, normal, &object.material) +} -func intersect_sphere(ray: ray, object: object*) -> intersection: +func intersect_sphere(ray: ray, object: object*) -> intersection { let shape = &object.shape.data as sphere* // |o + t * d - p|^2 = r^2 // dot(o - p, o - p) + 2 * t * dot(o - p, d) + t^2 * dot(d, d) = r * r @@ -465,22 +553,27 @@ func intersect_sphere(ray: ray, object: object*) -> intersection: let b = 2.0 * dot(delta, ray.direction) let c = dot(delta, delta) - shape.radius * shape.radius let D = b * b - 4.0 * a * c - if D < 0.0: + if D < 0.0 { return no_intersection + } let t1 = (- b - sqrt(D)) / (2.0 * a) let t2 = (- b + sqrt(D)) / (2.0 * a) - if t2 < 0.0: + if t2 < 0.0 { return no_intersection + } let t = if t1 < 0.0 then t2 else t1 let normal = normalized(add(delta, mults(ray.direction, t))) return intersection(true, t, normal, &object.material) +} -func intersect_box(ray: ray, object: object*) -> intersection: - func sort(x: f32 mut*, y: f32 mut*): - if *x > *y: +func intersect_box(ray: ray, object: object*) -> intersection { + func sort(x: f32 mut*, y: f32 mut*) { + if *x > *y { let temp = *x *x = *y *y = temp + } + } let shape = &object.shape.data as box* let inverse_rotation = inverse(object.rotation) @@ -502,8 +595,9 @@ func intersect_box(ray: ray, object: object*) -> intersection: let tmin = max(txmin, max(tymin, tzmin)) let tmax = min(txmax, min(tymax, tzmax)) - if tmin > tmax || tmax < 0.0: + if tmin > tmax || tmax < 0.0 { return no_intersection + } let inside = tmin < 0.0 let t = if inside then tmax else tmin @@ -511,59 +605,70 @@ func intersect_box(ray: ray, object: object*) -> intersection: mut normal = vec3() - if t == tt.x: - if local_direction.x > 0.0: + if t == tt.x { + if local_direction.x > 0.0 { normal = vec3(-1.0, 0.0, 0.0) - else: + } else { normal = vec3( 1.0, 0.0, 0.0) - else if t == tt.y: - if local_direction.y > 0.0: + } + } else if t == tt.y { + if local_direction.y > 0.0 { normal = vec3(0.0, -1.0, 0.0) - else: + } else { normal = vec3(0.0, 1.0, 0.0) - else: - if local_direction.z > 0.0: + } + } else { + if local_direction.z > 0.0 { normal = vec3(0.0, 0.0, -1.0) - else: + } else { normal = vec3(0.0, 0.0, 1.0) + } + } - if inside: + if inside { normal = mults(normal, -1.0) + } return intersection(true, t, rotate(object.rotation, normal), &object.material) +} -func intersect_scene(scene: scene*, ray: ray) -> intersection: +func intersect_scene(scene: scene*, ray: ray) -> intersection { mut intersection = no_intersection mut i = 0ul - while i < scene.objects.size: + while i < scene.objects.size { mut current_intersection = no_intersection - if scene.objects.data[i].shape.tag == plane_tag: + if scene.objects.data[i].shape.tag == plane_tag { current_intersection = intersect_plane(ray, &scene.objects.data[i]) - else if scene.objects.data[i].shape.tag == sphere_tag: + } else if scene.objects.data[i].shape.tag == sphere_tag { current_intersection = intersect_sphere(ray, &scene.objects.data[i]) - else if scene.objects.data[i].shape.tag == box_tag: + } else if scene.objects.data[i].shape.tag == box_tag { current_intersection = intersect_box(ray, &scene.objects.data[i]) + } - if current_intersection.intersected && current_intersection.distance < intersection.distance: + if current_intersection.intersected && current_intersection.distance < intersection.distance { intersection = current_intersection + } i += 1ul + } return intersection +} -func raytrace(scene: scene*, camera_ray: ray, rng: rng mut*) -> vec3: +func raytrace(scene: scene*, camera_ray: ray, rng: rng mut*) -> vec3 { mut current_ray = camera_ray mut result = vec3(0.0, 0.0, 0.0) mut factor = vec3(1.0, 1.0, 1.0) let termination_probability = 0.25 - while true: + while true { let intersection = intersect_scene(scene, current_ray) - if !intersection.intersected: + if !intersection.intersected { result = add(result, multv(factor, scene.background)) break + } // Uncomment to debug normals // return mults(add(intersection.normal, vec3(1.0, 1.0, 1.0)), 0.5) @@ -574,12 +679,13 @@ func raytrace(scene: scene*, camera_ray: ray, rng: rng mut*) -> vec3: result = add(result, multv(factor, intersection.material.emission)) // Russian roulette ray termination - if next_f32(rng) < termination_probability: + if next_f32(rng) < termination_probability { break + } mut new_direction = vec3(0.0, 0.0, 0.0) - if intersection.material.type == diffuse_tag: + if intersection.material.type == diffuse_tag { // NB: albedo is assumed to be premultiplied by pi to be in [0..1] range // This should also contain multiplication by cos(new_dir, normal), division by direction pdf (cos / pi) // and division by pi (because of albedo normalization), but these all cancel out @@ -588,7 +694,7 @@ func raytrace(scene: scene*, camera_ray: ray, rng: rng mut*) -> vec3: // Cosine-weighted hemisphere direction new_direction = normalized(add(next_vec3(rng), intersection.normal)) - else if intersection.material.type == metallic_tag: + } else if intersection.material.type == metallic_tag { // This should also contain multiplication by brdf and division by direction pdf, // but we'll just pretend that the random reflected ray pdf coincides with brdf and thus cancels out factor = multv(factor, intersection.material.color) @@ -599,86 +705,101 @@ func raytrace(scene: scene*, camera_ray: ray, rng: rng mut*) -> vec3: // Alter the direction based on roughness new_direction = normalized(add(new_direction, mults(next_vec3_normal(rng), cosine * intersection.material.roughness))) - else if intersection.material.type == glass_tag: + } else if intersection.material.type == glass_tag { mut ior = intersection.material.ior - if inside: + if inside { ior = 1.0 / ior + } // Schlick's approximation for Fresnel term let r0 = sqr((1.0 - ior) / (1.0 + ior)) let reflectance = r0 + (1.0 - r0) * pow(max(0.0, 1.0 - abs(cosine)), 5.0) - if next_f32(rng) < reflectance: + if next_f32(rng) < reflectance { // Compute perfect-mirror reflected direction new_direction = add(current_ray.direction, mults(intersection.normal, 2.0 * cosine)) - else: + } else { // This should also contain multiplication by brdf and division by direction pdf, // but we'll just pretend that the random refracted ray pdf coincides with brdf and thus cancels out factor = multv(factor, intersection.material.color) // Compute perfect refracted ray let k = 1.0 - ior * ior * (1.0 - cosine * cosine) - if k >= 0.0: + if k >= 0.0 { new_direction = add(mults(current_ray.direction, ior), mults(intersection.normal, ior * abs(cosine) - sqrt(k))) - else: + } else { // Total internal reflection new_direction = add(current_ray.direction, mults(intersection.normal, 2.0 * cosine)) + } + } // Alter the direction based on roughness new_direction = normalized(add(new_direction, mults(next_vec3_normal(rng), intersection.material.roughness))) + } // Compute the new ray, and offset its origin a bit along intersection normal let position = add(current_ray.origin, mults(current_ray.direction, intersection.distance)) mut position_shift = 0.001 - if dot(new_direction, intersection.normal) < 0.0: + if dot(new_direction, intersection.normal) < 0.0 { position_shift = -position_shift + } current_ray = ray(add(position, mults(intersection.normal, position_shift)), new_direction) // Account for Russian roulette factor = mults(factor, 1.0 / (1.0 - termination_probability)) + } return result +} // ===== Multithreading ===== // Opaque handle -struct thread: +struct thread { // Platform-dependent! handle: unit* +} -func create_thread(thread_func: unit mut* -> unit, thread_data: unit mut*) -> thread: +func create_thread(thread_func: unit mut* -> unit, thread_data: unit mut*) -> thread { foreign func pthread_create(thread: thread mut*, attr: unit*, start_routine: unit mut* -> unit, arg: unit mut*) -> i32 mut result = thread() pthread_create(&mut result, 0ul as unit*, thread_func, thread_data) return result +} -func join_thread(thread: thread): +func join_thread(thread: thread) { foreign func pthread_join(thread: thread, retval: unit*) -> i32 pthread_join(thread, 0ul as unit*) +} -struct mutex: +struct mutex { // Platform-dependent! payload: u64[8] +} -func create_mutex(mutex: mutex mut*): +func create_mutex(mutex: mutex mut*) { foreign func pthread_mutex_init(mutex: mutex mut*, attr: unit*) -> i32 pthread_mutex_init(mutex, 0ul as unit*) +} -func destroy_mutex(mutex: mutex mut*): +func destroy_mutex(mutex: mutex mut*) { foreign func pthread_mutex_destroy(mutex: mutex mut *) -> i32 pthread_mutex_destroy(mutex) +} -func lock_mutex(mutex: mutex mut*): +func lock_mutex(mutex: mutex mut*) { foreign func pthread_mutex_lock(mutex: mutex mut*) -> i32 pthread_mutex_lock(mutex) +} -func unlock_mutex(mutex: mutex mut*): +func unlock_mutex(mutex: mutex mut*) { foreign func pthread_mutex_unlock(mutex: mutex mut*) -> i32 pthread_mutex_unlock(mutex) +} // ===== Main ===== -func make_default_scene() -> scene: +func make_default_scene() -> scene { let object_count = 8ul let objects = allocate(object_count * sizeof(object)) as object mut* @@ -687,7 +808,7 @@ func make_default_scene() -> scene: // The reasonable solution: fix the compiler! // The temporary solution: split into several separate functions! - func fill_walls(objects: object mut*): + func fill_walls(objects: object mut*) { // Floor objects[0].position = vec3(0.0, -5.0, 0.0) objects[0].rotation = rotation(vec3(1.0, 0.0, 0.0), - pi * 0.5) @@ -727,8 +848,9 @@ func make_default_scene() -> scene: objects[4].material.emission = vec3(0.0, 0.0, 0.0) objects[4].material.type = diffuse_tag set_plane(&mut objects[4], plane()) + } - func fill_objects(objects: object mut*): + func fill_objects(objects: object mut*) { // Top lamp objects[5].position = vec3(0.0, 10.0, 0.0) objects[5].rotation = identity @@ -755,12 +877,14 @@ func make_default_scene() -> scene: objects[7].material.type = metallic_tag objects[7].material.roughness = 0.5 set_sphere(&mut objects[7], sphere(2.0)) + } fill_walls(objects) fill_objects(objects) let background = vec3(0.0, 0.0, 0.0) return scene(background, object_array(object_count, objects)) +} const default_fovy = 2.0 * atan(0.5) @@ -769,7 +893,7 @@ const default_fovy = 2.0 * atan(0.5) // 4 threads: 35s (~8.25us per sample) // 8 threads: 18s (~8.5us per sample) -func main(): +func main() { let scene = make_default_scene() // let image = create_image(512ul, 512ul) @@ -788,7 +912,7 @@ func main(): // const samples_per_pixel = 16ul // const samples_per_pixel = 1ul - struct thread_data: + struct thread_data { scene: scene* image: image camera: camera @@ -796,22 +920,24 @@ func main(): ystep: u64 done_mutex: mutex mut* done: u64 + } - func thread_func(data_raw: unit mut*): + func thread_func(data_raw: unit mut*) { let data = data_raw as thread_data mut* mut y = data.ystart - while y < data.image.height: + while y < data.image.height { mut x = 0ul - while x < data.image.width: + while x < data.image.width { mut rng = rng([splitmix64(x | (y << 32u)), 10723151780598845931ul]) mut sample = 0ul mut color = vec3(0.0, 0.0, 0.0) - while sample < samples_per_pixel: + while sample < samples_per_pixel { let tx = - 1.0 + 2.0 * (x as f32 + next_f32(&mut rng)) / (data.image.width as f32) let ty = 1.0 - 2.0 * (y as f32 + next_f32(&mut rng)) / (data.image.height as f32) let ray = camera_ray((*data).camera, tx, ty) color = add(color, raytrace((*data).scene, ray, &mut rng)) sample += 1ul + } color = mults(color, 1.0 / (samples_per_pixel as f32)) data.image.data[y * data.image.width + x] = to_bytes(gamma_correct(aces(color))) x += 1ul @@ -819,7 +945,10 @@ func main(): lock_mutex(data.done_mutex) data.done += 1ul unlock_mutex(data.done_mutex) + } y += data.ystep + } + } let start_time = get_time() mut last_report_time = start_time @@ -832,7 +961,7 @@ func main(): let threads_data = allocate(thread_count * sizeof(thread_data)) as thread_data mut* mut th = 0ul - while th < thread_count: + while th < thread_count { let data = threads_data + th data.scene = &scene data.image = image @@ -844,35 +973,41 @@ func main(): data.done = 0ul threads[th] = create_thread(thread_func, data as unit mut*) th += 1ul + } - func clear_line(spaces: u32): + func clear_line(spaces: u32) { mut i = 0u print_byte('\r') - while i < spaces: + while i < spaces { print_byte(' ') i += 1u + } print_byte('\r') + } mut done = 0ul let total = image.width * image.height - while done < total: + while done < total { sleep(timespec(0l, 125000000l)) let time = get_time() let delta = time_delta(time, last_report_time) - if !(delta > 0.125 || last_report_progress == 0.0): + if !(delta > 0.125 || last_report_progress == 0.0) { continue + } done = 0ul mut th = 0ul - while th < thread_count: + while th < thread_count { lock_mutex(threads_data[th].done_mutex) done += threads_data[th].done unlock_mutex(threads_data[th].done_mutex) th += 1ul + } - if done == 0ul: + if done == 0ul { continue + } let progress = (done as f32) / (total as f32) @@ -897,12 +1032,14 @@ func main(): last_report_time = time last_report_progress = progress report_count += 1u + } // No for loops yet... th = 0ul - while th < thread_count: + while th < thread_count { join_thread(threads[th]) th += 1ul + } let total_time = time_delta(get_time(), start_time) let total_samples = image.width * image.height * samples_per_pixel @@ -923,5 +1060,6 @@ func main(): deallocate(image.data as unit*) deallocate(scene.objects.data as unit*) +} main() diff --git a/examples/short-circuit.psl b/examples/short-circuit.psl index e5f5b6c..9688fba 100644 --- a/examples/short-circuit.psl +++ b/examples/short-circuit.psl @@ -1,11 +1,15 @@ -func g() -> u16: +func g() -> u16 { return g() +} -func h() -> bool: +func h() -> bool { return h() +} -func test_and() -> u16: +func test_and() -> u16 { return 0us && g() +} -func test_or() -> u16: +func test_or() -> u16 { return 65535us || g() +} \ No newline at end of file diff --git a/examples/struct_test.psl b/examples/struct_test.psl index 21fd512..6168702 100644 --- a/examples/struct_test.psl +++ b/examples/struct_test.psl @@ -1,10 +1,13 @@ -struct vec2f: +struct vec2f { x: f32 y: f32 +} -struct body: +struct body { position: vec2f rotation: f32 +} -func move_x(b: body mut*, delta: f32): - (*b).position.x = (*b).position.x + delta +func move_x(b: body mut*, delta: f32) { + b.position.x = b.position.x + delta +} diff --git a/examples/test.psl b/examples/test.psl index b48b8e9..8d88e39 100644 --- a/examples/test.psl +++ b/examples/test.psl @@ -1,11 +1,13 @@ // Vectors -struct vec2: +struct vec2 { x : f32 y : f32 +} -func add(a : vec2, b : vec2) -> vec2: +func add(a : vec2, b : vec2) -> vec2 { return vec2(a.x + b.x, a.y + b.y) +} mut v = add(vec2(1.0, 2.0), vec2(3.0, 4.0)) v.x = -v.x @@ -13,34 +15,39 @@ v.y = -v.y // Factorial -func factorial(n : u32) -> u32: - if n == 0u: - return 1u +func factorial(n : u32) -> u32 { + if n == 0u { return 1u } return n * factorial(n - 1u) +} let factorial10 = factorial(10u) // Fibonacci -func fib(n : u32) -> u32: +func fib(n : u32) -> u32 { // Slow implementation with // exponentially-growing recursion tree - if n == 0u | n == 1u: - return n // base case + if n == 0u | n == 1u { + // Base case + return n + } return fib(n - 1u) + fib(n - 2u) +} let fib10 = fib(10u) -func h() -> u32: +func h() -> u32 { return 0u +} -func f() -> u32: +func f() -> u32 { return h() +} -func g() -> u32: - func h() -> u32: - return 1u +func g() -> u32 { + func h() -> u32 { return 1u } return f() +} // Should equal 0u, but equals 1u due to an error in // how the interpreter resolves functions & variables diff --git a/libs/ast/include/pslang/ast/statement.hpp b/libs/ast/include/pslang/ast/statement.hpp index 224a896..618faaf 100644 --- a/libs/ast/include/pslang/ast/statement.hpp +++ b/libs/ast/include/pslang/ast/statement.hpp @@ -20,29 +20,6 @@ namespace pslang::ast ast::location location; }; - using pre_statement_impl = std::variant< - expression_ptr, - assignment, - variable_declaration, - if_block, - else_block, - else_if_block, - while_block, - break_statement, - continue_statement, - function_definition, - foreign_function_declaration, - return_statement, - field_definition, - struct_definition - >; - - struct pre_statement - : pre_statement_impl - { - using pre_statement_impl::pre_statement_impl; - }; - using statement_impl = std::variant< expression_ptr, assignment, @@ -63,7 +40,6 @@ namespace pslang::ast using statement_impl::statement_impl; }; - location get_location(pre_statement const & statement); location get_location(statement const & statement); } diff --git a/libs/ast/include/pslang/ast/statement_fwd.hpp b/libs/ast/include/pslang/ast/statement_fwd.hpp index c877e9f..134aaa9 100644 --- a/libs/ast/include/pslang/ast/statement_fwd.hpp +++ b/libs/ast/include/pslang/ast/statement_fwd.hpp @@ -6,23 +6,15 @@ namespace pslang::ast { - struct pre_statement; struct statement; - using pre_statement_ptr = std::shared_ptr; using statement_ptr = std::shared_ptr; - struct pre_statement_list - { - std::vector statements; - }; - struct statement_list { std::vector statements; }; - using pre_statement_list_ptr = std::shared_ptr; using statement_list_ptr = std::shared_ptr; } diff --git a/libs/ast/source/statement.cpp b/libs/ast/source/statement.cpp index 0a732e2..aa3b2d4 100644 --- a/libs/ast/source/statement.cpp +++ b/libs/ast/source/statement.cpp @@ -22,11 +22,6 @@ namespace pslang::ast } - location get_location(pre_statement const & statement) - { - return std::visit(get_location_visitor{}, statement); - } - location get_location(statement const & statement) { return std::visit(get_location_visitor{}, statement); diff --git a/libs/parser/include/pslang/parser/context.hpp b/libs/parser/include/pslang/parser/context.hpp index a94f3ad..39ceeaa 100644 --- a/libs/parser/include/pslang/parser/context.hpp +++ b/libs/parser/include/pslang/parser/context.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include namespace pslang::ast { @@ -15,7 +15,7 @@ namespace pslang::parser struct context { ast::location & location; - indented_statement_list & result; + ast::statement_list_ptr & result; }; } diff --git a/libs/parser/include/pslang/parser/finalize.hpp b/libs/parser/include/pslang/parser/finalize.hpp new file mode 100644 index 0000000..c9c2118 --- /dev/null +++ b/libs/parser/include/pslang/parser/finalize.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace pslang::parser +{ + + ast::statement_list_ptr finalize(ast::statement_list_ptr statements); + +} diff --git a/libs/parser/include/pslang/parser/indented_statement.hpp b/libs/parser/include/pslang/parser/indented_statement.hpp deleted file mode 100644 index cb94132..0000000 --- a/libs/parser/include/pslang/parser/indented_statement.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include - -#include - -namespace pslang::parser -{ - - struct indented_statement - { - std::size_t indentation; - ast::pre_statement_ptr statement; - }; - - struct indented_statement_list - { - std::vector statements; - }; - - ast::statement_list_ptr finalize(indented_statement_list statements); - -} diff --git a/libs/parser/rules/pslang.l b/libs/parser/rules/pslang.l index 75761e4..2a8840a 100644 --- a/libs/parser/rules/pslang.l +++ b/libs/parser/rules/pslang.l @@ -19,7 +19,7 @@ using bp = ::pslang::parser::bison::parser; ctx.location.step(); %} -[ ]+ { ctx.location.step(); } +[ \r\t]+ { ctx.location.step(); } "//"[^\n]* { return bp::make_comment(ctx.location); } @@ -60,7 +60,6 @@ f64 { return bp::make_f64(ctx.location); } [a-zA-Z_]+[a-zA-Z0-9_]* { return bp::make_name(yytext, ctx.location); } "\n" { auto old_location = ctx.location; ctx.location.move_lines(1); return bp::make_newline(old_location); } -"\t" { return bp::make_indent(ctx.location); } "=" { return bp::make_assignment(ctx.location); } ":" { return bp::make_colon(ctx.location); } "," { return bp::make_comma(ctx.location); } @@ -69,6 +68,8 @@ f64 { return bp::make_f64(ctx.location); } ")" { return bp::make_rparen(ctx.location); } "[" { return bp::make_lbracket(ctx.location); } "]" { return bp::make_rbracket(ctx.location); } +"{" { return bp::make_lbrace(ctx.location); } +"}" { return bp::make_rbrace(ctx.location); } "+=" { return bp::make_plus_assignment(ctx.location); } "-=" { return bp::make_minus_assignment(ctx.location); } "*=" { return bp::make_asterisk_assignment(ctx.location); } diff --git a/libs/parser/rules/pslang.y b/libs/parser/rules/pslang.y index 40c92a6..41eb24a 100644 --- a/libs/parser/rules/pslang.y +++ b/libs/parser/rules/pslang.y @@ -20,7 +20,6 @@ %code requires { -#include #include namespace pslang::parser { @@ -75,8 +74,7 @@ template %define api.token.prefix {tok_} %token newline "newline" -%token indent "indentation" -%token comment +%token comment "comment" %token assignment "=" %token colon ":" %token comma "," @@ -85,6 +83,8 @@ template %token rparen ")" %token lbracket "[" %token rbracket "]" +%token lbrace "{" +%token rbrace "}" %token plus "+" %token minus "-" %token asterisk "*" @@ -184,10 +184,12 @@ template %precedence else %precedence lbracket -%type indented_statement_list -%type statement_line -%type indentation -%type statement +%type statement_list +%type statement_block +%type statement_line +%type statement +%type if_chain +%type single_if %type > function_declaration_argument_list %type > nonempty_function_declaration_argument_list %type function_declaration_single_argument @@ -199,6 +201,8 @@ template %type primitive_type %type > function_paren_type_list %type > two_or_more_type_list +%type > field_definition_list +%type field_definition %type expression %type postfix_expression %type base_expression @@ -209,27 +213,26 @@ template %% module -: indented_statement_list end { ctx.result = $1; } +: statement_list end { ctx.result = std::make_unique($1); } ; -indented_statement_list -: statement_line { indented_statement_list tmp; tmp.statements.push_back(std::move($1)); $$ = std::move(tmp); } +statement_list +: statement_line { ast::statement_list tmp; tmp.statements.push_back(std::make_unique($1)); $$ = std::move(tmp); } | empty_line { $$ = {}; } -| indented_statement_list newline statement_line { auto tmp = $1; tmp.statements.push_back(std::move($3)); $$ = std::move(tmp); } -| indented_statement_list newline empty_line { $$ = $1; } +| statement_list newline statement_line { auto tmp = $1; tmp.statements.push_back(std::make_unique($3)); $$ = std::move(tmp); } +| statement_list newline empty_line { $$ = $1; } +; + +statement_block +: lbrace statement_list rbrace { $$ = std::make_unique($2); } ; statement_line -: indentation statement optional_comment { $$ = indented_statement{$1, std::make_unique($2)}; } +: statement optional_comment { $$ = $1; } ; empty_line -: indentation optional_comment -; - -indentation -: indent indentation { $$ = $2 + 1ull; } -| %empty { $$ = 0ull; } +: optional_comment ; optional_comment @@ -251,18 +254,25 @@ statement | expression left_shift_assignment expression { auto lhs = std::make_shared($1); $$ = ast::assignment{ lhs, std::make_shared(ast::binary_operation{ ast::binary_operation_type::left_shift, lhs, std::make_unique($3), @$ }), @$ }; } | expression right_shift_assignment expression { auto lhs = std::make_shared($1); $$ = ast::assignment{ lhs, std::make_shared(ast::binary_operation{ ast::binary_operation_type::right_shift, lhs, std::make_unique($3), @$ }), @$ }; } | variable_declaration { $$ = $1; } -| if expression colon { $$ = ast::if_block{std::make_unique($2), @$}; } -| else colon { $$ = ast::else_block{@$}; } -| else if expression colon { $$ = ast::else_if_block{std::make_unique($3), @$}; } -| while expression colon { $$ = ast::while_block{std::make_unique($2), {}, @$, @$}; } +| if_chain { $$ = $1; } +| while expression statement_block { $$ = ast::while_block{std::make_unique($2), $3, @$, @$}; } | break { $$ = ast::break_statement{@$}; } | continue { $$ = ast::continue_statement{@$}; } -| func name lparen function_declaration_argument_list rparen function_return_type colon { $$ = ast::function_definition{{$2, $4, $6, @$}, {}}; } +| func name lparen function_declaration_argument_list rparen function_return_type statement_block { $$ = ast::function_definition{{$2, $4, $6, @$}, $7}; } | foreign func name lparen function_declaration_argument_list rparen function_return_type { $$ = ast::foreign_function_declaration{{$3, $5, $7, @$}}; } | return expression { $$ = ast::return_statement{std::make_unique($2), @$}; } | return { $$ = ast::return_statement{nullptr, @$}; } -| struct name colon { $$ = ast::struct_definition{$2, {}, @$, @$}; } -| name colon type_expression { $$ = ast::field_definition{$1, std::make_unique($3), @$}; } +| struct name lbrace field_definition_list rbrace { $$ = ast::struct_definition{$2, $4, merge(@1, @2), @$}; } +; + +if_chain +: single_if { $$ = ast::if_chain{{$1}, @$}; } +| if_chain else single_if { auto tmp = $1; tmp.blocks.push_back($3); tmp.location = @$; $$ = std::move(tmp); } +| if_chain else statement_block { auto tmp = $1; tmp.blocks.push_back({nullptr, $3, @2, merge(@2, @3)}); tmp.location = @$; $$ = std::move(tmp); } +; + +single_if +: if expression statement_block { $$ = ast::if_chain::block{std::make_unique($2), $3, merge(@1, @2), @$}; } ; function_declaration_argument_list @@ -301,6 +311,17 @@ variable_keyword | mut { $$ = ast::value_category::_mutable; } ; +field_definition_list +: field_definition { std::vector tmp; tmp.push_back(std::move($1)); $$ = std::move(tmp); } +| empty_line { $$ = {}; } +| field_definition_list newline field_definition { auto tmp = $1; tmp.push_back(std::move($3)); $$ = std::move(tmp); } +| field_definition_list newline empty_line { $$ = $1; } +; + +field_definition +: name colon type_expression { $$ = ast::field_definition{$1, std::make_unique($3), @$}; } +; + type_expression : unit { $$ = types::unit_type{}; } | primitive_type { $$ = ast::type($1); } diff --git a/libs/parser/source/finalize.cpp b/libs/parser/source/finalize.cpp index 1e9ace4..7a303f7 100644 --- a/libs/parser/source/finalize.cpp +++ b/libs/parser/source/finalize.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -11,265 +11,50 @@ namespace pslang::parser namespace { - struct fill_location_visitor - : ast::statement_visitor + void validate(ast::statement_list_ptr statements, ast::function_definition * in_function, bool in_loop) { - using statement_visitor::apply; - - ast::location apply(ast::expression_ptr const & node) + for (auto & statement : statements->statements) { - return ast::get_location(*node); - } - - ast::location apply(ast::assignment const & node) - { - return node.location; - } - - ast::location apply(ast::variable_declaration const & node) - { - return node.location; - } - - ast::location apply(ast::if_chain & node) - { - bool first = true; - for (auto & block : node.blocks) + if (auto if_chain = std::get_if(statement.get())) { - block.location = apply(*block.statements); - if (first) - node.location = block.location; - else - node.location = ast::merge(node.location, block.location); - first = false; + for (auto const & block : if_chain->blocks) + { + validate(block.statements, in_function, in_loop); + } } - return node.location; - } - - ast::location apply(ast::while_block & node) - { - return node.location = ast::merge(node.prelude_location, apply(*node.statements)); - } - - ast::location apply(ast::break_statement & node) - { - return node.location; - } - - ast::location apply(ast::continue_statement & node) - { - return node.location; - } - - ast::location apply(ast::function_definition & node) - { - return node.location = ast::merge(node.prelude_location, apply(*node.statements)); - } - - ast::location apply(ast::foreign_function_declaration & node) - { - return node.location = node.prelude_location; - } - - ast::location apply(ast::return_statement const & node) - { - return node.location; - } - - ast::location apply(ast::struct_definition & node) - { - node.location = node.prelude_location; - for (auto const & field : node.fields) - node.location = ast::merge(node.location, field.location); - return node.location; - } - - ast::location apply(ast::statement_list & list) - { - ast::location result; - bool first = true; - for (auto & statement : list.statements) + else if (auto while_block = std::get_if(statement.get())) { - auto statement_location = apply(*statement); - if (first) - result = statement_location; - else - result = ast::merge(result, statement_location); - first = false; + validate(while_block->statements, in_function, true); + } + else if (auto function_definition = std::get_if(statement.get())) + { + validate(function_definition->statements, function_definition, in_loop); + } + else if (auto return_statement = std::get_if(statement.get())) + { + if (!in_function) + throw parse_error("Return statement outside of function scope", return_statement->location); + return_statement->node = in_function; + } + else if (auto break_statement = std::get_if(statement.get())) + { + if (!in_loop) + throw parse_error("Break without an enclosing loop", break_statement->location); + } + else if (auto continue_statement = std::get_if(statement.get())) + { + if (!in_loop) + throw parse_error("Continue without an enclosing loop", continue_statement->location); } - return result; - } - }; - - } - - ast::statement_list_ptr finalize(indented_statement_list statements) - { - ast::statement_list_ptr result = std::make_unique(); - - using stack_entry = std::variant; - - std::vector stack; - stack.push_back(result.get()); - std::size_t current_indent = 0; - std::vector function_stack; - std::vector loop_stack; - - auto current_statement_list = [&](ast::location const & location) -> ast::statement_list * - { - if (stack.empty()) - throw internal_error("Empty finilization stack"); - - if (auto list = std::get_if(&stack.back())) - return *list; - - throw parse_error("Unexpected statement inside struct definition", location); - }; - - auto current_struct_definition = [&](ast::location const & location) -> ast::struct_definition * - { - if (stack.empty()) - throw internal_error("Empty finilization stack"); - - if (auto list = std::get_if(&stack.back())) - return *list; - - throw parse_error("Unexpected statement outside struct definition", location); - }; - - for (auto & statement : statements.statements) - { - auto location = ast::get_location(*statement.statement); - - if (statement.indentation > current_indent) - throw parse_error("Unexpected indent", location); - - while (statement.indentation < current_indent) - { - if (stack.empty()) - throw ast::invalid_ast_error("Unexpected empty indent stack", ast::get_location(*statement.statement)); - if (!function_stack.empty() && std::holds_alternative(stack.back()) && function_stack.back()->statements.get() == std::get(stack.back())) - function_stack.pop_back(); - if (!loop_stack.empty() && std::holds_alternative(stack.back()) && loop_stack.back() == std::get(stack.back())) - loop_stack.pop_back(); - stack.pop_back(); - --current_indent; - } - - // Now statement.indentation == current_indent - - ast::statement_list * list = nullptr; - - if (auto if_block = std::get_if(statement.statement.get())) - { - ast::if_chain chain; - chain.location = if_block->location; - chain.blocks.push_back({.condition = std::move(if_block->condition), .statements = std::make_unique(), .prelude_location = if_block->location}); - list = chain.blocks.back().statements.get(); - current_statement_list(location)->statements.push_back(std::make_unique(std::move(chain))); - } - else if (auto else_block = std::get_if(statement.statement.get())) - { - if (current_statement_list(location)->statements.empty()) - throw parse_error("Unexpected else block", location); - auto chain = std::get_if(current_statement_list(location)->statements.back().get()); - if (!chain || chain->blocks.empty() || !chain->blocks.back().condition) - throw parse_error("Unexpected else block", location); - - chain->blocks.push_back({.condition = nullptr, .statements = std::make_unique(), .prelude_location = else_block->location}); - list = chain->blocks.back().statements.get(); - } - else if (auto else_if_block = std::get_if(statement.statement.get())) - { - if (current_statement_list(location)->statements.empty()) - throw parse_error("Unexpected else if block", location); - auto chain = std::get_if(current_statement_list(location)->statements.back().get()); - if (!chain || chain->blocks.empty() || !chain->blocks.back().condition) - throw parse_error("Unexpected else if block", location); - - chain->blocks.push_back({.condition = std::move(else_if_block->condition), .statements = std::make_unique(), .prelude_location = else_if_block->location}); - list = chain->blocks.back().statements.get(); - } - else if (auto while_block = std::get_if(statement.statement.get())) - { - while_block->statements = std::make_unique(); - list = while_block->statements.get(); - current_statement_list(location)->statements.push_back(std::make_unique(std::move(*while_block))); - loop_stack.push_back(list); - } - else if (auto function_definition = std::get_if(statement.statement.get())) - { - function_definition->statements = std::make_unique(); - auto statement = std::make_unique(std::move(*function_definition)); - auto function_definition_ptr = std::get_if(statement.get()); - current_statement_list(location)->statements.push_back(std::move(statement)); - list = function_definition_ptr->statements.get(); - function_stack.push_back(function_definition_ptr); - } - else if (auto field_definition = std::get_if(statement.statement.get())) - { - auto current = current_struct_definition(location); - for (auto const & field : current->fields) - if (field.name == field_definition->name) - throw parse_error("Duplicate field definition: \"" + field.name + "\"", field.location); - current->fields.push_back(*field_definition); - } - else if (auto struct_definition = std::get_if(statement.statement.get())) - { - current_statement_list(location)->statements.push_back(std::make_unique(std::move(*struct_definition))); - stack.push_back(std::get_if(current_statement_list(location)->statements.back().get())); - ++current_indent; - } - else if (auto return_statement = std::get_if(statement.statement.get())) - { - if (function_stack.empty()) - throw parse_error("Return statement outside of function scope", return_statement->location); - return_statement->node = function_stack.back(); - current_statement_list(location)->statements.push_back(std::make_unique(std::move(*return_statement))); - } - else if (auto expression_ptr = std::get_if(statement.statement.get())) - { - current_statement_list(location)->statements.push_back(std::make_unique(std::move(*expression_ptr))); - } - else if (auto assignment = std::get_if(statement.statement.get())) - { - current_statement_list(location)->statements.push_back(std::make_unique(std::move(*assignment))); - } - else if (auto variable_declaration = std::get_if(statement.statement.get())) - { - current_statement_list(location)->statements.push_back(std::make_unique(std::move(*variable_declaration))); - } - else if (auto foreign_function_declaration = std::get_if(statement.statement.get())) - { - current_statement_list(location)->statements.push_back(std::make_unique(std::move(*foreign_function_declaration))); - } - else if (auto break_statement = std::get_if(statement.statement.get())) - { - if (loop_stack.empty()) - throw parse_error("Break without an enclosing loop", break_statement->location); - current_statement_list(location)->statements.push_back(std::make_unique(std::move(*break_statement))); - } - else if (auto continue_statement = std::get_if(statement.statement.get())) - { - if (loop_stack.empty()) - throw parse_error("Continue without an enclosing loop", continue_statement->location); - current_statement_list(location)->statements.push_back(std::make_unique(std::move(*continue_statement))); - } - else - { - throw ast::invalid_ast_error(std::format("Unknown pre-statement \"{}\"", std::visit([](auto const & statement){ return typeid(statement).name(); }, *statement.statement)), location); - } - - if (list) - { - stack.push_back(list); - ++current_indent; } } - fill_location_visitor{}.apply(*result); + } - return result; + ast::statement_list_ptr finalize(ast::statement_list_ptr statements) + { + validate(statements, nullptr, false); + return statements; } } diff --git a/libs/parser/source/parser.cpp b/libs/parser/source/parser.cpp index 886567d..e3f107b 100644 --- a/libs/parser/source/parser.cpp +++ b/libs/parser/source/parser.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include "gen_parser.hpp" #include "gen_lexer.hpp" @@ -15,7 +15,7 @@ namespace pslang::parser throw std::system_error(std::make_error_code(static_cast(errno))); ast::location location{.begin = {.filename = path}, .end = {.filename = path}}; - indented_statement_list statements; + ast::statement_list_ptr statements; context ctx{location, statements}; bison::parser parser(ctx); diff --git a/spec.txt b/spec.txt index 4a358f3..5b38a55 100644 --- a/spec.txt +++ b/spec.txt @@ -36,41 +36,41 @@ Function types: ======== LITERALS ======== Literals: - 56b -> i8 - 42ub -> u8 - 456s -> i16 - 456us -> u16 - 98765 -> i32 - 98765u -> u32 - 123l -> i64 - 123ul -> u64 - 3.14h -> f16 - 3.14 -> f32 - 3.14d -> f64 - 'a' -> u8 (ascii only?) - '猫'u -> u32 + 56b -> i8 + 42ub -> u8 + 456s -> i16 + 456us -> u16 + 98765 -> i32 + 98765u -> u32 + 123l -> i64 + 123ul -> u64 + 3.14h -> f16 + 3.14 -> f32 + 3.14d -> f64 + 'a' -> u8 (ascii only?) + '猫'u -> u32 // TODO: string literals? fixed-size arrays? built-in spans? Probably built-in spans (defined in prelude.psl) - "hello, world" -> utf-8 string - "здарова, братки"u -> utf-32 string + "hello, world" -> utf-8 string + "здарова, братки"u -> utf-32 string ======== VARIABLES ======== Variable declaration: - const x = ... compile-time value, type inferred - const x: T = ... compile-time value of type T - let x = ... immutable value, type inferred - let x: T = ... immutable value of type T - mut x = ... mutable, ... - mut x: T = ... + const x = ... compile-time value, type inferred + const x: T = ... compile-time value of type T + let x = ... immutable value, type inferred + let x: T = ... immutable value of type T + mut x = ... mutable, ... + mut x: T = ... Array declaration: - let arr: i32[4] = [12, 15, 65, 42] - let arr = [2, 5, 6] // size and type inferred as i32[3] - let arr: i32[0] = [] // need special empty array literal, type cannot be inferred + let arr: i32[4] = [12, 15, 65, 42] + let arr = [2, 5, 6] // size and type inferred as i32[3] + let arr: i32[0] = [] // need special empty array literal, type cannot be inferred Null pointer literal: - let p: u32* = null // special like empty array literal, type cannot be inferred + let p: u32* = null // special like empty array literal, type cannot be inferred Variables must always be initialized. // TODO: really? What about arrays? Maybe need special syntax for zero-initialization or mass-initialization. Alternative: default to zero-initialization Const variables must be initialized with a const expression (any expression that doesn't include non-const values). @@ -78,162 +78,176 @@ Const variables must be initialized with a const expression (any expression that ======== OPERATORS ======== Logical (only bool type): - !x - x & y - x | y - x && y // short-circuit - x || y // short-circuit - x ^ y + !x + x & y + x | y + x && y // short-circuit + x || y // short-circuit + x ^ y Equality (all built-in types, all pointer types, all array/struct types, only same type unless integers): - x == y - x != y + x == y + x != y Comparison (all built-in types, all pointer types, all array/struct types, only same type unless integers): - x < y - x > y - x <= y - x >= y + x < y + x > y + x <= y + x >= y Bitwise (integer types, only same type): - !x - x & y - x | y - x && y // short-circuit - x || y // short-circuit - x ^ y + !x + x & y + x | y + x && y // short-circuit + x || y // short-circuit + x ^ y Bitwise shift (any integer + any unsigned integer type): - x >> y - x << y + x >> y + x << y Arithmetic (only same integer/floating-point type): - -x - x + y - x * y - x / y - x % y + -x + x + y + x * y + x / y + x % y Pointer arithmetic (any pointer type + any integer type): - p + x - p - x - p - q // returns i64 + p + x + p - x + p - q // returns i64 Pointer arithmetic works element-wise (like C or C++), i.e. p + n advances by n * sizeof(T) when typeof(p) is *T Casting: - x as u32 // always explicit, no implicit casts allowed + x as u32 // always explicit, no implicit casts allowed The only implicit casting allowed is T mut* -> T* (maybe?) Any integer/floating-point types can be cast to each other. Any pointer types can be cast to each other // TODO: alignment? UB or safe fallback? Probably UB. Ternary if operator: - if condition then true_value else false_value + if condition then true_value else false_value Address: - &x // returns *T, fails if x is a const variable - &mut x // returns *mut T, fails if x is non-mut variable + &x // returns *T, fails if x is a const variable + &mut x // returns *mut T, fails if x is non-mut variable Assignment: - x = 15 // requires x to be a mut variable - *p = 15 // p must be a pointer to mut + x = 15 // requires x to be a mut variable + *p = 15 // p must be a pointer to mut Special built-ins: - typeof(value) - sizeof(type) - sizeof(value) // same as sizeof(typeof(value)) - alignof(type) - alignof(value) // same as alignof(typeof(value)) - offsetof(struct type, field) - offsetof(field access expression) + typeof(value) + sizeof(type) + sizeof(value) // same as sizeof(typeof(value)) + alignof(type) + alignof(value) // same as alignof(typeof(value)) + offsetof(struct type, field) + offsetof(field access expression) ======== FLOW CONTROL ======== Conditionals: - if condition: - statements - else if condition: - statements - else: - statements + if condition { + statements + } else if condition { + statements + } else { + statements + } While loop: - while condition: - statements - if x: - break - if y: - continue + while condition { + statements + if x { + break + } + if y { + continue + } + } Iterator interface: - get(it) returns the currently pointed-to value - get_ref(it) returns the pointer to the currently pointed-to value - next(it) returns the next iterator + get(it) returns the currently pointed-to value + get_ref(it) returns the pointer to the currently pointed-to value + next(it) returns the next iterator Range interface: - begin(range) returns the begin iterator - end(range) returns the end iterator + begin(range) returns the begin iterator + end(range) returns the end iterator For loop: - Operates only on ranges. - - for x in range(10): - do_something(x) + Operates only on ranges. + + for x in range(10) { + do_something(x) + } - i is immutable within the loop body. + i is immutable within the loop body. - Modifiable ranges use special syntax for pointers to elements: + Modifiable ranges use special syntax for pointers to elements: - for &x in array: - *x += 1 + for &x in array { + *x += 1 + } - The loop is equivalent to + The loop is equivalent to - mut begin = begin(range) - let end = end(range) - while begin != end: - let x = get(begin) // or get_ref(x) for pointer loop - statements - begin = next(begin) + mut it = begin(range) + let end = end(range) + while it != end { + let x = get(it) // or get_ref(x) for pointer loop + statements + it = next(it) + } - The prelude contains an implementation of range interface for built-in arrays. + The prelude contains an implementation of range interface for built-in arrays. ======== STRUCTS ======== Struct types: - struct rect: - width: u32 - height: u32 + struct rect { + width: u32 + height: u32 + } Creating a struct value: - let x = rect(10u, 20u) - let y = rect(width = 10u, height = 20u) // named function arguments in general? + let x = rect(10u, 20u) + let y = rect(width = 10u, height = 20u) // named function arguments in general? Struct field access: - let r = rect(1u, 2u) - let x = r.width - let p = &r - let y = p.height // field access through pointer is the same + let r = rect(1u, 2u) + let x = r.width + let p = &r + let y = p.height // field access through pointer is the same // TODO: inner struct functions maybe? to act as namespace/module containers ======== FUNCTIONS ======== Function definition: - func foo(x: i32, y: i32) -> i32: - return x * y + func foo(x: i32, y: i32) -> i32 { + return x * y + } - func bar(x: f32): // deduced return type unit - print(x) + func bar(x: f32) { // deduced return type unit + print(x) + } - Function arguments are automatically immutable (as if declared with let). + Function arguments are immutable by default (as if declared with let) unless declared with mut: - // External function: name taken literally as `powf` - // and C calling convention assumed - foreign func powf(x: f32, y: f32) -> f32 // no implementation + func bar(mut x: u32) -> u32 { + x = (x + 1u) + x = x * x + return x + } -// TODO: mutable function arguments? + // External function: name taken literally as `powf` + // and C calling convention assumed + foreign func powf(x: f32, y: f32) -> f32 // no implementation // TODO: function overloading? Probably requires selecting a specific overload using `as` operator to save to a value (but not on call site) // TODO: alternative - Rust-like traits, aka parametric polymorphism? @@ -265,12 +279,14 @@ Types are also considered to be values. The keyword `type` denotes the type of a I.e. `typeof(16) == i32` and `typeof(i32) == type`. Incidentally, `typeof(type) == type` as well; there are no type kinds or etc. `type` can be used in any place where a type is required (variable types, function arguments, function return value, etc). E.g. - func foo(x: type) -> type: - return x[4] // type of arrays of 4 elements of type x + func foo(x: type) -> type { + return x[4] // type of arrays of 4 elements of type x + } - let y: type = u32 - if foo(y) == u32[4]: - do_smth() + let y: type = u32 + if foo(y) == u32[4] { + do_smth() + } ======== CONST EXPRESSIONS ======== @@ -283,12 +299,14 @@ E.g. // Functions returning functions/structs // Syntactic sugar for common cases // Figure out: max(a,b) - how to deduce type parameters? How does it play with overloading? -// func max(t: type): -// return func(x : t, y : t): +// func max(t: type) { +// return func(x : t, y : t) { // if x > y: // return x // else: // return y +// } +// } ======== PRELUDE ======== @@ -296,31 +314,32 @@ Prelude is a special source file implicitly included in any project (unless expl It contains: - An array_view template struct: + An array_view template struct: - struct array_view: - size: u64 - data: t* + struct array_view { + size: u64 + data: t* + } - A specialization for strings: + A specialization for strings: - const string_view = array_view + const string_view = array_view - (String literals compile into string_view objects.) + (String literals compile into string_view objects.) - Range interface for built-in arrays and for array_view. + Range interface for built-in arrays and for array_view. - Numeric ranges with signatures + Numeric ranges with signatures - range(end) // begin implicitly zero - range(begin, end) // step implicitly one - range(begin, end, step) - - that allow iteration like + range(end) // begin implicitly zero + range(begin, end) // step implicitly one + range(begin, end, step) + + that allow iteration like - for i in range(10): - for i in range(5u, 10u): - for i in range(1.0, 10.0, 0.5): + for i in range(10) { ... } + for i in range(5u, 10u) { ... } + for i in range(1.0, 10.0, 0.5) { ... } ======== MODULES AND IMPORTS ========