diff --git a/README.md b/README.md index a3e2b982..fdf77a78 100644 --- a/README.md +++ b/README.md @@ -77,9 +77,45 @@ Compiler binary path: ## Target Support -- Linux `x86_64` -- macOS (Darwin) `arm64` (Apple Silicon) -- Windows: not supported yet +

+Wave follows a tiered platform policy to set clear expectations for stability, CI, and standard library coverage. +

+ +
+ 🥇 Tier 1 · PrimaryLinux, Darwin, WaveOS + +
+ +
+ 🥈 Tier 2 · SecondaryFreeBSD, Redox, Fuchsia + +
+ +
+ 🥉 Tier 3 · ExperimentalOpenBSD + +
+ +
+ 🪦 Tier 4 · UnofficialWindows + +
--- @@ -88,8 +124,8 @@ Compiler binary path: ```bash wavec run wavec build -wavec build -o -wavec img +wavec build -o +wavec build -c ``` Useful global options: diff --git a/examples/doom.wave b/examples/doom.wave new file mode 100644 index 00000000..196be566 --- /dev/null +++ b/examples/doom.wave @@ -0,0 +1,297 @@ +import("std::sys::linux::fs"); +import("std::sys::linux::tty"); +import("std::time::clock"); +import("std::time::sleep"); +import("std::math::trig"); + +const STDIN_FILENO: i32 = 0; +const STDOUT_FILENO: i32 = 1; + +const SCREEN_W: i32 = 120; +const SCREEN_H: i32 = 40; +const MAP_W: i32 = 24; +const MAP_H: i32 = 24; +const ROW_STRIDE: i32 = 121; +const SCREEN_SIZE: i32 = 4840; + +const FOV: f64 = 1.0471975511965976; +const DEPTH: f64 = 20.0; +const MOVE_SPEED: f64 = 5.0; +const ROT_SPEED: f64 = 2.2; +const STATUS_TEXT_LEN: i32 = 52; + +fun cstrlen(s: str) -> i32 { + var i: i32 = 0; + while (s[i] != 0) { + i += 1; + } + return i; +} + +fun write_str(s: str) { + write(STDOUT_FILENO as i64, s as ptr, cstrlen(s) as i64); +} + +fun is_wall(map_rows: ptr, x: f64, y: f64) -> bool { + var tx: i32 = x as i32; + var ty: i32 = y as i32; + + if (tx < 0 || tx >= MAP_W || ty < 0 || ty >= MAP_H) { + return true; + } + + var row: str = map_rows[ty]; + if (row[tx] == 35) { + return true; + } + + return false; +} + +fun main() { + var map_data: array = [ + "########################", + "#..............##......#", + "#......###.....##......#", + "#......#...............#", + "#......#.......#####...#", + "#...............#......#", + "#....######.....#......#", + "#....#..........#......#", + "#....#..........#......#", + "#....#....###...#......#", + "#....#....#.....#......#", + "#....#....#.....#......#", + "#....######.....####...#", + "#......................#", + "#.....#####............#", + "#.....#................#", + "#.....#.........###....#", + "#.....#................#", + "#.....##########.......#", + "#......................#", + "#.......########.......#", + "#......................#", + "#......................#", + "########################" + ]; + var status_text: str = "Doom-ish Raycaster | WASD move, Q/E turn, X/ESC exit"; + + var screen: array; + + var term: TtyRawState; + if (tty_enable_raw_nonblock(STDIN_FILENO, &term) < 0) { + println("Failed to enable raw terminal mode."); + return; + } + + write_str("\x1b[2J\x1b[H\x1b[?25l"); + + var player_x: f64 = 8.0; + var player_y: f64 = 8.0; + var player_a: f64 = 0.0; + + var prev_ns: i64 = time_now_monotonic_ns(); + if (prev_ns < 0) { + prev_ns = 0; + } + var running: bool = true; + var input_buf: array; + + while (running) { + var now_ns: i64 = time_now_monotonic_ns(); + var dt: f64 = 0.016; + if (now_ns > 0 && prev_ns > 0 && now_ns >= prev_ns) { + dt = (now_ns - prev_ns) as f64 / 1000000000.0; + } + prev_ns = now_ns; + if (dt > 0.2) { + dt = 0.2; + } + + var bytes_read: i64 = read(STDIN_FILENO as i64, &input_buf[0], 16); + if (bytes_read > 0) { + var i: i32 = 0; + var n: i32 = bytes_read as i32; + while (i < n) { + var ch: u8 = input_buf[i]; + + if (ch == 113 || ch == 81) { + player_a -= ROT_SPEED * dt; + } else if (ch == 101 || ch == 69) { + player_a += ROT_SPEED * dt; + } else if (ch == 119 || ch == 87) { + var nx: f64 = player_x + cos_f64(player_a) * MOVE_SPEED * dt; + var ny: f64 = player_y + sin_f64(player_a) * MOVE_SPEED * dt; + if (!is_wall(&map_data[0], nx, ny)) { + player_x = nx; + player_y = ny; + } + } else if (ch == 115 || ch == 83) { + var nx: f64 = player_x - cos_f64(player_a) * MOVE_SPEED * dt; + var ny: f64 = player_y - sin_f64(player_a) * MOVE_SPEED * dt; + if (!is_wall(&map_data[0], nx, ny)) { + player_x = nx; + player_y = ny; + } + } else if (ch == 97 || ch == 65) { + var nx: f64 = player_x + sin_f64(player_a) * MOVE_SPEED * dt; + var ny: f64 = player_y - cos_f64(player_a) * MOVE_SPEED * dt; + if (!is_wall(&map_data[0], nx, ny)) { + player_x = nx; + player_y = ny; + } + } else if (ch == 100 || ch == 68) { + var nx: f64 = player_x - sin_f64(player_a) * MOVE_SPEED * dt; + var ny: f64 = player_y + cos_f64(player_a) * MOVE_SPEED * dt; + if (!is_wall(&map_data[0], nx, ny)) { + player_x = nx; + player_y = ny; + } + } else if (ch == 120 || ch == 88 || ch == 27) { + running = false; + } + + i += 1; + } + } + + var x: i32 = 0; + while (x < SCREEN_W) { + var ray_a: f64 = (player_a - (FOV / 2.0)) + ((x as f64) / (SCREEN_W as f64)) * FOV; + var eye_x: f64 = cos_f64(ray_a); + var eye_y: f64 = sin_f64(ray_a); + + var dist: f64 = 0.0; + var hit_wall: bool = false; + var boundary: bool = false; + + while (!hit_wall && dist < DEPTH) { + dist += 0.05; + + var test_x: i32 = (player_x + eye_x * dist) as i32; + var test_y: i32 = (player_y + eye_y * dist) as i32; + + if (test_x < 0 || test_x >= MAP_W || test_y < 0 || test_y >= MAP_H) { + hit_wall = true; + dist = DEPTH; + } else if (map_data[test_y][test_x] == 35) { + hit_wall = true; + + var tx: i32 = 0; + while (tx < 2) { + var ty: i32 = 0; + while (ty < 2) { + var vx: f64 = (test_x as f64) + (tx as f64) - player_x; + var vy: f64 = (test_y as f64) + (ty as f64) - player_y; + var d: f64 = sqrt_f64(vx * vx + vy * vy); + if (d > 0.000001) { + var dot: f64 = (eye_x * vx / d) + (eye_y * vy / d); + if (dot > 0.995) { + boundary = true; + } + } + ty += 1; + } + tx += 1; + } + } + } + + var ceiling: i32 = ((SCREEN_H as f64) / 2.0 - (SCREEN_H as f64) / dist) as i32; + var floor: i32 = SCREEN_H - ceiling; + if (ceiling < 0) { + ceiling = 0; + } + if (floor >= SCREEN_H) { + floor = SCREEN_H - 1; + } + + var wall_shade: u8 = 32; + if (dist <= DEPTH / 6.0) { + wall_shade = 64; + } else if (dist <= DEPTH / 4.0) { + wall_shade = 35; + } else if (dist <= DEPTH / 3.0) { + wall_shade = 79; + } else if (dist <= DEPTH / 2.0) { + wall_shade = 61; + } else if (dist <= DEPTH) { + wall_shade = 45; + } + + if (boundary) { + wall_shade = 124; + } + + var y: i32 = 0; + while (y < SCREEN_H) { + var idx: i32 = y * ROW_STRIDE + x; + + if (y < ceiling) { + deref screen[idx] = 32; + } else if (y >= ceiling && y <= floor) { + deref screen[idx] = wall_shade; + } else { + var b: f64 = 1.0 - (((y as f64) - (SCREEN_H as f64) / 2.0) / ((SCREEN_H as f64) / 2.0)); + if (b < 0.25) { + deref screen[idx] = 35; + } else if (b < 0.5) { + deref screen[idx] = 120; + } else if (b < 0.75) { + deref screen[idx] = 46; + } else { + deref screen[idx] = 32; + } + } + + y += 1; + } + + x += 1; + } + + var y2: i32 = 0; + while (y2 < SCREEN_H) { + var row_end: i32 = y2 * ROW_STRIDE + SCREEN_W; + deref screen[row_end] = 10; + y2 += 1; + } + + var si: i32 = 0; + while (si < SCREEN_W && si < STATUS_TEXT_LEN) { + deref screen[si] = status_text[si]; + si += 1; + } + + var my: i32 = 0; + while (my < MAP_H) { + if (my + 1 >= SCREEN_H) { + my = MAP_H; + } else { + var mx: i32 = 0; + while (mx < MAP_W && mx < SCREEN_W) { + var idx2: i32 = (my + 1) * ROW_STRIDE + mx; + deref screen[idx2] = map_data[my][mx]; + mx += 1; + } + my += 1; + } + } + + var px: i32 = player_x as i32; + var py: i32 = player_y as i32; + if (px >= 0 && px < MAP_W && py >= 0 && py < MAP_H && py + 1 < SCREEN_H) { + var pidx: i32 = (py + 1) * ROW_STRIDE + px; + deref screen[pidx] = 80; + } + + write_str("\x1b[H"); + write(STDOUT_FILENO as i64, &screen[0], SCREEN_SIZE as i64); + + time_sleep_us(16000); + } + + tty_restore(STDIN_FILENO, &term); + write_str("\x1b[?25h\n"); +} diff --git a/front/error/src/error.rs b/front/error/src/error.rs index dde181cb..54e06163 100644 --- a/front/error/src/error.rs +++ b/front/error/src/error.rs @@ -255,14 +255,24 @@ impl WaveError { for i in start..=end { let ln = i + 1; let ln_str = format!("{:>width$}", ln, width = width); - eprintln!(" {} {} {}", ln_str.color("38,139,235").bold(), pipe, lines[i]); + eprintln!( + " {} {} {}", + ln_str.color("38,139,235").bold(), + pipe, + lines[i] + ); } let pad = " ".repeat(width); let spaces = " ".repeat(col.saturating_sub(1)); - let marks = "^".repeat(self.span_len.max(1)).color(self.severity_color()).bold(); + let marks = "^" + .repeat(self.span_len.max(1)) + .color(self.severity_color()) + .bold(); match &self.label { - Some(label) => eprintln!(" {} {} {}{} {}", pad, pipe, spaces, marks, label.dim()), + Some(label) => { + eprintln!(" {} {} {}{} {}", pad, pipe, spaces, marks, label.dim()) + } None => eprintln!(" {} {} {}{}", pad, pipe, spaces, marks), } @@ -273,11 +283,19 @@ impl WaveError { if let Some(source_line) = &self.source { let width = line.to_string().len().max(2); let ln_str = format!("{:>width$}", line, width = width); - eprintln!(" {} {} {}", ln_str.color("38,139,235").bold(), pipe, source_line); + eprintln!( + " {} {} {}", + ln_str.color("38,139,235").bold(), + pipe, + source_line + ); let pad = " ".repeat(width); let spaces = " ".repeat(col.saturating_sub(1)); - let marks = "^".repeat(self.span_len.max(1)).color(self.severity_color()).bold(); + let marks = "^" + .repeat(self.span_len.max(1)) + .color(self.severity_color()) + .bold(); match &self.label { Some(label) => eprintln!(" {} {} {}{} {}", pad, pipe, spaces, marks, label.dim()), None => eprintln!(" {} {} {}{}", pad, pipe, spaces, marks), @@ -299,7 +317,12 @@ impl WaveError { let code = self .code .as_ref() - .map(|c| format!("[{}]", c).color(self.severity_color()).bold().to_string()) + .map(|c| { + format!("[{}]", c) + .color(self.severity_color()) + .bold() + .to_string() + }) .unwrap_or_default(); if code.is_empty() { diff --git a/front/lexer/src/core.rs b/front/lexer/src/core.rs index 697c7d82..f25a7580 100644 --- a/front/lexer/src/core.rs +++ b/front/lexer/src/core.rs @@ -21,7 +21,11 @@ pub struct Token { impl Token { pub fn new(token_type: TokenType, lexeme: String, line: usize) -> Self { - Token { token_type, lexeme, line } + Token { + token_type, + lexeme, + line, + } } } diff --git a/front/lexer/src/cursor.rs b/front/lexer/src/cursor.rs index d00a0313..16779de4 100644 --- a/front/lexer/src/cursor.rs +++ b/front/lexer/src/cursor.rs @@ -9,7 +9,7 @@ // // SPDX-License-Identifier: MPL-2.0 -use crate::{Lexer}; +use crate::Lexer; impl<'a> Lexer<'a> { pub(crate) fn is_at_end(&self) -> bool { @@ -25,7 +25,11 @@ impl<'a> Lexer<'a> { let (ch, size) = match std::str::from_utf8(rest.as_ref()) { Ok(s) => { let mut chars = s.chars(); - if let Some(c) = chars.next() { (c, c.len_utf8()) } else { ('\0', 1) } + if let Some(c) = chars.next() { + (c, c.len_utf8()) + } else { + ('\0', 1) + } } Err(_) => ('\0', 1), }; @@ -60,8 +64,12 @@ impl<'a> Lexer<'a> { } pub(crate) fn match_next(&mut self, expected: char) -> bool { - if self.is_at_end() { return false; } - if self.peek() != expected { return false; } + if self.is_at_end() { + return false; + } + if self.peek() != expected { + return false; + } self.advance(); true } diff --git a/front/lexer/src/lib.rs b/front/lexer/src/lib.rs index 428cb30a..838e7c1b 100644 --- a/front/lexer/src/lib.rs +++ b/front/lexer/src/lib.rs @@ -9,12 +9,12 @@ // // SPDX-License-Identifier: MPL-2.0 -pub mod token; pub mod core; pub mod cursor; -pub mod trivia; -pub mod literals; pub mod ident; +pub mod literals; pub mod scan; +pub mod token; +pub mod trivia; pub use crate::core::{Lexer, Token}; diff --git a/front/lexer/src/literals.rs b/front/lexer/src/literals.rs index 44b82e8c..300584bf 100644 --- a/front/lexer/src/literals.rs +++ b/front/lexer/src/literals.rs @@ -20,8 +20,8 @@ impl<'a> Lexer<'a> { while !self.is_at_end() && self.peek() != '"' { if self.peek() == '\n' { - return Err( - self.make_error( + return Err(self + .make_error( WaveErrorKind::UnterminatedString, "unterminated string literal (newline encountered before closing quote)", start_line, @@ -30,16 +30,15 @@ impl<'a> Lexer<'a> { .with_code("E1003") .with_label("string literal starts here") .with_help("close the string with `\"` before the end of line") - .with_suggestion("use `\\n` if you meant to embed a newline"), - ); + .with_suggestion("use `\\n` if you meant to embed a newline")); } let c = self.advance(); if c == '\\' { if self.is_at_end() { - return Err( - self.make_error_here( + return Err(self + .make_error_here( WaveErrorKind::InvalidString( "dangling escape sequence in string literal".to_string(), ), @@ -47,8 +46,9 @@ impl<'a> Lexer<'a> { ) .with_code("E1004") .with_label("escape sequence is incomplete") - .with_help("append a valid escape character such as `n`, `t`, `\"`, or `\\`"), - ); + .with_help( + "append a valid escape character such as `n`, `t`, `\"`, or `\\`", + )); } let next = self.advance(); @@ -60,29 +60,27 @@ impl<'a> Lexer<'a> { '"' => string_literal.push('"'), 'x' => { if self.is_at_end() { - return Err( - self.make_error_here( + return Err(self + .make_error_here( WaveErrorKind::InvalidString( "incomplete hex escape sequence".to_string(), ), "invalid escape sequence: expected two hex digits after `\\x`", ) .with_code("E1004") - .with_help("example: `\\x41` for `A`"), - ); + .with_help("example: `\\x41` for `A`")); } let h1 = self.advance(); if self.is_at_end() { - return Err( - self.make_error_here( + return Err(self + .make_error_here( WaveErrorKind::InvalidString( "incomplete hex escape sequence".to_string(), ), "invalid escape sequence: expected two hex digits after `\\x`", ) .with_code("E1004") - .with_help("example: `\\x41` for `A`"), - ); + .with_help("example: `\\x41` for `A`")); } let h2 = self.advance(); @@ -90,8 +88,8 @@ impl<'a> Lexer<'a> { let value = match u8::from_str_radix(&hex, 16) { Ok(v) => v, Err(_) => { - return Err( - self.make_error_here( + return Err(self + .make_error_here( WaveErrorKind::InvalidString(format!( "invalid hex escape: \\x{}", hex @@ -102,17 +100,18 @@ impl<'a> Lexer<'a> { ), ) .with_code("E1004") - .with_label("hex escapes must be exactly two hexadecimal digits") - .with_help("valid range: `00` to `FF`"), - ); + .with_label( + "hex escapes must be exactly two hexadecimal digits", + ) + .with_help("valid range: `00` to `FF`")); } }; string_literal.push(value as char); } _ => { - return Err( - self.make_error_here( + return Err(self + .make_error_here( WaveErrorKind::InvalidString(format!( "unknown escape sequence: \\{}", next @@ -121,19 +120,17 @@ impl<'a> Lexer<'a> { ) .with_code("E1004") .with_label("unsupported escape sequence") - .with_help("supported escapes: \\\\, \\\", \\n, \\t, \\r, \\xNN"), - ); + .with_help("supported escapes: \\\\, \\\", \\n, \\t, \\r, \\xNN")); } } - } - else { + } else { string_literal.push(c); } } if self.is_at_end() { - return Err( - self.make_error( + return Err(self + .make_error( WaveErrorKind::UnterminatedString, "unterminated string literal; missing closing quote", start_line, @@ -141,8 +138,7 @@ impl<'a> Lexer<'a> { ) .with_code("E1003") .with_label("string literal starts here") - .with_help("add `\"` to close the string"), - ); + .with_help("add `\"` to close the string")); } self.advance(); // closing quote @@ -154,32 +150,30 @@ impl<'a> Lexer<'a> { let start_col = self.current_column().saturating_sub(1).max(1); if self.is_at_end() { - return Err( - self.make_error( + return Err(self + .make_error( WaveErrorKind::InvalidString("empty char literal".to_string()), "unterminated char literal; expected a character before closing quote", start_line, start_col, ) .with_code("E1005") - .with_help("write a single character like `'a'` or an escape like `'\\n'`"), - ); + .with_help("write a single character like `'a'` or an escape like `'\\n'`")); } let c = if self.peek() == '\\' { self.advance(); if self.is_at_end() { - return Err( - self.make_error( + return Err(self + .make_error( WaveErrorKind::InvalidString("dangling char escape".to_string()), "unterminated char literal; dangling escape sequence", start_line, start_col, ) .with_code("E1005") - .with_help("complete the escape and close the char literal with `'`"), - ); + .with_help("complete the escape and close the char literal with `'`")); } let escaped = self.advance(); @@ -192,8 +186,8 @@ impl<'a> Lexer<'a> { '"' => '"', 'x' => { if self.is_at_end() { - return Err( - self.make_error( + return Err(self + .make_error( WaveErrorKind::InvalidString( "incomplete hex escape in char literal".to_string(), ), @@ -202,13 +196,12 @@ impl<'a> Lexer<'a> { start_col, ) .with_code("E1005") - .with_help("example: `'\\x41'` for `A`"), - ); + .with_help("example: `'\\x41'` for `A`")); } let h1 = self.advance(); if self.is_at_end() { - return Err( - self.make_error( + return Err(self + .make_error( WaveErrorKind::InvalidString( "incomplete hex escape in char literal".to_string(), ), @@ -217,16 +210,15 @@ impl<'a> Lexer<'a> { start_col, ) .with_code("E1005") - .with_help("example: `'\\x41'` for `A`"), - ); + .with_help("example: `'\\x41'` for `A`")); } let h2 = self.advance(); let hex = format!("{}{}", h1, h2); let value = match u8::from_str_radix(&hex, 16) { Ok(v) => v, Err(_) => { - return Err( - self.make_error( + return Err(self + .make_error( WaveErrorKind::InvalidString(format!( "invalid hex escape in char literal: \\x{}", hex @@ -239,15 +231,14 @@ impl<'a> Lexer<'a> { start_col, ) .with_code("E1005") - .with_help("hex escapes must be two hexadecimal digits"), - ); + .with_help("hex escapes must be two hexadecimal digits")); } }; value as char } _ => { - return Err( - self.make_error( + return Err(self + .make_error( WaveErrorKind::InvalidString(format!( "invalid escape sequence in char literal: \\{}", escaped @@ -257,8 +248,7 @@ impl<'a> Lexer<'a> { start_col, ) .with_code("E1005") - .with_help("supported escapes: \\\\, \\\', \\n, \\t, \\r, \\xNN"), - ); + .with_help("supported escapes: \\\\, \\\', \\n, \\t, \\r, \\xNN")); } } } else { @@ -266,8 +256,8 @@ impl<'a> Lexer<'a> { }; if self.peek() != '\'' { - return Err( - self.make_error( + return Err(self + .make_error( WaveErrorKind::InvalidString("unterminated char literal".to_string()), "unterminated or invalid char literal", start_line, @@ -275,8 +265,7 @@ impl<'a> Lexer<'a> { ) .with_code("E1005") .with_label("char literal must contain exactly one character") - .with_help("close with `'` and ensure exactly one character value"), - ); + .with_help("close with `'` and ensure exactly one character value")); } self.advance(); // closing ' Ok(c) diff --git a/front/lexer/src/scan.rs b/front/lexer/src/scan.rs index 2e6f252d..858cae95 100644 --- a/front/lexer/src/scan.rs +++ b/front/lexer/src/scan.rs @@ -19,7 +19,11 @@ impl<'a> Lexer<'a> { self.skip_trivia()?; if self.is_at_end() { - return Ok(Token { token_type: TokenType::Eof, lexeme: String::new(), line: self.line }); + return Ok(Token { + token_type: TokenType::Eof, + lexeme: String::new(), + line: self.line, + }); } let c = self.advance(); @@ -31,19 +35,19 @@ impl<'a> Lexer<'a> { token_type: TokenType::Increment, lexeme: "++".to_string(), line: self.line, - }) + }); } else if self.match_next('=') { return Ok(Token { token_type: TokenType::PlusEq, lexeme: "+=".to_string(), line: self.line, - }) + }); } else { return Ok(Token { token_type: TokenType::Plus, lexeme: "+".to_string(), line: self.line, - }) + }); } } '-' => { @@ -52,25 +56,25 @@ impl<'a> Lexer<'a> { token_type: TokenType::Decrement, lexeme: "--".to_string(), line: self.line, - }) + }); } else if self.match_next('>') { return Ok(Token { token_type: TokenType::Arrow, lexeme: "->".to_string(), line: self.line, - }) + }); } else if self.match_next('=') { return Ok(Token { token_type: TokenType::MinusEq, lexeme: "-=".to_string(), line: self.line, - }) + }); } else { return Ok(Token { token_type: TokenType::Minus, lexeme: "-".to_string(), line: self.line, - }) + }); } } '*' => { @@ -79,25 +83,35 @@ impl<'a> Lexer<'a> { token_type: TokenType::StarEq, lexeme: "*=".to_string(), line: self.line, - }) + }); } else { return Ok(Token { token_type: TokenType::Star, lexeme: "*".to_string(), line: self.line, - }) + }); } } - '.' => return Ok(Token { - token_type: TokenType::Dot, - lexeme: ".".to_string(), - line: self.line, - }), + '.' => { + return Ok(Token { + token_type: TokenType::Dot, + lexeme: ".".to_string(), + line: self.line, + }) + } '/' => { if self.match_next('=') { - return Ok(Token { token_type: TokenType::DivEq, lexeme: "/=".to_string(), line: self.line }); + return Ok(Token { + token_type: TokenType::DivEq, + lexeme: "/=".to_string(), + line: self.line, + }); } else { - return Ok(Token { token_type: TokenType::Div, lexeme: "/".to_string(), line: self.line }); + return Ok(Token { + token_type: TokenType::Div, + lexeme: "/".to_string(), + line: self.line, + }); } } '%' => { @@ -106,44 +120,48 @@ impl<'a> Lexer<'a> { token_type: TokenType::RemainderEq, lexeme: "%=".to_string(), line: self.line, - }) + }); } else { return Ok(Token { token_type: TokenType::Remainder, lexeme: "%".to_string(), line: self.line, - }) + }); } } - ';' => return Ok(Token { - token_type: TokenType::SemiColon, - lexeme: ";".to_string(), - line: self.line, - }), - ':' => return Ok(Token { - token_type: TokenType::Colon, - lexeme: ":".to_string(), - line: self.line, - }), + ';' => { + return Ok(Token { + token_type: TokenType::SemiColon, + lexeme: ";".to_string(), + line: self.line, + }) + } + ':' => { + return Ok(Token { + token_type: TokenType::Colon, + lexeme: ":".to_string(), + line: self.line, + }) + } '<' => { if self.match_next('<') { return Ok(Token { token_type: TokenType::Rol, lexeme: "<<".to_string(), line: self.line, - }) + }); } else if self.match_next('=') { return Ok(Token { token_type: TokenType::LchevrEq, lexeme: "<=".to_string(), line: self.line, - }) + }); } else { return Ok(Token { token_type: TokenType::Lchevr, lexeme: "<".to_string(), line: self.line, - }) + }); } } '>' => { @@ -152,64 +170,76 @@ impl<'a> Lexer<'a> { token_type: TokenType::Ror, lexeme: ">>".to_string(), line: self.line, - }) + }); } else if self.match_next('=') { return Ok(Token { token_type: TokenType::RchevrEq, lexeme: ">=".to_string(), line: self.line, - }) + }); } else { return Ok(Token { token_type: TokenType::Rchevr, lexeme: ">".to_string(), line: self.line, - }) + }); } } - '(' => return Ok(Token { - token_type: TokenType::Lparen, - lexeme: "(".to_string(), - line: self.line, - }), - ')' => return Ok(Token { - token_type: TokenType::Rparen, - lexeme: ")".to_string(), - line: self.line, - }), - '{' => return Ok(Token { - token_type: TokenType::Lbrace, - lexeme: "{".to_string(), - line: self.line, - }), - '}' => return Ok(Token { - token_type: TokenType::Rbrace, - lexeme: "}".to_string(), - line: self.line, - }), - '[' => return Ok(Token { - token_type: TokenType::Lbrack, - lexeme: "[".to_string(), - line: self.line, - }), - ']' => return Ok(Token { - token_type: TokenType::Rbrack, - lexeme: "]".to_string(), - line: self.line, - }), + '(' => { + return Ok(Token { + token_type: TokenType::Lparen, + lexeme: "(".to_string(), + line: self.line, + }) + } + ')' => { + return Ok(Token { + token_type: TokenType::Rparen, + lexeme: ")".to_string(), + line: self.line, + }) + } + '{' => { + return Ok(Token { + token_type: TokenType::Lbrace, + lexeme: "{".to_string(), + line: self.line, + }) + } + '}' => { + return Ok(Token { + token_type: TokenType::Rbrace, + lexeme: "}".to_string(), + line: self.line, + }) + } + '[' => { + return Ok(Token { + token_type: TokenType::Lbrack, + lexeme: "[".to_string(), + line: self.line, + }) + } + ']' => { + return Ok(Token { + token_type: TokenType::Rbrack, + lexeme: "]".to_string(), + line: self.line, + }) + } '=' => { if self.match_next('=') { return Ok(Token { token_type: TokenType::EqualTwo, lexeme: "==".to_string(), line: self.line, - }) + }); } else { return Ok(Token { token_type: TokenType::Equal, lexeme: "=".to_string(), line: self.line, - }) + }); } } '&' => { @@ -218,13 +248,13 @@ impl<'a> Lexer<'a> { token_type: TokenType::LogicalAnd, lexeme: "&&".to_string(), line: self.line, - }) + }); } else { return Ok(Token { token_type: TokenType::AddressOf, lexeme: "&".to_string(), line: self.line, - }) + }); } } '|' => { @@ -233,13 +263,13 @@ impl<'a> Lexer<'a> { token_type: TokenType::LogicalOr, lexeme: "||".to_string(), line: self.line, - }) + }); } else { return Ok(Token { token_type: TokenType::BitwiseOr, lexeme: "|".to_string(), line: self.line, - }) + }); } } '!' => { @@ -248,45 +278,47 @@ impl<'a> Lexer<'a> { token_type: TokenType::NotEqual, lexeme: "!=".to_string(), line: self.line, - }) + }); } else if self.match_next('&') { return Ok(Token { token_type: TokenType::Nand, lexeme: "!&".to_string(), line: self.line, - }) + }); } else if self.match_next('|') { return Ok(Token { token_type: TokenType::Nor, lexeme: "!|".to_string(), line: self.line, - }) + }); } else { return Ok(Token { token_type: TokenType::Not, lexeme: "!".to_string(), line: self.line, - }) + }); } } - '^' => return Ok(Token { - token_type: TokenType::Xor, - lexeme: "^".to_string(), - line: self.line, - }), + '^' => { + return Ok(Token { + token_type: TokenType::Xor, + lexeme: "^".to_string(), + line: self.line, + }) + } '~' => { if self.match_next('^') { return Ok(Token { token_type: TokenType::Xnor, lexeme: "~^".to_string(), line: self.line, - }) + }); } else { return Ok(Token { token_type: TokenType::BitwiseNot, lexeme: "~".to_string(), line: self.line, - }) + }); } } '?' => { @@ -295,35 +327,37 @@ impl<'a> Lexer<'a> { token_type: TokenType::NullCoalesce, lexeme: "??".to_string(), line: self.line, - }) + }); } else { return Ok(Token { token_type: TokenType::Condition, lexeme: "?".to_string(), line: self.line, - }) + }); } } - ',' => return Ok(Token { - token_type: TokenType::Comma, - lexeme: ",".to_string(), - line: self.line, - }), + ',' => { + return Ok(Token { + token_type: TokenType::Comma, + lexeme: ",".to_string(), + line: self.line, + }) + } '\'' => { let value = self.char_literal()?; return Ok(Token { token_type: TokenType::CharLiteral(value), lexeme: format!("'{}'", value), line: self.line, - }) - }, + }); + } '"' => { let string_value = self.string()?; return Ok(Token { token_type: TokenType::String(string_value.clone()), lexeme: format!("\"{}\"", string_value), line: self.line, - }) + }); } 'a'..='z' | 'A'..='Z' | '_' => { @@ -406,15 +440,14 @@ impl<'a> Lexer<'a> { match num_str.parse::() { Ok(v) => TokenType::Float(v), Err(_) => { - return Err( - self.make_error_here( + return Err(self + .make_error_here( WaveErrorKind::InvalidNumber(num_str.clone()), format!("invalid floating-point literal `{}`", num_str), ) .with_code("E1006") .with_label("cannot parse float literal") - .with_help("check decimal point placement and digits"), - ); + .with_help("check decimal point placement and digits")); } } } else { @@ -425,40 +458,39 @@ impl<'a> Lexer<'a> { token_type, lexeme: num_str, line: self.line, - }) + }); } _ => { if c == '\0' { - return Err( - self.make_error_here( + return Err(self + .make_error_here( WaveErrorKind::UnexpectedChar(c), "null character (`\\0`) is not allowed in source", ) .with_code("E1001") .with_label("unexpected null byte in source") - .with_help("remove the null byte and save the file as plain UTF-8 text"), - ); + .with_help( + "remove the null byte and save the file as plain UTF-8 text", + )); } else if c == '\\' { - return Err( - self.make_error_here( + return Err(self + .make_error_here( WaveErrorKind::UnexpectedChar(c), "unexpected backslash outside of string literal", ) .with_code("E1001") .with_label("`\\` is only valid inside string/char literals") - .with_help("if you intended a string, wrap it with quotes"), - ); + .with_help("if you intended a string, wrap it with quotes")); } else { - return Err( - self.make_error_here( + return Err(self + .make_error_here( WaveErrorKind::UnexpectedChar(c), format!("unexpected character `{}` (U+{:04X})", c, c as u32), ) .with_code("E1001") .with_label("this character is not valid in Wave syntax") - .with_help("remove it or replace it with a valid token"), - ); + .with_help("remove it or replace it with a valid token")); } } } diff --git a/front/lexer/src/trivia.rs b/front/lexer/src/trivia.rs index d129831e..4258ba38 100644 --- a/front/lexer/src/trivia.rs +++ b/front/lexer/src/trivia.rs @@ -10,8 +10,8 @@ // SPDX-License-Identifier: MPL-2.0 use super::Lexer; -use error::WaveErrorKind; use error::WaveError; +use error::WaveErrorKind; impl<'a> Lexer<'a> { pub(crate) fn skip_trivia(&mut self) -> Result<(), WaveError> { @@ -48,7 +48,9 @@ impl<'a> Lexer<'a> { while !self.is_at_end() { let c = self.peek(); match c { - ' ' | '\r' | '\t' => { self.advance(); } + ' ' | '\r' | '\t' => { + self.advance(); + } '\n' => { self.advance(); self.line += 1; @@ -96,15 +98,14 @@ impl<'a> Lexer<'a> { self.advance(); } - Err( - self.make_error_here( + Err(self + .make_error_here( WaveErrorKind::UnterminatedComment, "unterminated block comment; expected closing `*/`", ) .with_code("E1002") .with_label("block comment starts here and never closes") .with_help("add `*/` to close the block comment") - .with_suggestion("if you intended a line comment, use `// ...`"), - ) + .with_suggestion("if you intended a line comment, use `// ...`")) } } diff --git a/front/parser/src/expr/assign.rs b/front/parser/src/expr/assign.rs index 1590d7fa..3ae84a7b 100644 --- a/front/parser/src/expr/assign.rs +++ b/front/parser/src/expr/assign.rs @@ -9,10 +9,10 @@ // // SPDX-License-Identifier: MPL-2.0 -use lexer::Token; -use lexer::token::TokenType; use crate::ast::{AssignOperator, Expression}; use crate::expr::binary::parse_logical_or_expression; +use lexer::token::TokenType; +use lexer::Token; pub fn parse_expression<'a, T>(tokens: &mut std::iter::Peekable) -> Option where @@ -49,4 +49,4 @@ where } Some(left) -} \ No newline at end of file +} diff --git a/front/parser/src/expr/helpers.rs b/front/parser/src/expr/helpers.rs index f2c59d45..4e1a703d 100644 --- a/front/parser/src/expr/helpers.rs +++ b/front/parser/src/expr/helpers.rs @@ -9,13 +9,13 @@ // // SPDX-License-Identifier: MPL-2.0 -use std::iter::Peekable; -use std::slice::Iter; -use lexer::Token; -use lexer::token::TokenType; use crate::ast::Expression; use crate::expr::parse_expression; use crate::expr::unary::parse_unary_expression; +use lexer::token::TokenType; +use lexer::Token; +use std::iter::Peekable; +use std::slice::Iter; pub fn is_assignable(expr: &Expression) -> bool { match expr { @@ -40,7 +40,10 @@ fn parse_lvalue_tail( Some(TokenType::Dot) => { tokens.next(); // '.' let field = match tokens.next() { - Some(Token { token_type: TokenType::Identifier(s), .. }) => s.clone(), + Some(Token { + token_type: TokenType::Identifier(s), + .. + }) => s.clone(), _ => { println!("Error: Expected identifier after '.'"); return None; @@ -93,4 +96,4 @@ pub fn parse_expression_from_token( _ => None, } -} \ No newline at end of file +} diff --git a/front/parser/src/expr/mod.rs b/front/parser/src/expr/mod.rs index 57be7762..24a5af64 100644 --- a/front/parser/src/expr/mod.rs +++ b/front/parser/src/expr/mod.rs @@ -9,12 +9,12 @@ // // SPDX-License-Identifier: MPL-2.0 +mod assign; +mod binary; mod helpers; -mod primary; mod postfix; +mod primary; mod unary; -mod binary; -mod assign; -pub use helpers::*; pub use assign::parse_expression; +pub use helpers::*; diff --git a/front/parser/src/expr/postfix.rs b/front/parser/src/expr/postfix.rs index 24d830d4..2733e7cd 100644 --- a/front/parser/src/expr/postfix.rs +++ b/front/parser/src/expr/postfix.rs @@ -30,9 +30,9 @@ where tokens.next(); // consume '.' let name = if let Some(Token { - token_type: TokenType::Identifier(name), - .. - }) = tokens.next() + token_type: TokenType::Identifier(name), + .. + }) = tokens.next() { name.clone() } else { @@ -41,9 +41,9 @@ where }; if let Some(Token { - token_type: TokenType::Lparen, - .. - }) = tokens.peek() + token_type: TokenType::Lparen, + .. + }) = tokens.peek() { // ----- MethodCall ----- tokens.next(); // consume '(' @@ -58,9 +58,9 @@ where args.push(arg); if let Some(Token { - token_type: TokenType::Comma, - .. - }) = tokens.peek() + token_type: TokenType::Comma, + .. + }) = tokens.peek() { tokens.next(); // consume ',' } else { @@ -119,7 +119,10 @@ where tokens.next(); // consume '++' if !is_assignable(&expr) { - println!("Error: postfix ++ target must be assignable (line {})", line); + println!( + "Error: postfix ++ target must be assignable (line {})", + line + ); return None; } @@ -137,7 +140,10 @@ where tokens.next(); // consume '--' if !is_assignable(&expr) { - println!("Error: postfix -- target must be assignable (line {})", line); + println!( + "Error: postfix -- target must be assignable (line {})", + line + ); return None; } diff --git a/front/parser/src/expr/primary.rs b/front/parser/src/expr/primary.rs index d1027e1a..6526d552 100644 --- a/front/parser/src/expr/primary.rs +++ b/front/parser/src/expr/primary.rs @@ -65,9 +65,9 @@ where args.push(arg); if let Some(Token { - token_type: TokenType::Comma, - .. - }) = tokens.peek() + token_type: TokenType::Comma, + .. + }) = tokens.peek() { tokens.next(); } else { @@ -96,9 +96,9 @@ where .map_or(false, |t| t.token_type != TokenType::Rbrace) { let field_name = if let Some(Token { - token_type: TokenType::Identifier(n), - .. - }) = tokens.next() + token_type: TokenType::Identifier(n), + .. + }) = tokens.next() { n.clone() } else { @@ -119,9 +119,9 @@ where fields.push((field_name, value)); if let Some(Token { - token_type: TokenType::Comma, - .. - }) = tokens.peek() + token_type: TokenType::Comma, + .. + }) = tokens.peek() { tokens.next(); } else { @@ -175,9 +175,9 @@ where loop { elements.push(parse_expression(tokens)?); if let Some(Token { - token_type: TokenType::Comma, - .. - }) = tokens.peek() + token_type: TokenType::Comma, + .. + }) = tokens.peek() { tokens.next(); } else { @@ -265,10 +265,18 @@ where }) } _ => match token.token_type { - TokenType::Continue | TokenType::Break | TokenType::Return | TokenType::SemiColon => None, + TokenType::Continue | TokenType::Break | TokenType::Return | TokenType::SemiColon => { + None + } _ => { - println!("Error: Expected primary expression, found {:?}", token.token_type); - println!("Error: Expected primary expression, found {:?}", token.lexeme); + println!( + "Error: Expected primary expression, found {:?}", + token.token_type + ); + println!( + "Error: Expected primary expression, found {:?}", + token.lexeme + ); println!("Error: Expected primary expression, found {:?}", token.line); None } diff --git a/front/parser/src/expr/unary.rs b/front/parser/src/expr/unary.rs index ec1b7715..963d09ef 100644 --- a/front/parser/src/expr/unary.rs +++ b/front/parser/src/expr/unary.rs @@ -9,11 +9,11 @@ // // SPDX-License-Identifier: MPL-2.0 -use lexer::Token; -use lexer::token::TokenType; use crate::ast::{Expression, IncDecKind, Literal, Operator}; use crate::expr::is_assignable; use crate::expr::primary::parse_primary_expression; +use lexer::token::TokenType; +use lexer::Token; pub fn parse_unary_expression<'a, T>(tokens: &mut std::iter::Peekable) -> Option where @@ -102,4 +102,4 @@ where } parse_primary_expression(tokens) -} \ No newline at end of file +} diff --git a/front/parser/src/import.rs b/front/parser/src/import.rs index 0ca6d850..a503a423 100644 --- a/front/parser/src/import.rs +++ b/front/parser/src/import.rs @@ -9,13 +9,157 @@ // // SPDX-License-Identifier: MPL-2.0 -use crate::ast::{ASTNode}; +use crate::ast::ASTNode; use crate::{parse_syntax_only, ParseError}; use error::error::{WaveError, WaveErrorKind}; use lexer::Lexer; use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; +enum TargetAttr<'a> { + Supported(&'a str), + Unsupported, +} + +fn parse_target_os_attr(line: &str) -> Option> { + let trimmed = line.trim(); + if !trimmed.starts_with("#[target(os=\"") || !trimmed.ends_with("\")]") { + return None; + } + + let start = "#[target(os=\"".len(); + let end = trimmed.len() - 3; // ")]" + let os = &trimmed[start..end]; + if os == "linux" || os == "macos" { + Some(TargetAttr::Supported(os)) + } else { + Some(TargetAttr::Unsupported) + } +} + +fn is_supported_target_item_start(line: &str) -> bool { + fn has_ident_boundary(rest: &str) -> bool { + match rest.chars().next() { + None => true, + Some(c) => !(c.is_ascii_alphanumeric() || c == '_'), + } + } + + let trimmed = line.trim_start(); + for kw in ["fun", "struct", "enum", "const", "static", "type", "proto"] { + if let Some(rest) = trimmed.strip_prefix(kw) { + if has_ident_boundary(rest) { + return true; + } + } + } + + false +} + +fn consume_target_item( + lines: &[&str], + mut idx: usize, + keep: bool, + out: &mut Vec, +) -> usize { + let mut depth: i32 = 0; + let mut seen_open = false; + + while idx < lines.len() { + let line = lines[idx]; + if keep { + out.push(line.to_string()); + } else { + out.push(String::new()); + } + + for ch in line.chars() { + if ch == '{' { + depth += 1; + seen_open = true; + } else if ch == '}' && depth > 0 { + depth -= 1; + } + } + + idx += 1; + + let trimmed = line.trim_end(); + if seen_open && depth == 0 { + break; + } + } + + idx +} + +fn preprocess_target_attrs(source: &str) -> String { + let host = std::env::consts::OS; + let lines: Vec<&str> = source.lines().collect(); + let mut out: Vec = Vec::with_capacity(lines.len()); + let mut idx: usize = 0; + + while idx < lines.len() { + let line = lines[idx]; + if let Some(target_attr) = parse_target_os_attr(line) { + // Attribute line is removed for parser compatibility, + // but we keep its line slot to preserve diagnostics. + out.push(String::new()); + idx += 1; + + let keep_item = match target_attr { + TargetAttr::Supported(target_os) => target_os == host, + // Ignore unsupported target values. + TargetAttr::Unsupported => true, + }; + + // Attribute applies to the next top-level item. + // Preserve line count for any leading blanks/comments. + while idx < lines.len() { + let item_line = lines[idx]; + let trimmed = item_line.trim_start(); + + let is_leading_comment = trimmed.starts_with("//") + || trimmed.starts_with("/*") + || trimmed.starts_with('*') + || trimmed.starts_with("*/"); + + if trimmed.is_empty() || is_leading_comment { + if keep_item { + out.push(item_line.to_string()); + } else { + out.push(String::new()); + } + idx += 1; + continue; + } + + if is_supported_target_item_start(trimmed) { + idx = consume_target_item(&lines, idx, keep_item, &mut out); + } else if keep_item { + out.push(item_line.to_string()); + idx += 1; + } else { + out.push(String::new()); + idx += 1; + } + break; + } + continue; + } + + out.push(line.to_string()); + idx += 1; + } + + let mut processed = out.join("\n"); + if source.ends_with('\n') { + processed.push('\n'); + } + processed +} + pub struct ImportedUnit { pub abs_path: PathBuf, pub ast: Vec, @@ -84,7 +228,10 @@ pub fn local_import( already_imported: &mut HashSet, base_dir: &Path, ) -> Result, WaveError> { - Ok(local_import_unit_with_config(path, already_imported, base_dir, &ImportConfig::default())?.ast) + Ok( + local_import_unit_with_config(path, already_imported, base_dir, &ImportConfig::default())? + .ast, + ) } pub fn local_import_with_config( @@ -96,7 +243,10 @@ pub fn local_import_with_config( Ok(local_import_unit_with_config(path, already_imported, base_dir, config)?.ast) } -fn resolve_external_package_root(package: &str, config: &ImportConfig) -> Result, Vec> { +fn resolve_external_package_root( + package: &str, + config: &ImportConfig, +) -> Result, Vec> { if let Some(path) = config.dep_packages.get(package) { return Ok(Some(path.clone())); } @@ -129,19 +279,17 @@ fn external_import_unit( || module_parts.is_empty() || module_parts.iter().any(|s| s.trim().is_empty()) { - return Err( - WaveError::new( - WaveErrorKind::SyntaxError("Invalid external import path".to_string()), - format!( - "invalid external import '{}': expected `package::module::path`", - path - ), - path, - 0, - 0, - ) - .with_help("use at least two segments, for example: import(\"math::vector::ops\")"), - ); + return Err(WaveError::new( + WaveErrorKind::SyntaxError("Invalid external import path".to_string()), + format!( + "invalid external import '{}': expected `package::module::path`", + path + ), + path, + 0, + 0, + ) + .with_help("use at least two segments, for example: import(\"math::vector::ops\")")); } let package_root = match resolve_external_package_root(package, config) { @@ -182,38 +330,34 @@ fn external_import_unit( .collect::>() .join(", "); - return Err( - WaveError::new( - WaveErrorKind::SyntaxError("Ambiguous external package root".to_string()), - format!( - "package '{}' is found in multiple dependency roots; resolution is ambiguous", - package - ), - path, - 0, - 0, - ) - .with_note(format!("candidates: {}", roots)) - .with_help("pin the package path explicitly with `--dep =`"), - ); - } - }; - - if !package_root.is_dir() { - return Err( - WaveError::new( - WaveErrorKind::SyntaxError("Dependency path is not a directory".to_string()), + return Err(WaveError::new( + WaveErrorKind::SyntaxError("Ambiguous external package root".to_string()), format!( - "configured dependency path for package '{}' is invalid: {}", - package, - package_root.display() + "package '{}' is found in multiple dependency roots; resolution is ambiguous", + package ), path, 0, 0, ) - .with_help("pass a valid directory path via `--dep =`"), - ); + .with_note(format!("candidates: {}", roots)) + .with_help("pin the package path explicitly with `--dep =`")); + } + }; + + if !package_root.is_dir() { + return Err(WaveError::new( + WaveErrorKind::SyntaxError("Dependency path is not a directory".to_string()), + format!( + "configured dependency path for package '{}' is invalid: {}", + package, + package_root.display() + ), + path, + 0, + 0, + ) + .with_help("pass a valid directory path via `--dep =`")); } let module_rel = module_parts.join("/"); @@ -240,23 +384,24 @@ fn external_import_unit( .collect::>() .join(", "); - Err( - WaveError::new( - WaveErrorKind::SyntaxError("File not found".to_string()), - format!( - "could not find external import target '{}' in package '{}'", - path, package - ), - path, - 0, - 0, - ) - .with_note(format!("searched paths: {}", searched)) - .with_help("check package/module names or pass an explicit path with `--dep =`"), + Err(WaveError::new( + WaveErrorKind::SyntaxError("File not found".to_string()), + format!( + "could not find external import target '{}' in package '{}'", + path, package + ), + path, + 0, + 0, ) + .with_note(format!("searched paths: {}", searched)) + .with_help("check package/module names or pass an explicit path with `--dep =`")) } -fn std_import_unit(path: &str, already_imported: &mut HashSet) -> Result { +fn std_import_unit( + path: &str, + already_imported: &mut HashSet, +) -> Result { let rel = path.strip_prefix("std::").unwrap(); if rel.trim().is_empty() { return Err(WaveError::new( @@ -277,7 +422,10 @@ fn std_import_unit(path: &str, already_imported: &mut HashSet) -> Result if !found_path.exists() || !found_path.is_file() { return Err(WaveError::new( WaveErrorKind::SyntaxError("File not found".to_string()), - format!("Could not find std import target '{}'", found_path.display()), + format!( + "Could not find std import target '{}'", + found_path.display() + ), path, 0, 0, @@ -330,11 +478,14 @@ fn parse_wave_file( .to_string(); if already_imported.contains(&abs_path_str) { - return Ok(ImportedUnit { abs_path, ast: vec![] }); + return Ok(ImportedUnit { + abs_path, + ast: vec![], + }); } already_imported.insert(abs_path_str); - let content = std::fs::read_to_string(&abs_path).map_err(|e| { + let raw_content = std::fs::read_to_string(&abs_path).map_err(|e| { WaveError::new( WaveErrorKind::SyntaxError("Read error".to_string()), format!("Failed to read '{}': {}", abs_path.display(), e), @@ -343,19 +494,33 @@ fn parse_wave_file( 0, ) })?; + let content = preprocess_target_attrs(&raw_content); let mut lexer = Lexer::new_with_file(&content, abs_path.display().to_string()); let tokens = lexer.tokenize()?; let ast = parse_syntax_only(&tokens).map_err(|e| { let (kind, phase, code) = match &e { - ParseError::Syntax(_) => (WaveErrorKind::SyntaxError(e.message().to_string()), "syntax", "E2001"), - ParseError::Semantic(_) => (WaveErrorKind::InvalidStatement(e.message().to_string()), "semantic", "E3001"), + ParseError::Syntax(_) => ( + WaveErrorKind::SyntaxError(e.message().to_string()), + "syntax", + "E2001", + ), + ParseError::Semantic(_) => ( + WaveErrorKind::InvalidStatement(e.message().to_string()), + "semantic", + "E3001", + ), }; let mut we = WaveError::new( kind, - format!("{} validation failed for '{}': {}", phase, abs_path.display(), e.message()), + format!( + "{} validation failed for '{}': {}", + phase, + abs_path.display(), + e.message() + ), display_name, e.line().max(1), e.column().max(1), diff --git a/front/parser/src/lib.rs b/front/parser/src/lib.rs index 8deeef6e..18ec33e3 100644 --- a/front/parser/src/lib.rs +++ b/front/parser/src/lib.rs @@ -17,12 +17,12 @@ macro_rules! println { }}; } -pub mod parser; pub mod ast; +pub mod expr; pub mod format; pub mod import; +pub mod parser; pub mod stdlib; pub mod verification; -pub mod expr; pub use parser::*; diff --git a/front/parser/src/parser/asm.rs b/front/parser/src/parser/asm.rs index 3c8a83a2..d3e96c8e 100644 --- a/front/parser/src/parser/asm.rs +++ b/front/parser/src/parser/asm.rs @@ -9,12 +9,12 @@ // // SPDX-License-Identifier: MPL-2.0 -use std::iter::Peekable; -use std::slice::Iter; -use lexer::Token; -use lexer::token::TokenType; use crate::ast::{ASTNode, Expression, Literal, StatementNode}; use crate::expr::is_assignable; +use lexer::token::TokenType; +use lexer::Token; +use std::iter::Peekable; +use std::slice::Iter; pub fn parse_asm_block(tokens: &mut Peekable>) -> Option { if tokens.peek()?.token_type != TokenType::Lbrace { @@ -118,8 +118,14 @@ where loop { let item = match tokens.next() { - Some(Token { token_type: TokenType::String(s), .. }) => s.clone(), - Some(Token { token_type: TokenType::Identifier(s), .. }) => s.clone(), + Some(Token { + token_type: TokenType::String(s), + .. + }) => s.clone(), + Some(Token { + token_type: TokenType::Identifier(s), + .. + }) => s.clone(), Some(other) => { println!( "Expected clobber item (string/identifier), got {:?}", @@ -145,10 +151,7 @@ where break; } other => { - println!( - "Expected ',' or ')' in clobber(...), got {:?}", - other - ); + println!("Expected ',' or ')' in clobber(...), got {:?}", other); return None; } } @@ -173,10 +176,19 @@ where tokens.next(); // '(' let reg = match tokens.next() { - Some(Token { token_type: TokenType::String(s), .. }) => s.clone(), - Some(Token { token_type: TokenType::Identifier(s), .. }) => s.clone(), + Some(Token { + token_type: TokenType::String(s), + .. + }) => s.clone(), + Some(Token { + token_type: TokenType::Identifier(s), + .. + }) => s.clone(), Some(other) => { - println!("Expected register string or identifier, got {:?}", other.token_type); + println!( + "Expected register string or identifier, got {:?}", + other.token_type + ); return None; } None => { @@ -220,7 +232,9 @@ where // &x let next = tokens.next()?; match &next.token_type { - TokenType::Identifier(s) => Some(Expression::AddressOf(Box::new(Expression::Variable(s.clone())))), + TokenType::Identifier(s) => Some(Expression::AddressOf(Box::new( + Expression::Variable(s.clone()), + ))), _ => { println!("Expected identifier after '&' in in/out(...)"); None @@ -231,7 +245,9 @@ where TokenType::Deref => { let next = tokens.next()?; match &next.token_type { - TokenType::Identifier(s) => Some(Expression::Deref(Box::new(Expression::Variable(s.clone())))), + TokenType::Identifier(s) => { + Some(Expression::Deref(Box::new(Expression::Variable(s.clone())))) + } _ => { println!("Expected identifier after 'deref' in in/out(...)"); None @@ -239,27 +255,27 @@ where } } - TokenType::Minus => { - match tokens.next()? { - Token { token_type: TokenType::IntLiteral(n), .. } => { - Some(Expression::Literal(Literal::Int(format!("-{}", n)))) - } - Token { token_type: TokenType::Float(f), .. } => { - Some(Expression::Literal(Literal::Float(-*f))) - } - other => { - println!( - "Expected int/float after '-' in asm operand, got {:?}", - other.token_type - ); - None - } + TokenType::Minus => match tokens.next()? { + Token { + token_type: TokenType::IntLiteral(n), + .. + } => Some(Expression::Literal(Literal::Int(format!("-{}", n)))), + Token { + token_type: TokenType::Float(f), + .. + } => Some(Expression::Literal(Literal::Float(-*f))), + other => { + println!( + "Expected int/float after '-' in asm operand, got {:?}", + other.token_type + ); + None } - } + }, other => { println!("Expected asm operand, got {:?}", other); None } } -} \ No newline at end of file +} diff --git a/front/parser/src/parser/control.rs b/front/parser/src/parser/control.rs index a33ab3fd..2c35170e 100644 --- a/front/parser/src/parser/control.rs +++ b/front/parser/src/parser/control.rs @@ -9,14 +9,16 @@ // // SPDX-License-Identifier: MPL-2.0 -use std::iter::Peekable; -use std::slice::Iter; -use lexer::Token; -use lexer::token::TokenType; -use crate::ast::{ASTNode, Expression, MatchArm, MatchPattern, Mutability, StatementNode, VariableNode}; +use crate::ast::{ + ASTNode, Expression, MatchArm, MatchPattern, Mutability, StatementNode, VariableNode, +}; use crate::expr::parse_expression; use crate::parser::stmt::parse_block; use crate::parser::types::parse_type_from_stream; +use lexer::token::TokenType; +use lexer::Token; +use std::iter::Peekable; +use std::slice::Iter; fn skip_ws_and_newlines(tokens: &mut Peekable>) { while let Some(t) = tokens.peek() { @@ -40,7 +42,10 @@ fn expect_fat_arrow(tokens: &mut Peekable>) -> bool { skip_ws_and_newlines(tokens); - if !matches!(tokens.peek().map(|t| &t.token_type), Some(TokenType::Rchevr)) { + if !matches!( + tokens.peek().map(|t| &t.token_type), + Some(TokenType::Rchevr) + ) { println!("Error: Expected '>' in match arm (use `=>`)"); return false; } @@ -109,9 +114,9 @@ pub fn parse_if(tokens: &mut Peekable>) -> Option { tokens.next(); // consume 'else' if let Some(Token { - token_type: TokenType::If, - .. - }) = tokens.peek() + token_type: TokenType::If, + .. + }) = tokens.peek() { tokens.next(); // consume 'if' @@ -174,9 +179,9 @@ fn parse_typed_for_initializer( ) -> Option { let name = match tokens.next() { Some(Token { - token_type: TokenType::Identifier(name), - .. - }) => name.clone(), + token_type: TokenType::Identifier(name), + .. + }) => name.clone(), _ => { println!("Error: Expected identifier in for-loop initializer"); return None; @@ -184,7 +189,10 @@ fn parse_typed_for_initializer( }; if tokens.peek()?.token_type != TokenType::Colon { - println!("Error: Expected ':' after '{}' in for-loop initializer", name); + println!( + "Error: Expected ':' after '{}' in for-loop initializer", + name + ); return None; } tokens.next(); // consume ':' @@ -237,7 +245,9 @@ fn parse_for_initializer(tokens: &mut Peekable>) -> Option println!("Error: `static` is not allowed in local for-loop initializer"); None } - _ if is_typed_for_initializer(tokens) => parse_typed_for_initializer(tokens, Mutability::Var), + _ if is_typed_for_initializer(tokens) => { + parse_typed_for_initializer(tokens, Mutability::Var) + } _ => { let expr = parse_expression(tokens)?; Some(ASTNode::Statement(StatementNode::Expression(expr))) @@ -351,7 +361,10 @@ pub fn parse_match(tokens: &mut Peekable>) -> Option { loop { skip_ws_and_newlines(tokens); - if matches!(tokens.peek().map(|t| &t.token_type), Some(TokenType::Rbrace)) { + if matches!( + tokens.peek().map(|t| &t.token_type), + Some(TokenType::Rbrace) + ) { tokens.next(); // consume '}' break; } @@ -380,7 +393,10 @@ pub fn parse_match(tokens: &mut Peekable>) -> Option { arms.push(MatchArm { pattern, body }); skip_ws_and_newlines(tokens); - if matches!(tokens.peek().map(|t| &t.token_type), Some(TokenType::Comma | TokenType::SemiColon)) { + if matches!( + tokens.peek().map(|t| &t.token_type), + Some(TokenType::Comma | TokenType::SemiColon) + ) { tokens.next(); } } diff --git a/front/parser/src/parser/expr.rs b/front/parser/src/parser/expr.rs index c1a8fd3b..e74c81c5 100644 --- a/front/parser/src/parser/expr.rs +++ b/front/parser/src/parser/expr.rs @@ -9,12 +9,12 @@ // // SPDX-License-Identifier: MPL-2.0 -use std::iter::Peekable; -use std::slice::Iter; -use lexer::Token; -use lexer::token::TokenType; use crate::ast::Expression; use crate::expr::parse_expression; +use lexer::token::TokenType; +use lexer::Token; +use std::iter::Peekable; +use std::slice::Iter; pub fn parse_function_call( name: Option, @@ -75,4 +75,4 @@ pub fn parse_parentheses(tokens: &mut Peekable>) -> Vec { param_tokens.push(token.clone()); } param_tokens -} \ No newline at end of file +} diff --git a/front/parser/src/parser/functions.rs b/front/parser/src/parser/functions.rs index 52fa50f5..f88deac7 100644 --- a/front/parser/src/parser/functions.rs +++ b/front/parser/src/parser/functions.rs @@ -9,11 +9,6 @@ // // SPDX-License-Identifier: MPL-2.0 -use std::collections::HashSet; -use std::iter::Peekable; -use std::slice::Iter; -use lexer::Token; -use lexer::token::TokenType; use crate::ast::{ASTNode, FunctionNode, ParameterNode, StatementNode, Value}; use crate::expr::parse_expression; use crate::parser::asm::*; @@ -22,6 +17,11 @@ use crate::parser::decl::*; use crate::parser::io::*; use crate::parser::stmt::parse_assignment; use crate::parser::types::parse_type_from_stream; +use lexer::token::TokenType; +use lexer::Token; +use std::collections::HashSet; +use std::iter::Peekable; +use std::slice::Iter; pub fn parse_parameters(tokens: &mut Peekable>) -> Vec { let mut params = vec![]; @@ -30,9 +30,9 @@ pub fn parse_parameters(tokens: &mut Peekable>) -> Vec>) -> Vec>) -> Option { let name = match tokens.next() { Some(Token { - token_type: TokenType::Identifier(name), - .. - }) => name.clone(), + token_type: TokenType::Identifier(name), + .. + }) => name.clone(), _ => return None, }; @@ -150,9 +150,9 @@ pub fn parse_function(tokens: &mut Peekable>) -> Option { } let return_type = if let Some(Token { - token_type: TokenType::Arrow, - .. - }) = tokens.peek() + token_type: TokenType::Arrow, + .. + }) = tokens.peek() { tokens.next(); // consume '->' parse_type_from_stream(tokens) @@ -216,9 +216,9 @@ pub fn extract_body(tokens: &mut Peekable>) -> Option> let node = parse_println(tokens)?; // Added semicolon handling if let Some(Token { - token_type: TokenType::SemiColon, - .. - }) = tokens.peek() + token_type: TokenType::SemiColon, + .. + }) = tokens.peek() { tokens.next(); } @@ -229,9 +229,9 @@ pub fn extract_body(tokens: &mut Peekable>) -> Option> let node = parse_print(tokens)?; // Added semicolon handling if let Some(Token { - token_type: TokenType::SemiColon, - .. - }) = tokens.peek() + token_type: TokenType::SemiColon, + .. + }) = tokens.peek() { tokens.next(); } @@ -242,9 +242,9 @@ pub fn extract_body(tokens: &mut Peekable>) -> Option> let node = parse_input(tokens)?; // Added semicolon handling if let Some(Token { - token_type: TokenType::SemiColon, - .. - }) = tokens.peek() + token_type: TokenType::SemiColon, + .. + }) = tokens.peek() { tokens.next(); } @@ -269,9 +269,9 @@ pub fn extract_body(tokens: &mut Peekable>) -> Option> TokenType::Identifier(_) => { if let Some(expr) = parse_expression(tokens) { if let Some(Token { - token_type: TokenType::SemiColon, - .. - }) = tokens.peek() + token_type: TokenType::SemiColon, + .. + }) = tokens.peek() { tokens.next(); // consume ';' } @@ -284,9 +284,9 @@ pub fn extract_body(tokens: &mut Peekable>) -> Option> TokenType::Break => { tokens.next(); // consume 'break' if let Some(Token { - token_type: TokenType::SemiColon, - .. - }) = tokens.peek() + token_type: TokenType::SemiColon, + .. + }) = tokens.peek() { tokens.next(); // consume ; } @@ -295,9 +295,9 @@ pub fn extract_body(tokens: &mut Peekable>) -> Option> TokenType::Continue => { tokens.next(); // consume 'continue' if let Some(Token { - token_type: TokenType::SemiColon, - .. - }) = tokens.peek() + token_type: TokenType::SemiColon, + .. + }) = tokens.peek() { tokens.next(); // consume ; } @@ -307,9 +307,9 @@ pub fn extract_body(tokens: &mut Peekable>) -> Option> tokens.next(); // consume 'return' let expr = if let Some(Token { - token_type: TokenType::SemiColon, - .. - }) = tokens.peek() + token_type: TokenType::SemiColon, + .. + }) = tokens.peek() { tokens.next(); // return; None @@ -323,9 +323,9 @@ pub fn extract_body(tokens: &mut Peekable>) -> Option> }; if let Some(Token { - token_type: TokenType::SemiColon, - .. - }) = tokens.peek() + token_type: TokenType::SemiColon, + .. + }) = tokens.peek() { tokens.next(); } else { @@ -345,9 +345,9 @@ pub fn extract_body(tokens: &mut Peekable>) -> Option> _ => { if let Some(expr) = parse_expression(tokens) { if let Some(Token { - token_type: TokenType::SemiColon, - .. - }) = tokens.peek() + token_type: TokenType::SemiColon, + .. + }) = tokens.peek() { tokens.next(); // consume ; } diff --git a/front/parser/src/parser/io.rs b/front/parser/src/parser/io.rs index 793b1f38..ade3376d 100644 --- a/front/parser/src/parser/io.rs +++ b/front/parser/src/parser/io.rs @@ -9,13 +9,13 @@ // // SPDX-License-Identifier: MPL-2.0 +use crate::ast::{ASTNode, StatementNode}; +use crate::expr::parse_expression; +use lexer::token::TokenType; +use lexer::Token; use std::iter::Peekable; use std::slice::Iter; use utils::formatx::*; -use lexer::Token; -use lexer::token::TokenType; -use crate::ast::{ASTNode, StatementNode}; -use crate::expr::parse_expression; // PRINTLN parsing pub fn parse_println(tokens: &mut Peekable>) -> Option { @@ -26,9 +26,9 @@ pub fn parse_println(tokens: &mut Peekable>) -> Option { tokens.next(); // Consume '(' let content = if let Some(Token { - token_type: TokenType::String(content), - .. - }) = tokens.next() + token_type: TokenType::String(content), + .. + }) = tokens.next() { content.clone() } else { @@ -59,9 +59,9 @@ pub fn parse_println(tokens: &mut Peekable>) -> Option { let mut args = Vec::new(); while let Some(Token { - token_type: TokenType::Comma, - .. - }) = tokens.peek() + token_type: TokenType::Comma, + .. + }) = tokens.peek() { tokens.next(); // Consume ',' if let Some(expr) = parse_expression(tokens) { @@ -108,9 +108,9 @@ pub fn parse_print(tokens: &mut Peekable>) -> Option { tokens.next(); // Consume '(' let content = if let Some(Token { - token_type: TokenType::String(content), - .. - }) = tokens.next() + token_type: TokenType::String(content), + .. + }) = tokens.next() { content.clone() // Need clone() because it is String } else { @@ -142,9 +142,9 @@ pub fn parse_print(tokens: &mut Peekable>) -> Option { let mut args = Vec::new(); while let Some(Token { - token_type: TokenType::Comma, - .. - }) = tokens.peek() + token_type: TokenType::Comma, + .. + }) = tokens.peek() { tokens.next(); // Consume ',' if let Some(expr) = parse_expression(tokens) { @@ -190,9 +190,9 @@ pub fn parse_input(tokens: &mut Peekable>) -> Option { tokens.next(); // Consume '(' let content = if let Some(Token { - token_type: TokenType::String(content), - .. - }) = tokens.next() + token_type: TokenType::String(content), + .. + }) = tokens.next() { content.clone() // Need clone() because it is String } else { @@ -204,9 +204,9 @@ pub fn parse_input(tokens: &mut Peekable>) -> Option { let mut args = Vec::new(); while let Some(Token { - token_type: TokenType::Comma, - .. - }) = tokens.peek() + token_type: TokenType::Comma, + .. + }) = tokens.peek() { tokens.next(); // Consume ',' if let Some(expr) = parse_expression(tokens) { @@ -242,4 +242,4 @@ pub fn parse_input(tokens: &mut Peekable>) -> Option { format: content, args, })) -} \ No newline at end of file +} diff --git a/front/parser/src/parser/items.rs b/front/parser/src/parser/items.rs index 3edc4b11..7a29f030 100644 --- a/front/parser/src/parser/items.rs +++ b/front/parser/src/parser/items.rs @@ -9,13 +9,13 @@ // // SPDX-License-Identifier: MPL-2.0 -use std::iter::Peekable; -use std::slice::Iter; -use lexer::Token; -use lexer::token::TokenType; use crate::ast::{ASTNode, ProtoImplNode, StatementNode, StructNode, WaveType}; use crate::parser::functions::parse_function; use crate::types::parse_type_from_stream; +use lexer::token::TokenType; +use lexer::Token; +use std::iter::Peekable; +use std::slice::Iter; fn skip_ws(tokens: &mut Peekable>) { while let Some(t) = tokens.peek() { @@ -37,9 +37,9 @@ pub fn parse_import(tokens: &mut Peekable>) -> Option { let import_path = match tokens.next() { Some(Token { - token_type: TokenType::String(s), - .. - }) => s.clone(), + token_type: TokenType::String(s), + .. + }) => s.clone(), other => { println!( "Error: Expected string literal in import, found {:?}", @@ -67,9 +67,9 @@ pub fn parse_import(tokens: &mut Peekable>) -> Option { pub fn parse_proto(tokens: &mut Peekable>) -> Option { let target_struct = match tokens.next() { Some(Token { - token_type: TokenType::Identifier(name), - .. - }) => name.clone(), + token_type: TokenType::Identifier(name), + .. + }) => name.clone(), other => { println!( "Error: Expected struct name after 'proto', found {:?}", @@ -142,9 +142,9 @@ pub fn parse_proto(tokens: &mut Peekable>) -> Option { pub fn parse_struct(tokens: &mut Peekable>) -> Option { let name = match tokens.next() { Some(Token { - token_type: TokenType::Identifier(name), - .. - }) => name.clone(), + token_type: TokenType::Identifier(name), + .. + }) => name.clone(), _ => { println!("Error: Expected struct name after 'struct' keyword."); return None; @@ -205,13 +205,22 @@ pub fn parse_struct(tokens: &mut Peekable>) -> Option { lookahead.next(); while let Some(t) = lookahead.peek() { match t.token_type { - TokenType::Whitespace | TokenType::Newline => { lookahead.next(); } + TokenType::Whitespace | TokenType::Newline => { + lookahead.next(); + } _ => break, } } - if matches!(lookahead.peek().map(|t| &t.token_type), Some(TokenType::Colon)) { - let field_name = if let Some(Token { token_type: TokenType::Identifier(n), .. }) = tokens.next() { + if matches!( + lookahead.peek().map(|t| &t.token_type), + Some(TokenType::Colon) + ) { + let field_name = if let Some(Token { + token_type: TokenType::Identifier(n), + .. + }) = tokens.next() + { n.clone() } else { unreachable!() @@ -220,8 +229,14 @@ pub fn parse_struct(tokens: &mut Peekable>) -> Option { skip_ws(tokens); // ':' - if tokens.peek().map_or(true, |t| t.token_type != TokenType::Colon) { - println!("Error: Expected ':' after field '{}' in struct '{}'.", field_name, name); + if tokens + .peek() + .map_or(true, |t| t.token_type != TokenType::Colon) + { + println!( + "Error: Expected ':' after field '{}' in struct '{}'.", + field_name, name + ); return None; } tokens.next(); // consume ':' @@ -241,7 +256,10 @@ pub fn parse_struct(tokens: &mut Peekable>) -> Option { skip_ws(tokens); - if tokens.peek().map_or(true, |t| t.token_type != TokenType::SemiColon) { + if tokens + .peek() + .map_or(true, |t| t.token_type != TokenType::SemiColon) + { println!( "Error: Expected ';' after field declaration in struct '{}'.", name @@ -252,11 +270,12 @@ pub fn parse_struct(tokens: &mut Peekable>) -> Option { fields.push((field_name, wave_type)); } else { - let id_str = if let TokenType::Identifier(id) = &tokens.peek().unwrap().token_type { - id.clone() - } else { - "".to_string() - }; + let id_str = + if let TokenType::Identifier(id) = &tokens.peek().unwrap().token_type { + id.clone() + } else { + "".to_string() + }; println!( "Error: Unexpected identifier '{}' in struct '{}' body. Expected field or method.", id_str, name @@ -280,4 +299,4 @@ pub fn parse_struct(tokens: &mut Peekable>) -> Option { fields, methods, })) -} \ No newline at end of file +} diff --git a/front/parser/src/parser/mod.rs b/front/parser/src/parser/mod.rs index f0f2cec5..0b7864bb 100644 --- a/front/parser/src/parser/mod.rs +++ b/front/parser/src/parser/mod.rs @@ -9,7 +9,6 @@ // // SPDX-License-Identifier: MPL-2.0 -mod parse; pub mod asm; pub mod control; pub mod decl; @@ -17,6 +16,7 @@ pub mod expr; pub mod functions; pub mod io; pub mod items; +mod parse; pub mod stmt; pub mod types; diff --git a/front/parser/src/parser/parse.rs b/front/parser/src/parser/parse.rs index 7d8eea92..9c35622f 100644 --- a/front/parser/src/parser/parse.rs +++ b/front/parser/src/parser/parse.rs @@ -9,13 +9,13 @@ // // SPDX-License-Identifier: MPL-2.0 -use lexer::Token; -use lexer::token::TokenType; use crate::ast::ASTNode; use crate::parser::decl::*; use crate::parser::functions::parse_function; use crate::parser::items::*; use crate::verification::*; +use lexer::token::TokenType; +use lexer::Token; #[derive(Debug, Clone, PartialEq, Eq)] pub struct ParseDiagnostic { @@ -204,13 +204,14 @@ pub fn parse_syntax_only(tokens: &[Token]) -> Result, ParseError> { if let Some(path) = parse_import(&mut iter) { nodes.push(path); } else { - return Err( - ParseError::syntax_at(Some(&anchor), "failed to parse import declaration") - .with_context("top-level import") - .with_expected("import(\"path\");") - .with_found_token(iter.peek().copied()) - .with_help("imports must use parentheses and end with ';'"), - ); + return Err(ParseError::syntax_at( + Some(&anchor), + "failed to parse import declaration", + ) + .with_context("top-level import") + .with_expected("import(\"path\");") + .with_found_token(iter.peek().copied()) + .with_help("imports must use parentheses and end with ';'")); } } TokenType::Extern => { @@ -219,16 +220,17 @@ pub fn parse_syntax_only(tokens: &[Token]) -> Result, ParseError> { if let Some(extern_nodes) = parse_extern(&mut iter) { nodes.extend(extern_nodes); } else { - return Err( - ParseError::syntax_at(Some(&anchor), "failed to parse extern declaration") - .with_context("top-level extern block/declaration") - .with_expected_many([ - "extern(c) fun name(...);", - "extern(c) { fun a(...); fun b(...); }", - ]) - .with_found_token(iter.peek().copied()) - .with_help("check ABI syntax, function signature, and separators"), - ); + return Err(ParseError::syntax_at( + Some(&anchor), + "failed to parse extern declaration", + ) + .with_context("top-level extern block/declaration") + .with_expected_many([ + "extern(c) fun name(...);", + "extern(c) { fun a(...); fun b(...); }", + ]) + .with_found_token(iter.peek().copied()) + .with_help("check ABI syntax, function signature, and separators")); } } TokenType::Const => { @@ -237,13 +239,14 @@ pub fn parse_syntax_only(tokens: &[Token]) -> Result, ParseError> { if let Some(var) = parse_const(&mut iter) { nodes.push(var); } else { - return Err( - ParseError::syntax_at(Some(&anchor), "failed to parse const declaration") - .with_context("top-level constant declaration") - .with_expected("const name: type = value;") - .with_found_token(iter.peek().copied()) - .with_help("const declarations require explicit type and initializer"), - ); + return Err(ParseError::syntax_at( + Some(&anchor), + "failed to parse const declaration", + ) + .with_context("top-level constant declaration") + .with_expected("const name: type = value;") + .with_found_token(iter.peek().copied()) + .with_help("const declarations require explicit type and initializer")); } } TokenType::Static => { @@ -252,13 +255,14 @@ pub fn parse_syntax_only(tokens: &[Token]) -> Result, ParseError> { if let Some(var) = parse_static(&mut iter) { nodes.push(var); } else { - return Err( - ParseError::syntax_at(Some(&anchor), "failed to parse static declaration") - .with_context("top-level static declaration") - .with_expected("static name: type = value;") - .with_found_token(iter.peek().copied()) - .with_help("static declarations require an explicit type"), - ); + return Err(ParseError::syntax_at( + Some(&anchor), + "failed to parse static declaration", + ) + .with_context("top-level static declaration") + .with_expected("static name: type = value;") + .with_found_token(iter.peek().copied()) + .with_help("static declarations require an explicit type")); } } TokenType::Proto => { @@ -267,13 +271,14 @@ pub fn parse_syntax_only(tokens: &[Token]) -> Result, ParseError> { if let Some(proto_impl) = parse_proto(&mut iter) { nodes.push(proto_impl); } else { - return Err( - ParseError::syntax_at(Some(&anchor), "failed to parse proto implementation") - .with_context("top-level proto block") - .with_expected("proto Type { fun method(...); }") - .with_found_token(iter.peek().copied()) - .with_help("check braces and method declarations inside proto"), - ); + return Err(ParseError::syntax_at( + Some(&anchor), + "failed to parse proto implementation", + ) + .with_context("top-level proto block") + .with_expected("proto Type { fun method(...); }") + .with_found_token(iter.peek().copied()) + .with_help("check braces and method declarations inside proto")); } } TokenType::Type => { @@ -297,13 +302,14 @@ pub fn parse_syntax_only(tokens: &[Token]) -> Result, ParseError> { if let Some(node) = parse_enum(&mut iter) { nodes.push(node); } else { - return Err( - ParseError::syntax_at(Some(&anchor), "failed to parse enum declaration") - .with_context("top-level enum declaration") - .with_expected("enum Name -> i32 { A = 0, B = 1 }") - .with_found_token(iter.peek().copied()) - .with_help("check enum repr type, braces, and variant values"), - ); + return Err(ParseError::syntax_at( + Some(&anchor), + "failed to parse enum declaration", + ) + .with_context("top-level enum declaration") + .with_expected("enum Name -> i32 { A = 0, B = 1 }") + .with_found_token(iter.peek().copied()) + .with_help("check enum repr type, braces, and variant values")); } } TokenType::Struct => { @@ -312,13 +318,14 @@ pub fn parse_syntax_only(tokens: &[Token]) -> Result, ParseError> { if let Some(struct_node) = parse_struct(&mut iter) { nodes.push(struct_node); } else { - return Err( - ParseError::syntax_at(Some(&anchor), "failed to parse struct declaration") - .with_context("top-level struct declaration") - .with_expected("struct Name { field: type; fun method(...) { ... } }") - .with_found_token(iter.peek().copied()) - .with_help("check field separators (`;`) and method bodies"), - ); + return Err(ParseError::syntax_at( + Some(&anchor), + "failed to parse struct declaration", + ) + .with_context("top-level struct declaration") + .with_expected("struct Name { field: type; fun method(...) { ... } }") + .with_found_token(iter.peek().copied()) + .with_help("check field separators (`;`) and method bodies")); } } TokenType::Fun => { @@ -326,16 +333,19 @@ pub fn parse_syntax_only(tokens: &[Token]) -> Result, ParseError> { if let Some(func) = parse_function(&mut iter) { nodes.push(func); } else { - return Err( - ParseError::syntax_at(Some(&anchor), "failed to parse function declaration") - .with_context("top-level function") - .with_expected_many([ - "fun name(params) { ... }", - "fun name(params) -> return_type { ... }", - ]) - .with_found_token(iter.peek().copied()) - .with_help("check parameter syntax, return type arrow, and function body braces"), - ); + return Err(ParseError::syntax_at( + Some(&anchor), + "failed to parse function declaration", + ) + .with_context("top-level function") + .with_expected_many([ + "fun name(params) { ... }", + "fun name(params) -> return_type { ... }", + ]) + .with_found_token(iter.peek().copied()) + .with_help( + "check parameter syntax, return type arrow, and function body braces", + )); } } TokenType::Eof => break, @@ -344,15 +354,8 @@ pub fn parse_syntax_only(tokens: &[Token]) -> Result, ParseError> { ParseError::syntax_at(Some(token), "unexpected token at top level") .with_context("top-level items") .with_expected_many([ - "import", - "extern", - "const", - "static", - "type", - "enum", - "struct", - "proto", - "fun", + "import", "extern", "const", "static", "type", "enum", "struct", + "proto", "fun", ]) .with_found_token(Some(token)) .with_help("only declarations are allowed at top level"), @@ -368,11 +371,9 @@ pub fn parse(tokens: &[Token]) -> Result, ParseError> { let nodes = parse_syntax_only(tokens)?; if let Err(e) = validate_program(&nodes) { - return Err( - ParseError::semantic(e) - .with_context("semantic validation") - .with_help("fix mutability, scope, and expression validity issues"), - ); + return Err(ParseError::semantic(e) + .with_context("semantic validation") + .with_help("fix mutability, scope, and expression validity issues")); } Ok(nodes) diff --git a/front/parser/src/parser/stmt.rs b/front/parser/src/parser/stmt.rs index aec71553..0ea4df81 100644 --- a/front/parser/src/parser/stmt.rs +++ b/front/parser/src/parser/stmt.rs @@ -9,18 +9,21 @@ // // SPDX-License-Identifier: MPL-2.0 -use std::iter::Peekable; -use std::slice::Iter; -use lexer::Token; -use lexer::token::TokenType; use crate::ast::{ASTNode, AssignOperator, Expression, Operator, StatementNode}; use crate::expr::{is_assignable, parse_expression, parse_expression_from_token}; use crate::parser::control::{parse_for, parse_if, parse_match, parse_while}; use crate::parser::decl::{parse_let, parse_var}; use crate::parser::io::*; use crate::parser::types::is_expression_start; +use lexer::token::TokenType; +use lexer::Token; +use std::iter::Peekable; +use std::slice::Iter; -pub fn parse_assignment(tokens: &mut Peekable>, first_token: &Token) -> Option { +pub fn parse_assignment( + tokens: &mut Peekable>, + first_token: &Token, +) -> Option { let left_expr = match parse_expression_from_token(first_token, tokens) { Some(expr) => expr, None => { @@ -63,9 +66,9 @@ pub fn parse_assignment(tokens: &mut Peekable>, first_token: &Token) let right_expr = parse_expression(tokens)?; if let Some(Token { - token_type: TokenType::SemiColon, - .. - }) = tokens.peek() + token_type: TokenType::SemiColon, + .. + }) = tokens.peek() { tokens.next(); } @@ -152,7 +155,10 @@ pub fn parse_statement(tokens: &mut Peekable>) -> Option { None => return None, }; - if matches!(token.token_type, TokenType::Identifier(_) | TokenType::Deref) { + if matches!( + token.token_type, + TokenType::Identifier(_) | TokenType::Deref + ) { let first = token.clone(); let mut look = tokens.clone(); @@ -212,9 +218,9 @@ pub fn parse_statement(tokens: &mut Peekable>) -> Option { TokenType::Continue => { tokens.next(); if let Some(Token { - token_type: TokenType::SemiColon, - .. - }) = tokens.peek() + token_type: TokenType::SemiColon, + .. + }) = tokens.peek() { tokens.next(); } @@ -223,9 +229,9 @@ pub fn parse_statement(tokens: &mut Peekable>) -> Option { TokenType::Break => { tokens.next(); if let Some(Token { - token_type: TokenType::SemiColon, - .. - }) = tokens.peek() + token_type: TokenType::SemiColon, + .. + }) = tokens.peek() { tokens.next(); } @@ -234,9 +240,9 @@ pub fn parse_statement(tokens: &mut Peekable>) -> Option { TokenType::Return => { tokens.next(); let expr = if let Some(Token { - token_type: TokenType::SemiColon, - .. - }) = tokens.peek() + token_type: TokenType::SemiColon, + .. + }) = tokens.peek() { tokens.next(); None @@ -245,9 +251,9 @@ pub fn parse_statement(tokens: &mut Peekable>) -> Option { } else { let value = parse_expression(tokens)?; if let Some(Token { - token_type: TokenType::SemiColon, - .. - }) = tokens.peek() + token_type: TokenType::SemiColon, + .. + }) = tokens.peek() { tokens.next(); } @@ -261,9 +267,9 @@ pub fn parse_statement(tokens: &mut Peekable>) -> Option { if is_expression_start(&token.token_type) { if let Some(expr) = parse_expression(tokens) { if let Some(Token { - token_type: TokenType::SemiColon, - .. - }) = tokens.peek() + token_type: TokenType::SemiColon, + .. + }) = tokens.peek() { tokens.next(); } diff --git a/llvm/src/backend.rs b/llvm/src/backend.rs index d9035198..51ace7c5 100644 --- a/llvm/src/backend.rs +++ b/llvm/src/backend.rs @@ -22,17 +22,8 @@ fn normalize_clang_opt_flag(opt_flag: &str) -> &str { } } -fn ensure_target_dir() { - let target_dir = Path::new("target"); - if !target_dir.exists() { - fs::create_dir_all(target_dir) - .expect("Unable to create target directory"); - } -} - pub fn compile_ir_to_object(ir: &str, file_stem: &str, opt_flag: &str) -> String { - ensure_target_dir(); - let object_path = format!("target/{}.o", file_stem); + let object_path = format!("{}.o", file_stem); let normalized_opt = normalize_clang_opt_flag(opt_flag); let mut cmd = Command::new("clang"); @@ -92,77 +83,3 @@ pub fn link_objects(objects: &[String], output: &str, libs: &[String], lib_paths eprintln!("link failed: {}", String::from_utf8_lossy(&output.stderr)); } } - -pub fn compile_ir_to_img_code(ir: &str, file_stem: &str) -> String { - ensure_target_dir(); - - let target_dir = Path::new("../../target"); - if !target_dir.exists() { - fs::create_dir_all(target_dir).expect("Unable to create target directory"); - } - - let ll_ir = "target/temp.ll"; - let o_file = "target/boot.o"; - let bin_file = "target/boot.bin"; - let img_file = format!("target/{}.img", file_stem); - - fs::write(ll_ir, ir).expect("Unable to write IR to file"); - - let o_output = Command::new("llc") - .arg("-march=x86") - .arg("-mattr=+16bit-mode") - .arg("-filetype=obj") - .arg(ll_ir) - .arg("-o") - .arg(o_file) - .status() - .expect("Failed to execute llc"); - - if !o_output.success() { - eprintln!("llc failed"); - return String::new(); - } - - let elf_output = Command::new("ld") - .arg("-m") - .arg("elf_i386") - .arg("-Ttext") - .arg("0x7c00") - .arg("--oformat") - .arg("binary") - .arg(o_file) - .arg("-o") - .arg(bin_file) - .status() - .expect("Failed to execute ld"); - - if !elf_output.success() { - eprintln!("ld failed"); - return String::new(); - } - - let mut bin_data = fs::read(bin_file).expect("Unable to read binary"); - if bin_data.len() < 512 { - bin_data.resize(512, 0); - } - bin_data[510] = 0x55; - bin_data[511] = 0xAA; - fs::write(&bin_file, &bin_data).expect("Unable to write binary"); - - let dd_status = Command::new("dd") - .arg("if=target/boot.bin") - .arg(&format!("of={}", img_file)) - .arg("bs=512") - .arg("count=1") - .arg("conv=notrunc") - .status() - .expect("Failed to execute dd"); - if !dd_status.success() { - eprintln!("dd failed"); - return String::new(); - } - - println!("[+] Image created: {}", img_file); - - img_file -} diff --git a/llvm/src/codegen/address.rs b/llvm/src/codegen/address.rs index 307b2ab9..7a563e44 100644 --- a/llvm/src/codegen/address.rs +++ b/llvm/src/codegen/address.rs @@ -24,7 +24,9 @@ use crate::codegen::wave_type_to_llvm_type; use super::types::VariableInfo; fn normalize_struct_name(raw: &str) -> &str { - raw.strip_prefix("struct.").unwrap_or(raw).trim_start_matches('%') + raw.strip_prefix("struct.") + .unwrap_or(raw) + .trim_start_matches('%') } fn cast_int_to_i64<'ctx>( @@ -91,7 +93,9 @@ fn pointee_ty_of_ptr_expr<'ctx>( struct_types: &HashMap>, ) -> BasicTypeEnum<'ctx> { match expr { - Expression::Grouped(inner) => pointee_ty_of_ptr_expr(context, inner, variables, struct_types), + Expression::Grouped(inner) => { + pointee_ty_of_ptr_expr(context, inner, variables, struct_types) + } Expression::Variable(name) => { let vi = variables @@ -103,7 +107,10 @@ fn pointee_ty_of_ptr_expr<'ctx>( wave_type_to_llvm_type(context, inner, struct_types, TypeFlavor::AbiC) } WaveType::String => context.i8_type().as_basic_type_enum(), - other => panic!("deref/index expects pointer type, got {:?} for {}", other, name), + other => panic!( + "deref/index expects pointer type, got {:?} for {}", + other, name + ), } } @@ -119,7 +126,9 @@ fn struct_ty_of_ptr_expr<'ctx>( struct_types: &HashMap>, ) -> StructType<'ctx> { match expr { - Expression::Grouped(inner) => struct_ty_of_ptr_expr(context, inner, variables, struct_types), + Expression::Grouped(inner) => { + struct_ty_of_ptr_expr(context, inner, variables, struct_types) + } Expression::Variable(name) => { let vi = variables @@ -131,9 +140,15 @@ fn struct_ty_of_ptr_expr<'ctx>( WaveType::Struct(sname) => *struct_types .get(sname) .unwrap_or_else(|| panic!("Struct type '{}' not found", sname)), - other => panic!("pointer does not point to struct: {:?} (var {})", other, name), + other => panic!( + "pointer does not point to struct: {:?} (var {})", + other, name + ), }, - other => panic!("expected pointer-to-struct var, got {:?} (var {})", other, name), + other => panic!( + "expected pointer-to-struct var, got {:?} (var {})", + other, name + ), } } @@ -239,7 +254,12 @@ fn addr_and_ty<'ctx>( .get(&sname) .unwrap_or_else(|| panic!("Struct '{}' missing in struct_field_indices", sname)) .get(field) - .unwrap_or_else(|| panic!("Field '{}.{}' missing in struct_field_indices", sname, field)); + .unwrap_or_else(|| { + panic!( + "Field '{}.{}' missing in struct_field_indices", + sname, field + ) + }); let field_ty = struct_ty .get_field_type_at_index(idx) @@ -346,8 +366,15 @@ fn int_expr_as_i64<'ctx>( | Expression::IndexAccess { .. } | Expression::Deref(_) | Expression::AddressOf(_) => { - let (addr, ty) = - addr_and_ty(context, builder, expr, variables, module, struct_types, struct_field_indices); + let (addr, ty) = addr_and_ty( + context, + builder, + expr, + variables, + module, + struct_types, + struct_field_indices, + ); let int_ty = match ty { BasicTypeEnum::IntType(it) => it, @@ -384,7 +411,7 @@ pub fn generate_address_ir<'ctx>( struct_types, struct_field_indices, ) - .0 + .0 } pub fn generate_address_and_type_ir<'ctx>( diff --git a/llvm/src/codegen/consts.rs b/llvm/src/codegen/consts.rs index 6cbc60b5..ed62ec66 100644 --- a/llvm/src/codegen/consts.rs +++ b/llvm/src/codegen/consts.rs @@ -35,8 +35,16 @@ impl fmt::Display for ConstEvalError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ConstEvalError::UnknownIdentifier(n) => write!(f, "unknown const identifier `{}`", n), - ConstEvalError::TypeMismatch { expected, got, note } => { - write!(f, "type mismatch (expected {}, got {}): {}", expected, got, note) + ConstEvalError::TypeMismatch { + expected, + got, + note, + } => { + write!( + f, + "type mismatch (expected {}, got {}): {}", + expected, got, note + ) } ConstEvalError::InvalidLiteral(s) => write!(f, "invalid literal: {}", s), ConstEvalError::Unsupported(s) => write!(f, "unsupported const expression: {}", s), @@ -66,7 +74,8 @@ fn parse_signed_and_radix(s: &str) -> (bool, StringRadix, String) { t = rest.to_string(); } - let (radix, digits) = if let Some(rest) = t.strip_prefix("0x").or_else(|| t.strip_prefix("0X")) { + let (radix, digits) = if let Some(rest) = t.strip_prefix("0x").or_else(|| t.strip_prefix("0X")) + { (StringRadix::Hexadecimal, rest) } else if let Some(rest) = t.strip_prefix("0b").or_else(|| t.strip_prefix("0B")) { (StringRadix::Binary, rest) @@ -84,9 +93,18 @@ fn is_zero_like(s: &str) -> bool { let s = s.strip_prefix('+').unwrap_or(&s); let s = s.strip_prefix('-').unwrap_or(s); - let s = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")).unwrap_or(s); - let s = s.strip_prefix("0b").or_else(|| s.strip_prefix("0B")).unwrap_or(s); - let s = s.strip_prefix("0o").or_else(|| s.strip_prefix("0O")).unwrap_or(s); + let s = s + .strip_prefix("0x") + .or_else(|| s.strip_prefix("0X")) + .unwrap_or(s); + let s = s + .strip_prefix("0b") + .or_else(|| s.strip_prefix("0B")) + .unwrap_or(s); + let s = s + .strip_prefix("0o") + .or_else(|| s.strip_prefix("0O")) + .unwrap_or(s); !s.is_empty() && s.chars().all(|c| c == '0') } @@ -130,7 +148,14 @@ fn const_from_expected<'ctx>( ) -> Result, ConstEvalError> { match expr { Expression::Grouped(inner) => { - return const_from_expected(context, expected, inner, struct_types, struct_field_indices, const_env); + return const_from_expected( + context, + expected, + inner, + struct_types, + struct_field_indices, + const_env, + ); } Expression::Variable(name) => { @@ -143,7 +168,10 @@ fn const_from_expected<'ctx>( return Err(ConstEvalError::TypeMismatch { expected: type_name(expected), got: value_type_name(v), - note: format!("identifier `{}` resolved to a const of different LLVM type", name), + note: format!( + "identifier `{}` resolved to a const of different LLVM type", + name + ), }); } @@ -159,8 +187,12 @@ fn const_from_expected<'ctx>( }), }, - Expression::Cast { expr: inner, target_type } => { - let cast_ty = wave_type_to_llvm_type(context, target_type, struct_types, TypeFlavor::AbiC); + Expression::Cast { + expr: inner, + target_type, + } => { + let cast_ty = + wave_type_to_llvm_type(context, target_type, struct_types, TypeFlavor::AbiC); if cast_ty != expected { return Err(ConstEvalError::TypeMismatch { expected: type_name(expected), @@ -265,7 +297,9 @@ fn const_from_expected<'ctx>( // --- floats --- Expression::Literal(Literal::Float(fv)) => match expected { - BasicTypeEnum::FloatType(float_ty) => Ok(float_ty.const_float(*fv).as_basic_value_enum()), + BasicTypeEnum::FloatType(float_ty) => { + Ok(float_ty.const_float(*fv).as_basic_value_enum()) + } _ => Err(ConstEvalError::TypeMismatch { expected: type_name(expected), got: "float".to_string(), @@ -304,36 +338,61 @@ fn const_from_expected<'ctx>( if fields.len() != field_count { return Err(ConstEvalError::Unsupported(format!( "StructLiteral '{}' positional init expects {} fields, got {}", - struct_name, field_count, fields.len() + struct_name, + field_count, + fields.len() ))); } for (i, (_, vexpr)) in fields.iter().enumerate() { - let fty = st - .get_field_type_at_index(i as u32) - .ok_or_else(|| ConstEvalError::Unsupported(format!( + let fty = st.get_field_type_at_index(i as u32).ok_or_else(|| { + ConstEvalError::Unsupported(format!( "Struct '{}' has no field index {}", struct_name, i - )))?; + )) + })?; - let cv = const_from_expected(context, fty, vexpr, struct_types, struct_field_indices, const_env)?; + let cv = const_from_expected( + context, + fty, + vexpr, + struct_types, + struct_field_indices, + const_env, + )?; slots[i] = Some(cv); } } else { let idx_map = struct_field_indices.get(struct_name).ok_or_else(|| { - ConstEvalError::Unsupported(format!("Struct '{}' field map not found", struct_name)) + ConstEvalError::Unsupported(format!( + "Struct '{}' field map not found", + struct_name + )) })?; for (fname, vexpr) in fields { let idx = *idx_map.get(fname).ok_or_else(|| { - ConstEvalError::Unsupported(format!("Field '{}' not found in struct '{}'", fname, struct_name)) + ConstEvalError::Unsupported(format!( + "Field '{}' not found in struct '{}'", + fname, struct_name + )) })? as usize; let fty = st.get_field_type_at_index(idx as u32).ok_or_else(|| { - ConstEvalError::Unsupported(format!("Struct '{}' has no field index {}", struct_name, idx)) + ConstEvalError::Unsupported(format!( + "Struct '{}' has no field index {}", + struct_name, idx + )) })?; - let cv = const_from_expected(context, fty, vexpr, struct_types, struct_field_indices, const_env)?; + let cv = const_from_expected( + context, + fty, + vexpr, + struct_types, + struct_field_indices, + const_env, + )?; slots[idx] = Some(cv); } } @@ -354,7 +413,8 @@ fn const_from_expected<'ctx>( if elems.len() != len { return Err(ConstEvalError::Unsupported(format!( "Array literal length mismatch: expected {}, got {}", - len, elems.len() + len, + elems.len() ))); } @@ -362,7 +422,16 @@ fn const_from_expected<'ctx>( let elem_vals: Vec> = elems .iter() - .map(|e| const_from_expected(context, elem_ty, e, struct_types, struct_field_indices, const_env)) + .map(|e| { + const_from_expected( + context, + elem_ty, + e, + struct_types, + struct_field_indices, + const_env, + ) + }) .collect::>()?; match elem_ty { @@ -488,5 +557,12 @@ pub(super) fn create_llvm_const_value<'ctx>( } let expected = wave_type_to_llvm_type(context, ty, struct_types, TypeFlavor::AbiC); - const_from_expected(context, expected, expr, struct_types, struct_field_indices, const_env) + const_from_expected( + context, + expected, + expr, + struct_types, + struct_field_indices, + const_env, + ) } diff --git a/llvm/src/codegen/format.rs b/llvm/src/codegen/format.rs index b7729b76..0634c8e2 100644 --- a/llvm/src/codegen/format.rs +++ b/llvm/src/codegen/format.rs @@ -120,9 +120,9 @@ pub fn wave_format_to_scanf(format: &str, arg_types: &[WaveType]) -> String { if let Some('}') = chars.peek() { chars.next(); // consume '}' - let ty = arg_types - .get(arg_index) - .unwrap_or_else(|| panic!("Missing argument for format at index {}", arg_index)); + let ty = arg_types.get(arg_index).unwrap_or_else(|| { + panic!("Missing argument for format at index {}", arg_index) + }); let fmt = match ty { WaveType::Bool => "%d", @@ -149,8 +149,8 @@ pub fn wave_format_to_scanf(format: &str, arg_types: &[WaveType]) -> String { }, WaveType::Float(bits) => match *bits { - 32 => "%f", // float* - 64 => "%lf", // double* + 32 => "%f", // float* + 64 => "%lf", // double* other => panic!("Unsupported float width in scanf: {}", other), }, diff --git a/llvm/src/codegen/ir.rs b/llvm/src/codegen/ir.rs index 01128ecc..9ccd9e89 100644 --- a/llvm/src/codegen/ir.rs +++ b/llvm/src/codegen/ir.rs @@ -192,7 +192,8 @@ pub unsafe fn generate_ir(ast_nodes: &[ASTNode], opt_flag: &str) -> String { continue; } - let llvm_ty = wave_type_to_llvm_type(context, &v.type_name, &struct_types, TypeFlavor::AbiC); + let llvm_ty = + wave_type_to_llvm_type(context, &v.type_name, &struct_types, TypeFlavor::AbiC); let g = module.add_global(llvm_ty, None, &v.name); let init = if let Some(expr) = &v.initial_value { diff --git a/llvm/src/codegen/types.rs b/llvm/src/codegen/types.rs index ccb7fb3d..0c78c02a 100644 --- a/llvm/src/codegen/types.rs +++ b/llvm/src/codegen/types.rs @@ -14,9 +14,9 @@ use inkwell::types::{BasicType, BasicTypeEnum}; use inkwell::values::PointerValue; use inkwell::AddressSpace; +use inkwell::targets::TargetData; use parser::ast::{Mutability, WaveType}; use std::collections::HashMap; -use inkwell::targets::TargetData; pub type StructFieldMap = HashMap>; @@ -34,11 +34,7 @@ pub fn build_field_map(fields: &[(String, parser::ast::WaveType)]) -> HashMap u32 { +pub fn get_field_index(struct_fields: &StructFieldMap, struct_name: &str, field: &str) -> u32 { *struct_fields .get(struct_name) .unwrap_or_else(|| panic!("Struct '{}' field map not found", struct_name)) @@ -53,9 +49,9 @@ pub fn wave_type_to_llvm_type<'ctx>( flavor: TypeFlavor, ) -> BasicTypeEnum<'ctx> { match wave_type { - WaveType::Int(bits) | WaveType::Uint(bits) => { - context.custom_width_int_type(*bits as u32).as_basic_type_enum() - } + WaveType::Int(bits) | WaveType::Uint(bits) => context + .custom_width_int_type(*bits as u32) + .as_basic_type_enum(), WaveType::Float(bits) => match bits { 32 => context.f32_type().as_basic_type_enum(), @@ -75,19 +71,19 @@ pub fn wave_type_to_llvm_type<'ctx>( WaveType::Void => context.i8_type().as_basic_type_enum(), - WaveType::Pointer(_inner) => { - context.ptr_type(AddressSpace::default()).as_basic_type_enum() - } + WaveType::Pointer(_inner) => context + .ptr_type(AddressSpace::default()) + .as_basic_type_enum(), WaveType::Array(inner, size) => { let inner_ty = wave_type_to_llvm_type(context, inner, struct_types, flavor); inner_ty.array_type(*size as u32).as_basic_type_enum() } - WaveType::String => { - context.ptr_type(AddressSpace::default()).as_basic_type_enum() - } - + WaveType::String => context + .ptr_type(AddressSpace::default()) + .as_basic_type_enum(), + WaveType::Struct(name) => struct_types .get(name) .unwrap_or_else(|| panic!("Struct type '{}' not found", name)) @@ -118,7 +114,12 @@ fn is_integer_only_aggregate<'ctx>(t: BasicTypeEnum<'ctx>) -> bool { BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_) => { let mut leaves = vec![]; flatten_leaves(t, &mut leaves); - leaves.iter().all(|lt| matches!(lt, BasicTypeEnum::IntType(_) | BasicTypeEnum::PointerType(_))) + leaves.iter().all(|lt| { + matches!( + lt, + BasicTypeEnum::IntType(_) | BasicTypeEnum::PointerType(_) + ) + }) } _ => false, } diff --git a/llvm/src/expression/lvalue.rs b/llvm/src/expression/lvalue.rs index cf1ef6f0..7c33e935 100644 --- a/llvm/src/expression/lvalue.rs +++ b/llvm/src/expression/lvalue.rs @@ -9,6 +9,7 @@ // // SPDX-License-Identifier: MPL-2.0 +use inkwell::targets::TargetData; use inkwell::{ builder::Builder, context::Context, @@ -17,14 +18,13 @@ use inkwell::{ values::{BasicValueEnum, PointerValue}, AddressSpace, }; -use std::collections::HashMap; -use inkwell::targets::TargetData; use parser::ast::{Expression, WaveType}; +use std::collections::HashMap; -use crate::expression::rvalue::generate_expression_ir; use crate::codegen::abi_c::ExternCInfo; -use crate::codegen::VariableInfo; use crate::codegen::types::{wave_type_to_llvm_type, TypeFlavor}; +use crate::codegen::VariableInfo; +use crate::expression::rvalue::generate_expression_ir; fn llvm_basic_to_wave_type<'ctx>(bt: BasicTypeEnum<'ctx>) -> WaveType { match bt { @@ -46,7 +46,10 @@ fn llvm_basic_to_wave_type<'ctx>(bt: BasicTypeEnum<'ctx>) -> WaveType { } BasicTypeEnum::ArrayType(at) => { let elem = at.get_element_type(); - WaveType::Array(Box::new(llvm_basic_to_wave_type(elem)), at.len() as usize as u32) + WaveType::Array( + Box::new(llvm_basic_to_wave_type(elem)), + at.len() as usize as u32, + ) } BasicTypeEnum::StructType(st) => { let raw = st @@ -56,9 +59,7 @@ fn llvm_basic_to_wave_type<'ctx>(bt: BasicTypeEnum<'ctx>) -> WaveType { let name = raw.strip_prefix("struct.").unwrap_or(raw).to_string(); WaveType::Struct(name) } - BasicTypeEnum::PointerType(_) => { - WaveType::Pointer(Box::new(WaveType::Void)) - } + BasicTypeEnum::PointerType(_) => WaveType::Pointer(Box::new(WaveType::Void)), BasicTypeEnum::VectorType(_) | BasicTypeEnum::ScalableVectorType(_) => { panic!("Vector types are not supported in WaveType mapping yet"); } @@ -296,12 +297,8 @@ fn generate_lvalue_ir_typed<'ctx>( WaveType::Pointer(inner) => { let base_ptr = load_ptr_value(context, builder, base_addr, "load_index_base"); - let elem_llvm = wave_type_to_llvm_type( - context, - &inner, - struct_types, - TypeFlavor::Value, - ); + let elem_llvm = + wave_type_to_llvm_type(context, &inner, struct_types, TypeFlavor::Value); let gep = unsafe { builder @@ -323,7 +320,10 @@ fn generate_lvalue_ir_typed<'ctx>( } other => { - panic!("IndexAccess target is not array/pointer/string: {:?}", other); + panic!( + "IndexAccess target is not array/pointer/string: {:?}", + other + ); } } } @@ -353,7 +353,10 @@ fn generate_lvalue_ir_typed<'ctx>( other => panic!("FieldAccess base pointer is not ptr: {:?}", other), }, - other => panic!("FieldAccess base is not a struct or ptr: {:?}", other), + other => panic!( + "FieldAccess base is not a struct or ptr: {:?}", + other + ), }; let struct_ty = struct_types diff --git a/llvm/src/expression/mod.rs b/llvm/src/expression/mod.rs index 8b1913e5..991e6b4d 100644 --- a/llvm/src/expression/mod.rs +++ b/llvm/src/expression/mod.rs @@ -10,4 +10,4 @@ // SPDX-License-Identifier: MPL-2.0 pub mod lvalue; -pub mod rvalue; \ No newline at end of file +pub mod rvalue; diff --git a/llvm/src/expression/rvalue/assign.rs b/llvm/src/expression/rvalue/assign.rs index 773f3cdc..20e2fc5e 100644 --- a/llvm/src/expression/rvalue/assign.rs +++ b/llvm/src/expression/rvalue/assign.rs @@ -9,16 +9,18 @@ // // SPDX-License-Identifier: MPL-2.0 +use super::ExprGenEnv; +use crate::codegen::types::TypeFlavor; use crate::codegen::{generate_address_ir, wave_type_to_llvm_type}; use crate::statement::variable::{coerce_basic_value, CoercionMode}; -use super::ExprGenEnv; use inkwell::types::{AnyTypeEnum, AsTypeRef, BasicTypeEnum}; use inkwell::values::{BasicValue, BasicValueEnum}; use parser::ast::{AssignOperator, Expression, WaveType}; -use crate::codegen::types::TypeFlavor; fn normalize_struct_name(raw: &str) -> &str { - raw.strip_prefix("struct.").unwrap_or(raw).trim_start_matches('%') + raw.strip_prefix("struct.") + .unwrap_or(raw) + .trim_start_matches('%') } fn resolve_struct_key<'ctx, 'a>( @@ -43,7 +45,10 @@ fn wave_to_basic<'ctx, 'a>(env: &ExprGenEnv<'ctx, 'a>, wt: &WaveType) -> BasicTy wave_type_to_llvm_type(env.context, wt, env.struct_types, TypeFlavor::Value) } -fn basic_to_wave<'ctx, 'a>(env: &ExprGenEnv<'ctx, 'a>, bt: BasicTypeEnum<'ctx>) -> Option { +fn basic_to_wave<'ctx, 'a>( + env: &ExprGenEnv<'ctx, 'a>, + bt: BasicTypeEnum<'ctx>, +) -> Option { match bt { BasicTypeEnum::IntType(it) => { let bw = it.get_bit_width() as u16; @@ -68,8 +73,9 @@ fn wave_type_of_lvalue<'ctx, 'a>(env: &ExprGenEnv<'ctx, 'a>, e: &Expression) -> match e { Expression::Variable(name) => env.variables.get(name).map(|vi| vi.ty.clone()), Expression::Grouped(inner) => wave_type_of_lvalue(env, inner), - Expression::AddressOf(inner) => wave_type_of_lvalue(env, inner) - .map(|t| WaveType::Pointer(Box::new(t))), + Expression::AddressOf(inner) => { + wave_type_of_lvalue(env, inner).map(|t| WaveType::Pointer(Box::new(t))) + } Expression::Deref(inner) => { let inner_ty = wave_type_of_lvalue(env, inner)?; match inner_ty { @@ -144,7 +150,9 @@ fn infer_lvalue_store_type<'ctx, 'a>( target: &Expression, ) -> BasicTypeEnum<'ctx> { match target { - Expression::Grouped(inner) | Expression::AddressOf(inner) => infer_lvalue_store_type(env, inner), + Expression::Grouped(inner) | Expression::AddressOf(inner) => { + infer_lvalue_store_type(env, inner) + } Expression::Variable(_) | Expression::Deref(_) | Expression::IndexAccess { .. } => { let wt = wave_type_of_lvalue(env, target) @@ -163,35 +171,50 @@ fn infer_lvalue_store_type<'ctx, 'a>( }); if let Some(struct_name) = struct_name_opt { - let st = *env.struct_types.get(&struct_name) + let st = *env + .struct_types + .get(&struct_name) .unwrap_or_else(|| panic!("Struct type '{}' not found", struct_name)); - let field_index = env.struct_field_indices + let field_index = env + .struct_field_indices .get(&struct_name) .and_then(|m| m.get(field)) .copied() .unwrap_or_else(|| panic!("Unknown field '{}.{}'", struct_name, field)); - return st.get_field_type_at_index(field_index) - .unwrap_or_else(|| panic!("Invalid field index {} for struct {}", field_index, struct_name)); + return st.get_field_type_at_index(field_index).unwrap_or_else(|| { + panic!( + "Invalid field index {} for struct {}", + field_index, struct_name + ) + }); } let obj_ty = infer_lvalue_store_type(env, object); let st = match obj_ty { BasicTypeEnum::StructType(st) => st, - other => panic!("FieldAccess base is not a struct value: {:?} (expr: {:?})", other, object), + other => panic!( + "FieldAccess base is not a struct value: {:?} (expr: {:?})", + other, object + ), }; let struct_key = resolve_struct_key(env, st); - let field_index = env.struct_field_indices + let field_index = env + .struct_field_indices .get(&struct_key) .and_then(|m| m.get(field)) .copied() .unwrap_or_else(|| panic!("Unknown field '{}.{}'", struct_key, field)); - st.get_field_type_at_index(field_index) - .unwrap_or_else(|| panic!("Invalid field index {} for struct {}", field_index, struct_key)) + st.get_field_type_at_index(field_index).unwrap_or_else(|| { + panic!( + "Invalid field index {} for struct {}", + field_index, struct_key + ) + }) } _ => panic!("Expression is not an assignable lvalue: {:?}", target), @@ -205,12 +228,16 @@ fn materialize_for_store<'ctx, 'a>( tag: &str, ) -> BasicValueEnum<'ctx> { match (rhs, element_type) { - (BasicValueEnum::PointerValue(pv), BasicTypeEnum::ArrayType(at)) => { - env.builder.build_load(at, pv, tag).unwrap().as_basic_value_enum() - } - (BasicValueEnum::PointerValue(pv), BasicTypeEnum::StructType(st)) => { - env.builder.build_load(st, pv, tag).unwrap().as_basic_value_enum() - } + (BasicValueEnum::PointerValue(pv), BasicTypeEnum::ArrayType(at)) => env + .builder + .build_load(at, pv, tag) + .unwrap() + .as_basic_value_enum(), + (BasicValueEnum::PointerValue(pv), BasicTypeEnum::StructType(st)) => env + .builder + .build_load(st, pv, tag) + .unwrap() + .as_basic_value_enum(), (v, _) => v, } } @@ -222,7 +249,13 @@ pub(crate) fn gen_assign_operation<'ctx, 'a>( value: &Expression, ) -> BasicValueEnum<'ctx> { let ptr = generate_address_ir( - env.context, env.builder, target, env.variables, env.module, env.struct_types, env.struct_field_indices + env.context, + env.builder, + target, + env.variables, + env.module, + env.struct_types, + env.struct_field_indices, ); let element_type = infer_lvalue_store_type(env, target); @@ -263,34 +296,80 @@ pub(crate) fn gen_assign_operation<'ctx, 'a>( .builder .build_signed_int_to_float(rhs, lhs.get_type(), "int_to_float") .unwrap(); - (BasicValueEnum::FloatValue(lhs), BasicValueEnum::FloatValue(rhs_casted)) + ( + BasicValueEnum::FloatValue(lhs), + BasicValueEnum::FloatValue(rhs_casted), + ) } (BasicValueEnum::IntValue(lhs), BasicValueEnum::FloatValue(rhs)) => { let lhs_casted = env .builder .build_signed_int_to_float(lhs, rhs.get_type(), "int_to_float") .unwrap(); - (BasicValueEnum::FloatValue(lhs_casted), BasicValueEnum::FloatValue(rhs)) + ( + BasicValueEnum::FloatValue(lhs_casted), + BasicValueEnum::FloatValue(rhs), + ) } other => other, }; let result = match (current_val, new_val) { (BasicValueEnum::IntValue(lhs), BasicValueEnum::IntValue(rhs)) => match operator { - AssignOperator::AddAssign => env.builder.build_int_add(lhs, rhs, "add_assign").unwrap().as_basic_value_enum(), - AssignOperator::SubAssign => env.builder.build_int_sub(lhs, rhs, "sub_assign").unwrap().as_basic_value_enum(), - AssignOperator::MulAssign => env.builder.build_int_mul(lhs, rhs, "mul_assign").unwrap().as_basic_value_enum(), - AssignOperator::DivAssign => env.builder.build_int_signed_div(lhs, rhs, "div_assign").unwrap().as_basic_value_enum(), - AssignOperator::RemAssign => env.builder.build_int_signed_rem(lhs, rhs, "rem_assign").unwrap().as_basic_value_enum(), + AssignOperator::AddAssign => env + .builder + .build_int_add(lhs, rhs, "add_assign") + .unwrap() + .as_basic_value_enum(), + AssignOperator::SubAssign => env + .builder + .build_int_sub(lhs, rhs, "sub_assign") + .unwrap() + .as_basic_value_enum(), + AssignOperator::MulAssign => env + .builder + .build_int_mul(lhs, rhs, "mul_assign") + .unwrap() + .as_basic_value_enum(), + AssignOperator::DivAssign => env + .builder + .build_int_signed_div(lhs, rhs, "div_assign") + .unwrap() + .as_basic_value_enum(), + AssignOperator::RemAssign => env + .builder + .build_int_signed_rem(lhs, rhs, "rem_assign") + .unwrap() + .as_basic_value_enum(), AssignOperator::Assign => unreachable!(), }, (BasicValueEnum::FloatValue(lhs), BasicValueEnum::FloatValue(rhs)) => match operator { - AssignOperator::AddAssign => env.builder.build_float_add(lhs, rhs, "add_assign").unwrap().as_basic_value_enum(), - AssignOperator::SubAssign => env.builder.build_float_sub(lhs, rhs, "sub_assign").unwrap().as_basic_value_enum(), - AssignOperator::MulAssign => env.builder.build_float_mul(lhs, rhs, "mul_assign").unwrap().as_basic_value_enum(), - AssignOperator::DivAssign => env.builder.build_float_div(lhs, rhs, "div_assign").unwrap().as_basic_value_enum(), - AssignOperator::RemAssign => env.builder.build_float_rem(lhs, rhs, "rem_assign").unwrap().as_basic_value_enum(), + AssignOperator::AddAssign => env + .builder + .build_float_add(lhs, rhs, "add_assign") + .unwrap() + .as_basic_value_enum(), + AssignOperator::SubAssign => env + .builder + .build_float_sub(lhs, rhs, "sub_assign") + .unwrap() + .as_basic_value_enum(), + AssignOperator::MulAssign => env + .builder + .build_float_mul(lhs, rhs, "mul_assign") + .unwrap() + .as_basic_value_enum(), + AssignOperator::DivAssign => env + .builder + .build_float_div(lhs, rhs, "div_assign") + .unwrap() + .as_basic_value_enum(), + AssignOperator::RemAssign => env + .builder + .build_float_rem(lhs, rhs, "rem_assign") + .unwrap() + .as_basic_value_enum(), AssignOperator::Assign => unreachable!(), }, @@ -321,7 +400,13 @@ pub(crate) fn gen_assignment<'ctx, 'a>( value: &Expression, ) -> BasicValueEnum<'ctx> { let ptr = generate_address_ir( - env.context, env.builder, target, env.variables, env.module, env.struct_types, env.struct_field_indices + env.context, + env.builder, + target, + env.variables, + env.module, + env.struct_types, + env.struct_field_indices, ); let element_type = infer_lvalue_store_type(env, target); diff --git a/llvm/src/expression/rvalue/binary.rs b/llvm/src/expression/rvalue/binary.rs index 33331f09..cb57d912 100644 --- a/llvm/src/expression/rvalue/binary.rs +++ b/llvm/src/expression/rvalue/binary.rs @@ -63,9 +63,12 @@ fn infer_ptr_pointee_ty<'ctx, 'a>( Expression::Variable(name) => { if let Some(vi) = env.variables.get(name) { match &vi.ty { - WaveType::Pointer(inner) => { - wave_type_to_llvm_type(env.context, inner, env.struct_types, TypeFlavor::AbiC) - } + WaveType::Pointer(inner) => wave_type_to_llvm_type( + env.context, + inner, + env.struct_types, + TypeFlavor::AbiC, + ), WaveType::String => env.context.i8_type().as_basic_type_enum(), _ => env.context.i8_type().as_basic_type_enum(), } @@ -170,10 +173,12 @@ pub(crate) fn gen<'ctx, 'a>( _ => { if l_type != r_type { if l_type.get_bit_width() < r_type.get_bit_width() { - let new_l = env.builder.build_int_z_extend(l, r_type, "zext_l").unwrap(); + let new_l = + env.builder.build_int_z_extend(l, r_type, "zext_l").unwrap(); (new_l, r) } else { - let new_r = env.builder.build_int_z_extend(r, l_type, "zext_r").unwrap(); + let new_r = + env.builder.build_int_z_extend(r, l_type, "zext_r").unwrap(); (l, new_r) } } else { @@ -186,20 +191,44 @@ pub(crate) fn gen<'ctx, 'a>( Operator::Add => env.builder.build_int_add(l_casted, r_casted, "addtmp"), Operator::Subtract => env.builder.build_int_sub(l_casted, r_casted, "subtmp"), Operator::Multiply => env.builder.build_int_mul(l_casted, r_casted, "multmp"), - Operator::Divide => env.builder.build_int_signed_div(l_casted, r_casted, "divtmp"), - Operator::Remainder => env.builder.build_int_signed_rem(l_casted, r_casted, "modtmp"), + Operator::Divide => env + .builder + .build_int_signed_div(l_casted, r_casted, "divtmp"), + Operator::Remainder => env + .builder + .build_int_signed_rem(l_casted, r_casted, "modtmp"), Operator::ShiftLeft => env.builder.build_left_shift(l_casted, r_casted, "shl"), - Operator::ShiftRight => env.builder.build_right_shift(l_casted, r_casted, true, "shr"), + Operator::ShiftRight => env + .builder + .build_right_shift(l_casted, r_casted, true, "shr"), Operator::BitwiseAnd => env.builder.build_and(l_casted, r_casted, "andtmp"), Operator::BitwiseOr => env.builder.build_or(l_casted, r_casted, "ortmp"), Operator::BitwiseXor => env.builder.build_xor(l_casted, r_casted, "xortmp"), - Operator::Greater => env.builder.build_int_compare(IntPredicate::SGT, l_casted, r_casted, "cmptmp"), - Operator::Less => env.builder.build_int_compare(IntPredicate::SLT, l_casted, r_casted, "cmptmp"), - Operator::Equal => env.builder.build_int_compare(IntPredicate::EQ, l_casted, r_casted, "cmptmp"), - Operator::NotEqual => env.builder.build_int_compare(IntPredicate::NE, l_casted, r_casted, "cmptmp"), - Operator::GreaterEqual => env.builder.build_int_compare(IntPredicate::SGE, l_casted, r_casted, "cmptmp"), - Operator::LessEqual => env.builder.build_int_compare(IntPredicate::SLE, l_casted, r_casted, "cmptmp"), + Operator::Greater => { + env.builder + .build_int_compare(IntPredicate::SGT, l_casted, r_casted, "cmptmp") + } + Operator::Less => { + env.builder + .build_int_compare(IntPredicate::SLT, l_casted, r_casted, "cmptmp") + } + Operator::Equal => { + env.builder + .build_int_compare(IntPredicate::EQ, l_casted, r_casted, "cmptmp") + } + Operator::NotEqual => { + env.builder + .build_int_compare(IntPredicate::NE, l_casted, r_casted, "cmptmp") + } + Operator::GreaterEqual => { + env.builder + .build_int_compare(IntPredicate::SGE, l_casted, r_casted, "cmptmp") + } + Operator::LessEqual => { + env.builder + .build_int_compare(IntPredicate::SLE, l_casted, r_casted, "cmptmp") + } Operator::LogicalAnd => { let lb = to_bool(env.builder, l_casted); @@ -214,12 +243,15 @@ pub(crate) fn gen<'ctx, 'a>( _ => panic!("Unsupported binary operator"), } - .unwrap(); + .unwrap(); if let Some(inkwell::types::BasicTypeEnum::IntType(target_ty)) = expected_type { let result_ty = result.get_type(); if result_ty != target_ty { - result = env.builder.build_int_cast(result, target_ty, "cast_result").unwrap(); + result = env + .builder + .build_int_cast(result, target_ty, "cast_result") + .unwrap(); } } @@ -228,32 +260,90 @@ pub(crate) fn gen<'ctx, 'a>( (BasicValueEnum::FloatValue(l), BasicValueEnum::FloatValue(r)) => { let mut result: BasicValueEnum<'ctx> = match operator { - Operator::Add => env.builder.build_float_add(l, r, "faddtmp").unwrap().as_basic_value_enum(), - Operator::Subtract => env.builder.build_float_sub(l, r, "fsubtmp").unwrap().as_basic_value_enum(), - Operator::Multiply => env.builder.build_float_mul(l, r, "fmultmp").unwrap().as_basic_value_enum(), - Operator::Divide => env.builder.build_float_div(l, r, "fdivtmp").unwrap().as_basic_value_enum(), - Operator::Remainder => env.builder.build_float_rem(l, r, "fmodtmp").unwrap().as_basic_value_enum(), - - Operator::Greater => env.builder.build_float_compare(FloatPredicate::OGT, l, r, "fcmpgt").unwrap().as_basic_value_enum(), - Operator::Less => env.builder.build_float_compare(FloatPredicate::OLT, l, r, "fcmplt").unwrap().as_basic_value_enum(), - Operator::Equal => env.builder.build_float_compare(FloatPredicate::OEQ, l, r, "fcmpeq").unwrap().as_basic_value_enum(), - Operator::NotEqual => env.builder.build_float_compare(FloatPredicate::ONE, l, r, "fcmpne").unwrap().as_basic_value_enum(), - Operator::GreaterEqual => env.builder.build_float_compare(FloatPredicate::OGE, l, r, "fcmpge").unwrap().as_basic_value_enum(), - Operator::LessEqual => env.builder.build_float_compare(FloatPredicate::OLE, l, r, "fcmple").unwrap().as_basic_value_enum(), + Operator::Add => env + .builder + .build_float_add(l, r, "faddtmp") + .unwrap() + .as_basic_value_enum(), + Operator::Subtract => env + .builder + .build_float_sub(l, r, "fsubtmp") + .unwrap() + .as_basic_value_enum(), + Operator::Multiply => env + .builder + .build_float_mul(l, r, "fmultmp") + .unwrap() + .as_basic_value_enum(), + Operator::Divide => env + .builder + .build_float_div(l, r, "fdivtmp") + .unwrap() + .as_basic_value_enum(), + Operator::Remainder => env + .builder + .build_float_rem(l, r, "fmodtmp") + .unwrap() + .as_basic_value_enum(), + + Operator::Greater => env + .builder + .build_float_compare(FloatPredicate::OGT, l, r, "fcmpgt") + .unwrap() + .as_basic_value_enum(), + Operator::Less => env + .builder + .build_float_compare(FloatPredicate::OLT, l, r, "fcmplt") + .unwrap() + .as_basic_value_enum(), + Operator::Equal => env + .builder + .build_float_compare(FloatPredicate::OEQ, l, r, "fcmpeq") + .unwrap() + .as_basic_value_enum(), + Operator::NotEqual => env + .builder + .build_float_compare(FloatPredicate::ONE, l, r, "fcmpne") + .unwrap() + .as_basic_value_enum(), + Operator::GreaterEqual => env + .builder + .build_float_compare(FloatPredicate::OGE, l, r, "fcmpge") + .unwrap() + .as_basic_value_enum(), + Operator::LessEqual => env + .builder + .build_float_compare(FloatPredicate::OLE, l, r, "fcmple") + .unwrap() + .as_basic_value_enum(), _ => panic!("Unsupported float operator"), }; if let Some(exp) = expected_type { match (result, exp) { - (BasicValueEnum::FloatValue(fv), inkwell::types::BasicTypeEnum::FloatType(target_ty)) => { + ( + BasicValueEnum::FloatValue(fv), + inkwell::types::BasicTypeEnum::FloatType(target_ty), + ) => { if fv.get_type() != target_ty { - result = env.builder.build_float_cast(fv, target_ty, "fcast_result").unwrap().as_basic_value_enum(); + result = env + .builder + .build_float_cast(fv, target_ty, "fcast_result") + .unwrap() + .as_basic_value_enum(); } } - (BasicValueEnum::IntValue(iv), inkwell::types::BasicTypeEnum::IntType(target_ty)) => { + ( + BasicValueEnum::IntValue(iv), + inkwell::types::BasicTypeEnum::IntType(target_ty), + ) => { if iv.get_type() != target_ty { - result = env.builder.build_int_cast(iv, target_ty, "icast_result").unwrap().as_basic_value_enum(); + result = env + .builder + .build_int_cast(iv, target_ty, "icast_result") + .unwrap() + .as_basic_value_enum(); } } _ => {} @@ -270,18 +360,62 @@ pub(crate) fn gen<'ctx, 'a>( .unwrap(); match operator { - Operator::Add => env.builder.build_float_add(casted, float_val, "addtmp").unwrap().as_basic_value_enum(), - Operator::Subtract => env.builder.build_float_sub(casted, float_val, "subtmp").unwrap().as_basic_value_enum(), - Operator::Multiply => env.builder.build_float_mul(casted, float_val, "multmp").unwrap().as_basic_value_enum(), - Operator::Divide => env.builder.build_float_div(casted, float_val, "divtmp").unwrap().as_basic_value_enum(), - Operator::Remainder => env.builder.build_float_rem(casted, float_val, "modtmp").unwrap().as_basic_value_enum(), - - Operator::Greater => env.builder.build_float_compare(FloatPredicate::OGT, casted, float_val, "fcmpgt").unwrap().as_basic_value_enum(), - Operator::Less => env.builder.build_float_compare(FloatPredicate::OLT, casted, float_val, "fcmplt").unwrap().as_basic_value_enum(), - Operator::Equal => env.builder.build_float_compare(FloatPredicate::OEQ, casted, float_val, "fcmpeq").unwrap().as_basic_value_enum(), - Operator::NotEqual => env.builder.build_float_compare(FloatPredicate::ONE, casted, float_val, "fcmpne").unwrap().as_basic_value_enum(), - Operator::GreaterEqual => env.builder.build_float_compare(FloatPredicate::OGE, casted, float_val, "fcmpge").unwrap().as_basic_value_enum(), - Operator::LessEqual => env.builder.build_float_compare(FloatPredicate::OLE, casted, float_val, "fcmple").unwrap().as_basic_value_enum(), + Operator::Add => env + .builder + .build_float_add(casted, float_val, "addtmp") + .unwrap() + .as_basic_value_enum(), + Operator::Subtract => env + .builder + .build_float_sub(casted, float_val, "subtmp") + .unwrap() + .as_basic_value_enum(), + Operator::Multiply => env + .builder + .build_float_mul(casted, float_val, "multmp") + .unwrap() + .as_basic_value_enum(), + Operator::Divide => env + .builder + .build_float_div(casted, float_val, "divtmp") + .unwrap() + .as_basic_value_enum(), + Operator::Remainder => env + .builder + .build_float_rem(casted, float_val, "modtmp") + .unwrap() + .as_basic_value_enum(), + + Operator::Greater => env + .builder + .build_float_compare(FloatPredicate::OGT, casted, float_val, "fcmpgt") + .unwrap() + .as_basic_value_enum(), + Operator::Less => env + .builder + .build_float_compare(FloatPredicate::OLT, casted, float_val, "fcmplt") + .unwrap() + .as_basic_value_enum(), + Operator::Equal => env + .builder + .build_float_compare(FloatPredicate::OEQ, casted, float_val, "fcmpeq") + .unwrap() + .as_basic_value_enum(), + Operator::NotEqual => env + .builder + .build_float_compare(FloatPredicate::ONE, casted, float_val, "fcmpne") + .unwrap() + .as_basic_value_enum(), + Operator::GreaterEqual => env + .builder + .build_float_compare(FloatPredicate::OGE, casted, float_val, "fcmpge") + .unwrap() + .as_basic_value_enum(), + Operator::LessEqual => env + .builder + .build_float_compare(FloatPredicate::OLE, casted, float_val, "fcmple") + .unwrap() + .as_basic_value_enum(), _ => panic!("Unsupported mixed-type operator (int + float)"), } @@ -294,30 +428,86 @@ pub(crate) fn gen<'ctx, 'a>( .unwrap(); match operator { - Operator::Add => env.builder.build_float_add(float_val, casted, "addtmp").unwrap().as_basic_value_enum(), - Operator::Subtract => env.builder.build_float_sub(float_val, casted, "subtmp").unwrap().as_basic_value_enum(), - Operator::Multiply => env.builder.build_float_mul(float_val, casted, "multmp").unwrap().as_basic_value_enum(), - Operator::Divide => env.builder.build_float_div(float_val, casted, "divtmp").unwrap().as_basic_value_enum(), - Operator::Remainder => env.builder.build_float_rem(float_val, casted, "modtmp").unwrap().as_basic_value_enum(), - - Operator::Greater => env.builder.build_float_compare(FloatPredicate::OGT, float_val, casted, "fcmpgt").unwrap().as_basic_value_enum(), - Operator::Less => env.builder.build_float_compare(FloatPredicate::OLT, float_val, casted, "fcmplt").unwrap().as_basic_value_enum(), - Operator::Equal => env.builder.build_float_compare(FloatPredicate::OEQ, float_val, casted, "fcmpeq").unwrap().as_basic_value_enum(), - Operator::NotEqual => env.builder.build_float_compare(FloatPredicate::ONE, float_val, casted, "fcmpne").unwrap().as_basic_value_enum(), - Operator::GreaterEqual => env.builder.build_float_compare(FloatPredicate::OGE, float_val, casted, "fcmpge").unwrap().as_basic_value_enum(), - Operator::LessEqual => env.builder.build_float_compare(FloatPredicate::OLE, float_val, casted, "fcmple").unwrap().as_basic_value_enum(), + Operator::Add => env + .builder + .build_float_add(float_val, casted, "addtmp") + .unwrap() + .as_basic_value_enum(), + Operator::Subtract => env + .builder + .build_float_sub(float_val, casted, "subtmp") + .unwrap() + .as_basic_value_enum(), + Operator::Multiply => env + .builder + .build_float_mul(float_val, casted, "multmp") + .unwrap() + .as_basic_value_enum(), + Operator::Divide => env + .builder + .build_float_div(float_val, casted, "divtmp") + .unwrap() + .as_basic_value_enum(), + Operator::Remainder => env + .builder + .build_float_rem(float_val, casted, "modtmp") + .unwrap() + .as_basic_value_enum(), + + Operator::Greater => env + .builder + .build_float_compare(FloatPredicate::OGT, float_val, casted, "fcmpgt") + .unwrap() + .as_basic_value_enum(), + Operator::Less => env + .builder + .build_float_compare(FloatPredicate::OLT, float_val, casted, "fcmplt") + .unwrap() + .as_basic_value_enum(), + Operator::Equal => env + .builder + .build_float_compare(FloatPredicate::OEQ, float_val, casted, "fcmpeq") + .unwrap() + .as_basic_value_enum(), + Operator::NotEqual => env + .builder + .build_float_compare(FloatPredicate::ONE, float_val, casted, "fcmpne") + .unwrap() + .as_basic_value_enum(), + Operator::GreaterEqual => env + .builder + .build_float_compare(FloatPredicate::OGE, float_val, casted, "fcmpge") + .unwrap() + .as_basic_value_enum(), + Operator::LessEqual => env + .builder + .build_float_compare(FloatPredicate::OLE, float_val, casted, "fcmple") + .unwrap() + .as_basic_value_enum(), _ => panic!("Unsupported mixed-type operator (float + int)"), } } (BasicValueEnum::PointerValue(lp), BasicValueEnum::PointerValue(rp)) => { let i64_ty = env.context.i64_type(); - let li = env.builder.build_ptr_to_int(lp, i64_ty, "l_ptr2int").unwrap(); - let ri = env.builder.build_ptr_to_int(rp, i64_ty, "r_ptr2int").unwrap(); + let li = env + .builder + .build_ptr_to_int(lp, i64_ty, "l_ptr2int") + .unwrap(); + let ri = env + .builder + .build_ptr_to_int(rp, i64_ty, "r_ptr2int") + .unwrap(); let mut result = match operator { - Operator::Equal => env.builder.build_int_compare(IntPredicate::EQ, li, ri, "ptreq").unwrap(), - Operator::NotEqual => env.builder.build_int_compare(IntPredicate::NE, li, ri, "ptrne").unwrap(), + Operator::Equal => env + .builder + .build_int_compare(IntPredicate::EQ, li, ri, "ptreq") + .unwrap(), + Operator::NotEqual => env + .builder + .build_int_compare(IntPredicate::NE, li, ri, "ptrne") + .unwrap(), Operator::Subtract => env.builder.build_int_sub(li, ri, "ptrdiff").unwrap(), _ => panic!("Unsupported pointer operator: {:?}", operator), }; @@ -333,14 +523,20 @@ pub(crate) fn gen<'ctx, 'a>( target_ty.get_bit_width() ); } - result = env.builder.build_int_cast(result, target_ty, "cast_result").unwrap(); + result = env + .builder + .build_int_cast(result, target_ty, "cast_result") + .unwrap(); } } } Operator::Subtract => { if let Some(inkwell::types::BasicTypeEnum::IntType(target_ty)) = expected_type { if result.get_type() != target_ty { - result = env.builder.build_int_cast(result, target_ty, "cast_result").unwrap(); + result = env + .builder + .build_int_cast(result, target_ty, "cast_result") + .unwrap(); } } } @@ -364,13 +560,22 @@ pub(crate) fn gen<'ctx, 'a>( }; let i64_ty = env.context.i64_type(); - let li = env.builder.build_ptr_to_int(lp, i64_ty, "l_ptr2int").unwrap(); + let li = env + .builder + .build_ptr_to_int(lp, i64_ty, "l_ptr2int") + .unwrap(); let ri = cast_int_to_i64(env, ri, "r_i64"); let mut result = match operator { - Operator::Equal => env.builder.build_int_compare(IntPredicate::EQ, li, ri, "ptreq0").unwrap(), - Operator::NotEqual => env.builder.build_int_compare(IntPredicate::NE, li, ri, "ptrne0").unwrap(), + Operator::Equal => env + .builder + .build_int_compare(IntPredicate::EQ, li, ri, "ptreq0") + .unwrap(), + Operator::NotEqual => env + .builder + .build_int_compare(IntPredicate::NE, li, ri, "ptrne0") + .unwrap(), _ => panic!("Unsupported ptr/int operator: {:?}", operator), }; @@ -383,7 +588,10 @@ pub(crate) fn gen<'ctx, 'a>( target_ty.get_bit_width() ); } - result = env.builder.build_int_cast(result, target_ty, "cast_result").unwrap(); + result = env + .builder + .build_int_cast(result, target_ty, "cast_result") + .unwrap(); } } @@ -400,11 +608,20 @@ pub(crate) fn gen<'ctx, 'a>( let i64_ty = env.context.i64_type(); let li = cast_int_to_i64(env, li, "l_i64"); - let ri = env.builder.build_ptr_to_int(rp, i64_ty, "r_ptr2int").unwrap(); + let ri = env + .builder + .build_ptr_to_int(rp, i64_ty, "r_ptr2int") + .unwrap(); let mut result = match operator { - Operator::Equal => env.builder.build_int_compare(IntPredicate::EQ, li, ri, "ptreq0").unwrap(), - Operator::NotEqual => env.builder.build_int_compare(IntPredicate::NE, li, ri, "ptrne0").unwrap(), + Operator::Equal => env + .builder + .build_int_compare(IntPredicate::EQ, li, ri, "ptreq0") + .unwrap(), + Operator::NotEqual => env + .builder + .build_int_compare(IntPredicate::NE, li, ri, "ptrne0") + .unwrap(), _ => panic!("Unsupported int/ptr operator: {:?}", operator), }; @@ -417,7 +634,10 @@ pub(crate) fn gen<'ctx, 'a>( target_ty.get_bit_width() ); } - result = env.builder.build_int_cast(result, target_ty, "cast_result").unwrap(); + result = env + .builder + .build_int_cast(result, target_ty, "cast_result") + .unwrap(); } } diff --git a/llvm/src/expression/rvalue/incdec.rs b/llvm/src/expression/rvalue/incdec.rs index d8d5c397..b2613dd1 100644 --- a/llvm/src/expression/rvalue/incdec.rs +++ b/llvm/src/expression/rvalue/incdec.rs @@ -17,7 +17,9 @@ use inkwell::values::{BasicValue, BasicValueEnum}; use parser::ast::{Expression, IncDecKind, WaveType}; fn normalize_struct_name(raw: &str) -> &str { - raw.strip_prefix("struct.").unwrap_or(raw).trim_start_matches('%') + raw.strip_prefix("struct.") + .unwrap_or(raw) + .trim_start_matches('%') } fn resolve_struct_key<'ctx, 'a>( @@ -70,9 +72,14 @@ fn wave_type_of_lvalue<'ctx, 'a>(env: &ExprGenEnv<'ctx, 'a>, e: &Expression) -> } } -fn infer_lvalue_value_type<'ctx, 'a>(env: &ExprGenEnv<'ctx, 'a>, target: &Expression) -> BasicTypeEnum<'ctx> { +fn infer_lvalue_value_type<'ctx, 'a>( + env: &ExprGenEnv<'ctx, 'a>, + target: &Expression, +) -> BasicTypeEnum<'ctx> { match target { - Expression::Grouped(inner) | Expression::AddressOf(inner) => infer_lvalue_value_type(env, inner), + Expression::Grouped(inner) | Expression::AddressOf(inner) => { + infer_lvalue_value_type(env, inner) + } Expression::Variable(_) | Expression::Deref(_) | Expression::IndexAccess { .. } => { let wt = wave_type_of_lvalue(env, target) @@ -91,52 +98,67 @@ fn infer_lvalue_value_type<'ctx, 'a>(env: &ExprGenEnv<'ctx, 'a>, target: &Expres }); if let Some(struct_name) = struct_name_opt { - let st = *env.struct_types + let st = *env + .struct_types .get(&struct_name) .unwrap_or_else(|| panic!("Struct type '{}' not found", struct_name)); - let field_index = env.struct_field_indices + let field_index = env + .struct_field_indices .get(&struct_name) .and_then(|m| m.get(field)) .copied() .unwrap_or_else(|| panic!("Unknown field '{}.{}'", struct_name, field)); - return st.get_field_type_at_index(field_index) - .unwrap_or_else(|| panic!("Invalid field index {} for struct {}", field_index, struct_name)); + return st.get_field_type_at_index(field_index).unwrap_or_else(|| { + panic!( + "Invalid field index {} for struct {}", + field_index, struct_name + ) + }); } let obj_ty = infer_lvalue_value_type(env, object); let st = match obj_ty { BasicTypeEnum::StructType(st) => st, - other => panic!("FieldAccess base is not a struct value: {:?} (expr: {:?})", other, object), + other => panic!( + "FieldAccess base is not a struct value: {:?} (expr: {:?})", + other, object + ), }; let struct_key = resolve_struct_key(env, st); - let field_index = env.struct_field_indices + let field_index = env + .struct_field_indices .get(&struct_key) .and_then(|m| m.get(field)) .copied() .unwrap_or_else(|| panic!("Unknown field '{}.{}'", struct_key, field)); - st.get_field_type_at_index(field_index) - .unwrap_or_else(|| panic!("Invalid field index {} for struct {}", field_index, struct_key)) + st.get_field_type_at_index(field_index).unwrap_or_else(|| { + panic!( + "Invalid field index {} for struct {}", + field_index, struct_key + ) + }) } _ => panic!("Expression is not an assignable lvalue: {:?}", target), } } -fn infer_ptr_pointee_type<'ctx, 'a>(env: &ExprGenEnv<'ctx, 'a>, target: &Expression) -> BasicTypeEnum<'ctx> { +fn infer_ptr_pointee_type<'ctx, 'a>( + env: &ExprGenEnv<'ctx, 'a>, + target: &Expression, +) -> BasicTypeEnum<'ctx> { let wt = wave_type_of_lvalue(env, target) .unwrap_or_else(|| panic!("Cannot infer pointer pointee type: {:?}", target)); match wt { WaveType::Pointer(inner) => wave_to_basic(env, &inner), WaveType::String => env.context.i8_type().as_basic_type_enum(), - _ => { - env.context.i8_type().as_basic_type_enum() - } + _ => env.context.i8_type().as_basic_type_enum(), } } @@ -146,7 +168,13 @@ pub(crate) fn gen<'ctx, 'a>( target: &Expression, ) -> BasicValueEnum<'ctx> { let ptr = generate_address_ir( - env.context, env.builder, target, env.variables, env.module, env.struct_types, env.struct_field_indices + env.context, + env.builder, + target, + env.variables, + env.module, + env.struct_types, + env.struct_field_indices, ); let element_type = infer_lvalue_value_type(env, target); @@ -164,8 +192,12 @@ pub(crate) fn gen<'ctx, 'a>( let one = iv.get_type().const_int(1, false); let nv = match kind { - IncDecKind::PreInc | IncDecKind::PostInc => env.builder.build_int_add(iv, one, "inc").unwrap(), - IncDecKind::PreDec | IncDecKind::PostDec => env.builder.build_int_sub(iv, one, "dec").unwrap(), + IncDecKind::PreInc | IncDecKind::PostInc => { + env.builder.build_int_add(iv, one, "inc").unwrap() + } + IncDecKind::PreDec | IncDecKind::PostDec => { + env.builder.build_int_sub(iv, one, "dec").unwrap() + } }; nv.as_basic_value_enum() } @@ -173,16 +205,24 @@ pub(crate) fn gen<'ctx, 'a>( BasicValueEnum::FloatValue(fv) => { let one = fv.get_type().const_float(1.0); let nv = match kind { - IncDecKind::PreInc | IncDecKind::PostInc => env.builder.build_float_add(fv, one, "finc").unwrap(), - IncDecKind::PreDec | IncDecKind::PostDec => env.builder.build_float_sub(fv, one, "fdec").unwrap(), + IncDecKind::PreInc | IncDecKind::PostInc => { + env.builder.build_float_add(fv, one, "finc").unwrap() + } + IncDecKind::PreDec | IncDecKind::PostDec => { + env.builder.build_float_sub(fv, one, "fdec").unwrap() + } }; nv.as_basic_value_enum() } BasicValueEnum::PointerValue(pv) => { let idx = match kind { - IncDecKind::PreInc | IncDecKind::PostInc => env.context.i64_type().const_int(1, true), - IncDecKind::PreDec | IncDecKind::PostDec => env.context.i64_type().const_int((-1i64) as u64, true), + IncDecKind::PreInc | IncDecKind::PostInc => { + env.context.i64_type().const_int(1, true) + } + IncDecKind::PreDec | IncDecKind::PostDec => { + env.context.i64_type().const_int((-1i64) as u64, true) + } }; let pointee_ty = infer_ptr_pointee_type(env, target); diff --git a/llvm/src/expression/rvalue/index.rs b/llvm/src/expression/rvalue/index.rs index 27997e9f..2fcaa49b 100644 --- a/llvm/src/expression/rvalue/index.rs +++ b/llvm/src/expression/rvalue/index.rs @@ -10,10 +10,10 @@ // SPDX-License-Identifier: MPL-2.0 use super::ExprGenEnv; +use crate::codegen::generate_address_and_type_ir; use inkwell::types::BasicType; use inkwell::values::{BasicValue, BasicValueEnum}; use parser::ast::Expression; -use crate::codegen::generate_address_and_type_ir; pub(crate) fn gen<'ctx, 'a>( env: &mut ExprGenEnv<'ctx, 'a>, diff --git a/llvm/src/expression/rvalue/literals.rs b/llvm/src/expression/rvalue/literals.rs index 897d97ff..36ca8e0c 100644 --- a/llvm/src/expression/rvalue/literals.rs +++ b/llvm/src/expression/rvalue/literals.rs @@ -49,7 +49,10 @@ pub(crate) fn gen_null<'ctx, 'a>( ) -> BasicValueEnum<'ctx> { match expected_type { Some(BasicTypeEnum::PointerType(ptr_ty)) => ptr_ty.const_null().as_basic_value_enum(), - Some(other) => panic!("null literal can only be used with pointer expected type, got {:?}", other), + Some(other) => panic!( + "null literal can only be used with pointer expected type, got {:?}", + other + ), None => env .context .ptr_type(AddressSpace::default()) @@ -115,13 +118,18 @@ pub(crate) fn gen<'ctx, 'a>( } iv.as_basic_value_enum() - }, + } - _ => panic!("Unsupported expected_type for int literal: {:?}", expected_type), - } + _ => panic!( + "Unsupported expected_type for int literal: {:?}", + expected_type + ), + }, Literal::Float(value) => match expected_type { - Some(BasicTypeEnum::FloatType(float_ty)) => float_ty.const_float(*value).as_basic_value_enum(), + Some(BasicTypeEnum::FloatType(float_ty)) => { + float_ty.const_float(*value).as_basic_value_enum() + } Some(BasicTypeEnum::IntType(int_ty)) => env .builder .build_float_to_signed_int( @@ -131,7 +139,11 @@ pub(crate) fn gen<'ctx, 'a>( ) .unwrap() .as_basic_value_enum(), - None => env.context.f32_type().const_float(*value).as_basic_value_enum(), + None => env + .context + .f32_type() + .const_float(*value) + .as_basic_value_enum(), _ => panic!("Unsupported expected_type for float"), }, @@ -156,17 +168,12 @@ pub(crate) fn gen<'ctx, 'a>( let gep = unsafe { env.builder - .build_in_bounds_gep( - str_type, - global.as_pointer_value(), - &indices, - "str_gep", - ) + .build_in_bounds_gep(str_type, global.as_pointer_value(), &indices, "str_gep") .unwrap() }; gep.as_basic_value_enum() - }, + } Literal::Bool(v) => env .context @@ -174,7 +181,15 @@ pub(crate) fn gen<'ctx, 'a>( .const_int(if *v { 1 } else { 0 }, false) .as_basic_value_enum(), - Literal::Char(c) => env.context.i8_type().const_int(*c as u64, false).as_basic_value_enum(), - Literal::Byte(b) => env.context.i8_type().const_int(*b as u64, false).as_basic_value_enum(), + Literal::Char(c) => env + .context + .i8_type() + .const_int(*c as u64, false) + .as_basic_value_enum(), + Literal::Byte(b) => env + .context + .i8_type() + .const_int(*b as u64, false) + .as_basic_value_enum(), } } diff --git a/llvm/src/expression/rvalue/pointers.rs b/llvm/src/expression/rvalue/pointers.rs index 6dc99417..dd0229ca 100644 --- a/llvm/src/expression/rvalue/pointers.rs +++ b/llvm/src/expression/rvalue/pointers.rs @@ -10,8 +10,8 @@ // SPDX-License-Identifier: MPL-2.0 use super::ExprGenEnv; -use crate::codegen::{generate_address_and_type_ir, generate_address_ir}; use crate::codegen::types::{wave_type_to_llvm_type, TypeFlavor}; +use crate::codegen::{generate_address_and_type_ir, generate_address_ir}; use crate::statement::variable::{coerce_basic_value, CoercionMode}; use inkwell::types::AsTypeRef; use inkwell::types::{BasicType, BasicTypeEnum}; @@ -34,7 +34,9 @@ fn push_deref_into_base(expr: &Expression) -> Expression { } fn normalize_struct_name(raw: &str) -> &str { - raw.strip_prefix("struct.").unwrap_or(raw).trim_start_matches('%') + raw.strip_prefix("struct.") + .unwrap_or(raw) + .trim_start_matches('%') } fn resolve_struct_key<'ctx>( @@ -162,7 +164,9 @@ fn infer_deref_load_ty<'ctx, 'a>( WaveType::String => env.context.i8_type().as_basic_type_enum(), other => match inner_expr { // Preserve legacy behavior for lvalues like `deref visited[x]`. - Expression::IndexAccess { .. } | Expression::FieldAccess { .. } | Expression::Grouped(_) => { + Expression::IndexAccess { .. } + | Expression::FieldAccess { .. } + | Expression::Grouped(_) => { wave_type_to_llvm_type(env.context, &other, env.struct_types, TypeFlavor::Value) } _ => panic!("deref expects pointer type, got {:?}", other), diff --git a/llvm/src/expression/rvalue/structs.rs b/llvm/src/expression/rvalue/structs.rs index 6eb7895e..9aadbbe6 100644 --- a/llvm/src/expression/rvalue/structs.rs +++ b/llvm/src/expression/rvalue/structs.rs @@ -10,10 +10,10 @@ // SPDX-License-Identifier: MPL-2.0 use super::ExprGenEnv; +use crate::codegen::generate_address_and_type_ir; use inkwell::types::{BasicType, BasicTypeEnum}; use inkwell::values::{BasicValue, BasicValueEnum}; use parser::ast::Expression; -use crate::codegen::generate_address_and_type_ir; pub(crate) fn gen_struct_literal<'ctx, 'a>( env: &mut ExprGenEnv<'ctx, 'a>, @@ -49,20 +49,32 @@ pub(crate) fn gen_struct_literal<'ctx, 'a>( if field_val.get_type() != expected_field_ty { panic!( "Struct literal field type mismatch: {}.{} expected {:?}, got {:?}", - name, field_name, expected_field_ty, field_val.get_type() + name, + field_name, + expected_field_ty, + field_val.get_type() ); } let field_ptr = env .builder - .build_struct_gep(struct_ty, tmp_alloca, idx, &format!("{}.{}", name, field_name)) + .build_struct_gep( + struct_ty, + tmp_alloca, + idx, + &format!("{}.{}", name, field_name), + ) .unwrap(); env.builder.build_store(field_ptr, field_val).unwrap(); } env.builder - .build_load(struct_ty.as_basic_type_enum(), tmp_alloca, &format!("{}_literal_val", name)) + .build_load( + struct_ty.as_basic_type_enum(), + tmp_alloca, + &format!("{}_literal_val", name), + ) .unwrap() .as_basic_value_enum() } diff --git a/llvm/src/expression/rvalue/unary.rs b/llvm/src/expression/rvalue/unary.rs index 9a9212cf..3bbb8152 100644 --- a/llvm/src/expression/rvalue/unary.rs +++ b/llvm/src/expression/rvalue/unary.rs @@ -46,7 +46,10 @@ pub(crate) fn gen<'ctx, 'a>( | (Operator::Not, BasicValueEnum::IntValue(iv)) => { let bw = iv.get_type().get_bit_width(); if bw == 1 { - env.builder.build_not(iv, "lnot").unwrap().as_basic_value_enum() + env.builder + .build_not(iv, "lnot") + .unwrap() + .as_basic_value_enum() } else { let zero = iv.get_type().const_zero(); env.builder @@ -57,10 +60,15 @@ pub(crate) fn gen<'ctx, 'a>( } // ~ (bitwise not) - (Operator::BitwiseNot, BasicValueEnum::IntValue(iv)) => { - env.builder.build_not(iv, "bnot").unwrap().as_basic_value_enum() - } + (Operator::BitwiseNot, BasicValueEnum::IntValue(iv)) => env + .builder + .build_not(iv, "bnot") + .unwrap() + .as_basic_value_enum(), - _ => panic!("Unsupported unary operator {:?} for value {:?}", operator, val), + _ => panic!( + "Unsupported unary operator {:?} for value {:?}", + operator, val + ), } } diff --git a/llvm/src/expression/rvalue/variables.rs b/llvm/src/expression/rvalue/variables.rs index 439d8121..ec9065eb 100644 --- a/llvm/src/expression/rvalue/variables.rs +++ b/llvm/src/expression/rvalue/variables.rs @@ -13,8 +13,8 @@ use super::ExprGenEnv; use inkwell::types::BasicTypeEnum; use inkwell::values::{BasicValue, BasicValueEnum}; -use parser::ast::WaveType; use crate::codegen::types::{wave_type_to_llvm_type, TypeFlavor}; +use parser::ast::WaveType; pub(crate) fn gen<'ctx, 'a>( env: &mut ExprGenEnv<'ctx, 'a>, @@ -22,9 +22,17 @@ pub(crate) fn gen<'ctx, 'a>( expected_type: Option>, ) -> BasicValueEnum<'ctx> { if var_name == "true" { - return env.context.bool_type().const_int(1, false).as_basic_value_enum(); + return env + .context + .bool_type() + .const_int(1, false) + .as_basic_value_enum(); } else if var_name == "false" { - return env.context.bool_type().const_int(0, false).as_basic_value_enum(); + return env + .context + .bool_type() + .const_int(0, false) + .as_basic_value_enum(); } if let Some(const_val) = env.global_consts.get(var_name) { diff --git a/llvm/src/importgen.rs b/llvm/src/importgen.rs index 39d4f794..c0a42589 100644 --- a/llvm/src/importgen.rs +++ b/llvm/src/importgen.rs @@ -32,7 +32,6 @@ fn expand_imports_recursive( let expanded = expand_imports_recursive(imported.ast, next_dir, already)?; out.extend(expanded); - } other => out.push(other), } @@ -41,7 +40,10 @@ fn expand_imports_recursive( Ok(out) } -pub fn build_codegen_ast(entry_path: &Path, entry_ast: Vec) -> Result, WaveError> { +pub fn build_codegen_ast( + entry_path: &Path, + entry_ast: Vec, +) -> Result, WaveError> { let mut already = HashSet::new(); if let Ok(abs) = entry_path.canonicalize() { diff --git a/llvm/src/lib.rs b/llvm/src/lib.rs index d53158a7..5da59478 100644 --- a/llvm/src/lib.rs +++ b/llvm/src/lib.rs @@ -9,12 +9,11 @@ // // SPDX-License-Identifier: MPL-2.0 -pub mod expression; pub mod backend; pub mod codegen; -pub mod statement; +pub mod expression; pub mod importgen; - +pub mod statement; pub fn backend() -> Option { unsafe { @@ -25,4 +24,3 @@ pub fn backend() -> Option { Some(format!("LLVM {}.{}.{}", major, minor, patch)) } } - diff --git a/llvm/src/statement/assign.rs b/llvm/src/statement/assign.rs index cf46149d..52ad7cb4 100644 --- a/llvm/src/statement/assign.rs +++ b/llvm/src/statement/assign.rs @@ -9,17 +9,19 @@ // // SPDX-License-Identifier: MPL-2.0 +use crate::codegen::abi_c::ExternCInfo; +use crate::codegen::types::TypeFlavor; +use crate::codegen::{ + generate_address_and_type_ir, generate_address_ir, wave_type_to_llvm_type, VariableInfo, +}; use crate::expression::rvalue::generate_expression_ir; -use crate::codegen::{generate_address_and_type_ir, generate_address_ir, wave_type_to_llvm_type, VariableInfo}; +use crate::statement::variable::{coerce_basic_value, CoercionMode}; use inkwell::module::Module; +use inkwell::targets::TargetData; use inkwell::types::{AnyTypeEnum, BasicType, BasicTypeEnum, StructType}; use inkwell::values::{BasicValue, BasicValueEnum}; use parser::ast::{Expression, Mutability}; use std::collections::HashMap; -use inkwell::targets::TargetData; -use crate::codegen::abi_c::ExternCInfo; -use crate::codegen::types::TypeFlavor; -use crate::statement::variable::{coerce_basic_value, CoercionMode}; pub(super) fn gen_assign_ir<'ctx>( context: &'ctx inkwell::context::Context, @@ -37,16 +39,28 @@ pub(super) fn gen_assign_ir<'ctx>( if variable == "deref" { if let Expression::BinaryExpression { left, right, .. } = value { if let Expression::Deref(inner_expr) = &**left { - let target_ptr = - generate_address_ir(context, builder, inner_expr, variables, module, struct_types, struct_field_indices); + let target_ptr = generate_address_ir( + context, + builder, + inner_expr, + variables, + module, + struct_types, + struct_field_indices, + ); let expected_elem_ty: BasicTypeEnum<'ctx> = match &**inner_expr { Expression::Variable(name) => { - let info = variables.get(name).unwrap_or_else(|| panic!("Pointer var '{}' not declared", name)); + let info = variables + .get(name) + .unwrap_or_else(|| panic!("Pointer var '{}' not declared", name)); match &info.ty { - parser::ast::WaveType::Pointer(inner) => { - wave_type_to_llvm_type(context, inner.as_ref(), struct_types, TypeFlavor::Value) - } + parser::ast::WaveType::Pointer(inner) => wave_type_to_llvm_type( + context, + inner.as_ref(), + struct_types, + TypeFlavor::Value, + ), parser::ast::WaveType::String => context.i8_type().as_basic_type_enum(), other => panic!("deref target is not a pointer/string: {:?}", other), } @@ -80,11 +94,17 @@ pub(super) fn gen_assign_ir<'ctx>( ); if val.get_type() != expected_elem_ty { - val = coerce_basic_value(context, builder, val, expected_elem_ty, "deref_assign_cast", CoercionMode::Implicit); + val = coerce_basic_value( + context, + builder, + val, + expected_elem_ty, + "deref_assign_cast", + CoercionMode::Implicit, + ); } builder.build_store(target_ptr, val).unwrap(); - } } return; @@ -104,7 +124,8 @@ pub(super) fn gen_assign_ir<'ctx>( let element_type: BasicTypeEnum<'ctx> = wave_type_to_llvm_type(context, &dst_wave_ty, struct_types, TypeFlavor::Value); - if matches!(value, Expression::Null) && !matches!(dst_wave_ty, parser::ast::WaveType::Pointer(_)) + if matches!(value, Expression::Null) + && !matches!(dst_wave_ty, parser::ast::WaveType::Pointer(_)) { panic!( "null literal can only be assigned to ptr (variable '{}': {:?})", @@ -140,5 +161,4 @@ pub(super) fn gen_assign_ir<'ctx>( } builder.build_store(dst_ptr, casted_val).unwrap(); - } diff --git a/llvm/src/statement/control.rs b/llvm/src/statement/control.rs index 7ea5e74f..449f2af7 100644 --- a/llvm/src/statement/control.rs +++ b/llvm/src/statement/control.rs @@ -9,19 +9,19 @@ // // SPDX-License-Identifier: MPL-2.0 +use crate::codegen::abi_c::ExternCInfo; use crate::codegen::VariableInfo; use crate::expression::rvalue::generate_expression_ir; +use crate::statement::variable::{coerce_basic_value, CoercionMode}; use inkwell::basic_block::BasicBlock; use inkwell::module::Module; -use inkwell::types::{StructType}; -use inkwell::values::{AnyValue, BasicValueEnum, FunctionValue}; +use inkwell::targets::TargetData; use inkwell::types::StringRadix; +use inkwell::types::StructType; +use inkwell::values::{AnyValue, BasicValueEnum, FunctionValue}; use inkwell::{FloatPredicate, IntPredicate}; use parser::ast::{ASTNode, Expression, MatchArm, MatchPattern, WaveType}; use std::collections::{HashMap, HashSet}; -use inkwell::targets::TargetData; -use crate::codegen::abi_c::ExternCInfo; -use crate::statement::variable::{coerce_basic_value, CoercionMode}; fn truthy_to_i1<'ctx>( context: &'ctx inkwell::context::Context, @@ -388,7 +388,7 @@ pub(super) fn gen_while_ir<'ctx>( ); } - let end_bb = builder.get_insert_block().unwrap(); + let end_bb = builder.get_insert_block().unwrap(); if end_bb.get_terminator().is_none() { builder.build_unconditional_branch(cond_block).unwrap(); } @@ -464,7 +464,8 @@ pub(super) fn gen_match_ir<'ctx>( panic!("duplicate match case value: {}", case_key); } - let case_block = context.append_basic_block(current_fn, &format!("match.case.{}", idx)); + let case_block = + context.append_basic_block(current_fn, &format!("match.case.{}", idx)); case_entries.push((case_value, case_block, arm)); } } @@ -476,10 +477,8 @@ pub(super) fn gen_match_ir<'ctx>( merge_block }; - let switch_cases: Vec<(inkwell::values::IntValue<'ctx>, BasicBlock<'ctx>)> = case_entries - .iter() - .map(|(v, bb, _)| (*v, *bb)) - .collect(); + let switch_cases: Vec<(inkwell::values::IntValue<'ctx>, BasicBlock<'ctx>)> = + case_entries.iter().map(|(v, bb, _)| (*v, *bb)).collect(); builder .build_switch(discr, default_block, &switch_cases) diff --git a/llvm/src/statement/expr_stmt.rs b/llvm/src/statement/expr_stmt.rs index 12cce98d..603e01ac 100644 --- a/llvm/src/statement/expr_stmt.rs +++ b/llvm/src/statement/expr_stmt.rs @@ -9,15 +9,15 @@ // // SPDX-License-Identifier: MPL-2.0 -use crate::expression::rvalue::generate_expression_ir; +use crate::codegen::abi_c::ExternCInfo; use crate::codegen::VariableInfo; +use crate::expression::rvalue::generate_expression_ir; use inkwell::module::Module; +use inkwell::targets::TargetData; use inkwell::types::StructType; use inkwell::values::BasicValueEnum; use parser::ast::Expression; use std::collections::HashMap; -use inkwell::targets::TargetData; -use crate::codegen::abi_c::ExternCInfo; pub(super) fn gen_expr_stmt_ir<'ctx>( context: &'ctx inkwell::context::Context, diff --git a/llvm/src/statement/io.rs b/llvm/src/statement/io.rs index 313011f8..9f29d4c0 100644 --- a/llvm/src/statement/io.rs +++ b/llvm/src/statement/io.rs @@ -137,25 +137,27 @@ fn lvalue_elem_basic_type<'ctx>( struct_field_types: &HashMap>, ) -> BasicTypeEnum<'ctx> { match e { - Expression::Grouped(inner) => { - lvalue_elem_basic_type( - context, - inner, - vars, - struct_types, - struct_field_indices, - struct_field_types, - ) - } + Expression::Grouped(inner) => lvalue_elem_basic_type( + context, + inner, + vars, + struct_types, + struct_field_indices, + struct_field_types, + ), Expression::Variable(name) => { - let vi = vars.get(name).unwrap_or_else(|| panic!("var '{}' not found", name)); + let vi = vars + .get(name) + .unwrap_or_else(|| panic!("var '{}' not found", name)); llvm_type_of_wave(context, &vi.ty, struct_types) } Expression::Deref(inner) => { if let Expression::Variable(name) = inner.as_ref() { - let vi = vars.get(name).unwrap_or_else(|| panic!("ptr var '{}' not found", name)); + let vi = vars + .get(name) + .unwrap_or_else(|| panic!("ptr var '{}' not found", name)); match &vi.ty { WaveType::Pointer(t) => llvm_type_of_wave(context, t.as_ref(), struct_types), WaveType::String => context.i8_type().as_basic_type_enum(), @@ -257,7 +259,10 @@ pub(super) fn gen_print_format_ir<'ctx>( let mut printf_vals: Vec> = Vec::with_capacity(args.len()); - let void_ptr_ty = context.i8_type().ptr_type(AddressSpace::default()).as_basic_type_enum(); + let void_ptr_ty = context + .i8_type() + .ptr_type(AddressSpace::default()) + .as_basic_type_enum(); for arg in args { let val = generate_expression_ir( @@ -282,7 +287,10 @@ pub(super) fn gen_print_format_ir<'ctx>( let bw = iv.get_type().get_bit_width(); if bw < 32 { // C varargs: promote small ints to i32 - let signed = wt.as_ref().map(|t| matches!(t, WaveType::Int(_))).unwrap_or(false); + let signed = wt + .as_ref() + .map(|t| matches!(t, WaveType::Int(_))) + .unwrap_or(false); let promoted = if signed { builder @@ -474,8 +482,7 @@ pub(super) fn gen_input_ir<'ctx>( .unwrap() }; - let mut scanf_args: Vec> = - Vec::with_capacity(1 + ptrs.len()); + let mut scanf_args: Vec> = Vec::with_capacity(1 + ptrs.len()); scanf_args.push(fmt_gep.into()); for p in ptrs { scanf_args.push(p.into()); @@ -502,17 +509,25 @@ pub(super) fn gen_input_ir<'ctx>( let fail_bb = context.append_basic_block(cur_fn, "input_fail"); let cont_bb = context.append_basic_block(cur_fn, "input_cont"); - builder.build_conditional_branch(ok, ok_bb, fail_bb).unwrap(); + builder + .build_conditional_branch(ok, ok_bb, fail_bb) + .unwrap(); builder.position_at_end(fail_bb); - let exit_ty = context.void_type().fn_type(&[context.i32_type().into()], false); + let exit_ty = context + .void_type() + .fn_type(&[context.i32_type().into()], false); let exit_fn = module .get_function("exit") .unwrap_or_else(|| module.add_function("exit", exit_ty, None)); builder - .build_call(exit_fn, &[context.i32_type().const_int(1, false).into()], "exit_call") + .build_call( + exit_fn, + &[context.i32_type().const_int(1, false).into()], + "exit_call", + ) .unwrap(); builder.build_unreachable().unwrap(); diff --git a/llvm/src/statement/mod.rs b/llvm/src/statement/mod.rs index 414a9d50..5528c5c6 100644 --- a/llvm/src/statement/mod.rs +++ b/llvm/src/statement/mod.rs @@ -9,23 +9,23 @@ // // SPDX-License-Identifier: MPL-2.0 -pub mod assign; pub mod asm; +pub mod assign; pub mod control; pub mod expr_stmt; pub mod io; pub mod variable; +use crate::codegen::abi_c::ExternCInfo; use crate::codegen::VariableInfo; use inkwell::basic_block::BasicBlock; use inkwell::context::Context; +use inkwell::targets::TargetData; use inkwell::types::StructType; use inkwell::values::{BasicValueEnum, FunctionValue}; +use parser::ast::WaveType; use parser::ast::{ASTNode, StatementNode}; use std::collections::HashMap; -use inkwell::targets::TargetData; -use crate::codegen::abi_c::ExternCInfo; -use parser::ast::WaveType; pub fn generate_statement_ir<'ctx>( context: &'ctx Context, @@ -103,11 +103,11 @@ pub fn generate_statement_ir<'ctx>( } ASTNode::Statement(StatementNode::If { - condition, - body, - else_if_blocks, - else_block, - }) => { + condition, + body, + else_if_blocks, + else_block, + }) => { control::gen_if_ir( context, builder, @@ -173,11 +173,11 @@ pub fn generate_statement_ir<'ctx>( } ASTNode::Statement(StatementNode::For { - initialization, - condition, - increment, - body, - }) => { + initialization, + condition, + increment, + body, + }) => { control::gen_for_ir( context, builder, @@ -201,11 +201,11 @@ pub fn generate_statement_ir<'ctx>( } ASTNode::Statement(StatementNode::AsmBlock { - instructions, - inputs, - outputs, - clobbers, - }) => { + instructions, + inputs, + outputs, + clobbers, + }) => { asm::gen_asm_stmt_ir( context, builder, diff --git a/llvm/src/statement/variable.rs b/llvm/src/statement/variable.rs index c38f5463..e3b62ca5 100644 --- a/llvm/src/statement/variable.rs +++ b/llvm/src/statement/variable.rs @@ -17,7 +17,7 @@ use crate::expression::rvalue::generate_expression_ir; use inkwell::module::Module; use inkwell::targets::TargetData; use inkwell::types::{BasicType, BasicTypeEnum, StructType}; -use inkwell::values::{BasicValue, BasicValueEnum}; +use inkwell::values::{BasicValue, BasicValueEnum, PointerValue}; use parser::ast::{Expression, Mutability, VariableNode, WaveType}; @@ -144,8 +144,7 @@ pub(super) fn gen_variable_ir<'ctx>( mutability, } = var_node; - if matches!(initial_value, Some(Expression::Null)) - && !matches!(type_name, WaveType::Pointer(_)) + if matches!(initial_value, Some(Expression::Null)) && !matches!(type_name, WaveType::Pointer(_)) { panic!( "null literal can only be assigned to ptr (variable '{}': {:?})", @@ -154,19 +153,26 @@ pub(super) fn gen_variable_ir<'ctx>( } let llvm_type = wave_type_to_llvm_type(context, type_name, struct_types, TypeFlavor::AbiC); - let alloca = builder.build_alloca(llvm_type, name).unwrap(); + let alloca = build_entry_alloca(context, builder, llvm_type, name); // Array literal init: var a: array = [ ... ] if let (WaveType::Array(element_type, size), Some(Expression::ArrayLiteral(values))) = (type_name, initial_value.as_ref()) { if values.len() != *size as usize { - panic!("❌ Array length mismatch: expected {}, got {}", size, values.len()); + panic!( + "❌ Array length mismatch: expected {}, got {}", + size, + values.len() + ); } let array_ty = match llvm_type { BasicTypeEnum::ArrayType(a) => a, - other => panic!("WaveType::Array must lower to LLVM array type, got {:?}", other), + other => panic!( + "WaveType::Array must lower to LLVM array type, got {:?}", + other + ), }; let llvm_element_type = @@ -269,3 +275,29 @@ pub(super) fn gen_variable_ir<'ctx>( // nothing to do here } } + +fn build_entry_alloca<'ctx>( + context: &'ctx inkwell::context::Context, + builder: &'ctx inkwell::builder::Builder<'ctx>, + ty: BasicTypeEnum<'ctx>, + name: &str, +) -> PointerValue<'ctx> { + let cur_block = builder + .get_insert_block() + .unwrap_or_else(|| panic!("build_entry_alloca: no insert block")); + let func = cur_block + .get_parent() + .unwrap_or_else(|| panic!("build_entry_alloca: insert block has no parent function")); + let entry = func + .get_first_basic_block() + .unwrap_or_else(|| panic!("build_entry_alloca: function has no entry block")); + + let entry_builder = context.create_builder(); + if let Some(inst) = entry.get_first_instruction() { + entry_builder.position_before(&inst); + } else { + entry_builder.position_at_end(entry); + } + + entry_builder.build_alloca(ty, name).unwrap() +} diff --git a/src/cli.rs b/src/cli.rs index 0955b1c5..b015153a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -13,17 +13,21 @@ use crate::errors::CliError; use crate::flags::{validate_opt_flag, DebugFlags, DepFlags, DepPackage, LinkFlags}; use crate::{runner, std as wave_std, version}; -use std::{env, path::PathBuf}; +use crate::version::get_os_pretty_name; use llvm::backend; +use std::{env, path::PathBuf}; use utils::colorex::*; -use crate::version::get_os_pretty_name; #[derive(Debug)] enum Command { - Run { file: PathBuf }, - Img { file: PathBuf }, - BuildExe { file: PathBuf }, - BuildObj { file: PathBuf }, + Run { + file: PathBuf, + }, + Build { + file: PathBuf, + output: Option, + compile_only: bool, + }, StdInstall, StdUpdate, @@ -71,28 +75,35 @@ fn dispatch(global: Global, cmd: Command) -> Result<(), CliError> { Ok(()) } - Command::Img { file } => { - unsafe { - runner::img_wave_file(&file, &global.dep); - } - Ok(()) - } - - Command::BuildExe { file } => { + Command::Build { + file, + output, + compile_only, + } => { unsafe { - runner::build_wave_file(&file, &global.opt, &global.debug, &global.link, &global.dep); + if compile_only { + let out = runner::object_build_wave_file( + &file, + &global.opt, + &global.debug, + &global.dep, + output.as_deref(), + ); + println!("{}", out); + } else { + runner::build_wave_file( + &file, + &global.opt, + &global.debug, + &global.link, + &global.dep, + output.as_deref(), + ); + } } Ok(()) } - Command::BuildObj { file } => { - let obj = unsafe { - runner::object_build_wave_file(&file, &global.opt, &global.debug, &global.dep) - }; - println!("{}", obj); - Ok(()) - } - Command::StdInstall => wave_std::std_install(), Command::StdUpdate => wave_std::std_update(), } @@ -290,7 +301,6 @@ fn parse_command(rest: Vec) -> Result { "--version" | "-V" | "version" => Ok(Command::Version), "run" => parse_run(args), - "img" => parse_img(args), "build" => parse_build(args), "install" => parse_install(args), @@ -301,16 +311,9 @@ fn parse_command(rest: Vec) -> Result { } fn parse_run(args: &[String]) -> Result { - // legacy: run --img file - let mut img = false; let mut file: Option = None; for a in args { - if a == "--img" { - img = true; - continue; - } - if a.starts_with('-') { return Err(CliError::usage(format!("unknown option for run: {}", a))); } @@ -324,30 +327,51 @@ fn parse_run(args: &[String]) -> Result { let file = file.ok_or_else(|| CliError::usage("usage: wavec run "))?; - if img { - Ok(Command::Img { file }) - } else { - Ok(Command::Run { file }) - } -} - -fn parse_img(args: &[String]) -> Result { - let file = args.get(0).ok_or_else(|| CliError::usage("usage: wavec img "))?; - if args.len() > 1 { - return Err(CliError::usage(format!("unexpected extra argument: {}", args[1]))); - } - Ok(Command::Img { file: PathBuf::from(file) }) + Ok(Command::Run { file }) } fn parse_build(args: &[String]) -> Result { - // build - // build -o (object only) - let mut emit_obj = false; + // build [-o ] [-c] + let mut compile_only = false; + let mut output: Option = None; let mut file: Option = None; + let mut i = 0usize; - for a in args { + while i < args.len() { + let a = &args[i]; match a.as_str() { - "-o" | "--obj" => emit_obj = true, + "-c" => { + compile_only = true; + i += 1; + } + "-o" => { + let Some(v) = args.get(i + 1) else { + return Err(CliError::usage("missing value: -o ")); + }; + if v.starts_with('-') { + return Err(CliError::usage(format!("invalid output file: {}", v))); + } + output = Some(PathBuf::from(v)); + i += 2; + } + "--output" => { + let Some(v) = args.get(i + 1) else { + return Err(CliError::usage("missing value: --output ")); + }; + if v.starts_with('-') { + return Err(CliError::usage(format!("invalid output file: {}", v))); + } + output = Some(PathBuf::from(v)); + i += 2; + } + _ if a.starts_with("--output=") => { + let v = a.trim_start_matches("--output="); + if v.trim().is_empty() { + return Err(CliError::usage("missing value: --output=")); + } + output = Some(PathBuf::from(v)); + i += 1; + } _ if a.starts_with('-') => { return Err(CliError::usage(format!("unknown option for build: {}", a))); } @@ -357,45 +381,62 @@ fn parse_build(args: &[String]) -> Result { } else { return Err(CliError::usage(format!("unexpected extra argument: {}", a))); } + i += 1; } } } - let file = file.ok_or_else(|| CliError::usage("usage: wavec build "))?; + let file = file.ok_or_else(|| CliError::usage("usage: wavec build [-o ] [-c]"))?; - if emit_obj { - Ok(Command::BuildObj { file }) - } else { - Ok(Command::BuildExe { file }) - } + Ok(Command::Build { + file, + output, + compile_only, + }) } fn parse_install(args: &[String]) -> Result { - let target = args.get(0).ok_or_else(|| CliError::usage("usage: wavec install "))?; + let target = args + .get(0) + .ok_or_else(|| CliError::usage("usage: wavec install "))?; if args.len() > 1 { - return Err(CliError::usage(format!("unexpected extra argument: {}", args[1]))); + return Err(CliError::usage(format!( + "unexpected extra argument: {}", + args[1] + ))); } match target.as_str() { "std" => Ok(Command::StdInstall), - _ => Err(CliError::usage(format!("unknown install target: {}", target))), + _ => Err(CliError::usage(format!( + "unknown install target: {}", + target + ))), } } fn parse_update(args: &[String]) -> Result { - let target = args.get(0).ok_or_else(|| CliError::usage("usage: wavec update "))?; + let target = args + .get(0) + .ok_or_else(|| CliError::usage("usage: wavec update "))?; if args.len() > 1 { - return Err(CliError::usage(format!("unexpected extra argument: {}", args[1]))); + return Err(CliError::usage(format!( + "unexpected extra argument: {}", + args[1] + ))); } match target.as_str() { "std" => Ok(Command::StdUpdate), - _ => Err(CliError::usage(format!("unknown update target: {}", target))), + _ => Err(CliError::usage(format!( + "unknown update target: {}", + target + ))), } } pub fn print_usage() { - eprintln!( + println!( "\n{} {}", "Usage:".color("255,71,71"), "wavec [global-options] [command-options]" @@ -424,20 +465,78 @@ pub fn print_help() { print_usage(); println!("\nCommands:"); - println!(" {:<18} {}", "run ".color("38,139,235"), "Execute Wave file"); - println!(" {:<18} {}", "img ".color("38,139,235"), "Build & run image via QEMU (legacy: run --img )"); - println!(" {:<18} {}", "build ".color("38,139,235"), "Compile Wave file (exe)"); - println!(" {:<18} {}", "build -o ".color("38,139,235"), "Compile Wave file (object; prints path)"); - println!(" {:<18} {}", "install std".color("38,139,235"), "Install Wave standard library"); - println!(" {:<18} {}", "update std".color("38,139,235"), "Update Wave standard library"); - println!(" {:<18} {}", "--version".color("38,139,235"), "Show version"); - println!(" {:<18} {}", "--help".color("38,139,235"), "Show help"); - - println!("\nGlobal options (anywhere):"); - println!(" {:<22} {}", "-O0..-O3/-Os/-Oz/-Ofast".color("38,139,235"), "Optimization level"); - println!(" {:<22} {}", "--debug-wave=...".color("38,139,235"), "tokens,ast,ir,mc,hex,all (comma ok)"); - println!(" {:<22} {}", "--link=".color("38,139,235"), "Link library"); - println!(" {:<22} {}", "-L / -L ".color("38,139,235"), "Library search path"); - println!(" {:<22} {}", "--dep-root=".color("38,139,235"), "Dependency root directory (e.g. .vex/dep)"); - println!(" {:<22} {}", "--dep==".color("38,139,235"), "Explicit dependency package mapping"); + println!( + " {:<20} {}", + "run ".color("38,139,235"), + "Compile & execute Wave file" + ); + println!( + " {:<20} {}", + "build ".color("38,139,235"), + "Compile Wave file (executable)" + ); + println!( + " {:<20} {}", + "install std".color("38,139,235"), + "Install Wave standard library" + ); + println!( + " {:<20} {}", + "update std".color("38,139,235"), + "Update Wave standard library" + ); + println!( + " {:<20} {}", + "--version".color("38,139,235"), + "Show version" + ); + println!(" {:<20} {}", "--help".color("38,139,235"), "Show help"); + + println!("\nBuild options:"); + println!( + " {:<22} {}", + "-o ".color("38,139,235"), + "Specify output file name" + ); + println!( + " {:<22} {}", + "-c".color("38,139,235"), + "Compile only (emit object file)" + ); + println!( + " {:<22} {}", + "-O0..-O3/-Os/-Oz/-Ofast".color("38,139,235"), + "Optimization level" + ); + + println!("\nDebug options:"); + println!( + " {:<22} {}", + "--debug-wave=...".color("38,139,235"), + "tokens,ast,ir,mc,hex,all (comma ok)" + ); + + println!("\nLink options:"); + println!( + " {:<22} {}", + "--link=".color("38,139,235"), + "Link library" + ); + println!( + " {:<22} {}", + "-L ".color("38,139,235"), + "Library search path" + ); + + println!("\nDependency options:"); + println!( + " {:<22} {}", + "--dep-root=".color("38,139,235"), + "Dependency root directory (e.g. .vex/dep)" + ); + println!( + " {:<22} {}", + "--dep==".color("38,139,235"), + "Explicit dependency package mapping" + ); } diff --git a/src/errors.rs b/src/errors.rs index 8612cf0b..70074b4a 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -51,4 +51,4 @@ impl From for CliError { fn from(e: std::io::Error) -> Self { CliError::Io(e) } -} \ No newline at end of file +} diff --git a/src/flags.rs b/src/flags.rs index 1b846d9c..b0a202c0 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -66,5 +66,8 @@ pub struct DepFlags { } pub fn validate_opt_flag(flag: &str) -> bool { - matches!(flag, "-O0" | "-O1" | "-O2" | "-O3" | "-Os" | "-Oz" | "-Ofast") + matches!( + flag, + "-O0" | "-O1" | "-O2" | "-O3" | "-Os" | "-Oz" | "-Ofast" + ) } diff --git a/src/lib.rs b/src/lib.rs index 6bd63067..b15c5e8c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,10 +10,10 @@ // SPDX-License-Identifier: MPL-2.0 pub mod cli; -pub mod runner; +pub mod errors; pub mod flags; +pub mod runner; pub mod std; -pub mod errors; pub mod version; pub use flags::{DebugFlags, DepFlags, LinkFlags}; diff --git a/src/runner.rs b/src/runner.rs index ace79c9d..721d4ca0 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -11,20 +11,168 @@ use crate::{DebugFlags, DepFlags, LinkFlags}; use ::error::*; -use ::parser::*; +use ::parser::ast::*; +use ::parser::import::*; use ::parser::verification::validate_program; +use ::parser::*; use lexer::Lexer; use llvm::backend::*; use llvm::codegen::*; use std::collections::HashSet; use std::path::{Path, PathBuf}; -use std::{fs, process, process::Command}; use std::process::Stdio; use std::sync::{Arc, Mutex}; -use ::parser::ast::*; -use ::parser::import::*; +use std::{fs, process, process::Command}; + +enum TargetAttr<'a> { + Supported(&'a str), + Unsupported, +} + +fn parse_target_os_attr(line: &str) -> Option> { + let trimmed = line.trim(); + if !trimmed.starts_with("#[target(os=\"") || !trimmed.ends_with("\")]") { + return None; + } + + let start = "#[target(os=\"".len(); + let end = trimmed.len() - 3; // ")]" + let os = &trimmed[start..end]; + if os == "linux" || os == "macos" { + Some(TargetAttr::Supported(os)) + } else { + Some(TargetAttr::Unsupported) + } +} + +fn is_supported_target_item_start(line: &str) -> bool { + fn has_ident_boundary(rest: &str) -> bool { + match rest.chars().next() { + None => true, + Some(c) => !(c.is_ascii_alphanumeric() || c == '_'), + } + } + + let trimmed = line.trim_start(); + for kw in ["fun", "struct", "enum", "const", "static", "type", "proto"] { + if let Some(rest) = trimmed.strip_prefix(kw) { + if has_ident_boundary(rest) { + return true; + } + } + } + + false +} + +fn consume_target_item( + lines: &[&str], + mut idx: usize, + keep: bool, + out: &mut Vec, +) -> usize { + let mut depth: i32 = 0; + let mut seen_open = false; + + while idx < lines.len() { + let line = lines[idx]; + if keep { + out.push(line.to_string()); + } else { + out.push(String::new()); + } + + for ch in line.chars() { + if ch == '{' { + depth += 1; + seen_open = true; + } else if ch == '}' && depth > 0 { + depth -= 1; + } + } + + idx += 1; + + let trimmed = line.trim_end(); + if seen_open && depth == 0 { + break; + } + } + + idx +} -fn parse_wave_tokens_or_exit(file_path: &Path, source: &str, tokens: &[lexer::Token]) -> Vec { +fn preprocess_target_attrs(source: &str) -> String { + let host = std::env::consts::OS; + let lines: Vec<&str> = source.lines().collect(); + let mut out: Vec = Vec::with_capacity(lines.len()); + let mut idx: usize = 0; + + while idx < lines.len() { + let line = lines[idx]; + if let Some(target_attr) = parse_target_os_attr(line) { + // Attribute line is removed for parser compatibility, + // but we keep its line slot to preserve diagnostics. + out.push(String::new()); + idx += 1; + + let keep_item = match target_attr { + TargetAttr::Supported(target_os) => target_os == host, + // Ignore unsupported target values. + TargetAttr::Unsupported => true, + }; + + // Attribute applies to the next top-level item. + // Preserve line count for any leading blanks/comments. + while idx < lines.len() { + let item_line = lines[idx]; + let trimmed = item_line.trim_start(); + + let is_leading_comment = trimmed.starts_with("//") + || trimmed.starts_with("/*") + || trimmed.starts_with('*') + || trimmed.starts_with("*/"); + + if trimmed.is_empty() || is_leading_comment { + if keep_item { + out.push(item_line.to_string()); + } else { + out.push(String::new()); + } + idx += 1; + continue; + } + + if is_supported_target_item_start(trimmed) { + idx = consume_target_item(&lines, idx, keep_item, &mut out); + } else if keep_item { + out.push(item_line.to_string()); + idx += 1; + } else { + out.push(String::new()); + idx += 1; + } + break; + } + continue; + } + + out.push(line.to_string()); + idx += 1; + } + + let mut processed = out.join("\n"); + if source.ends_with('\n') { + processed.push('\n'); + } + processed +} + +fn parse_wave_tokens_or_exit( + file_path: &Path, + source: &str, + tokens: &[lexer::Token], +) -> Vec { parse_syntax_only(tokens).unwrap_or_else(|err| { let (kind, title, code) = match &err { ParseError::Syntax(_) => ( @@ -351,9 +499,7 @@ fn extract_single_quoted_identifiers(message: &str) -> Vec { let candidate = &tail[..end]; let is_symbol = !candidate.is_empty() && candidate.chars().any(is_ident_char) - && candidate - .chars() - .all(|ch| is_ident_char(ch) || ch == '.'); + && candidate.chars().all(|ch| is_ident_char(ch) || ch == '.'); if is_symbol && seen.insert(candidate.to_string()) { out.push(candidate.to_string()); @@ -401,13 +547,17 @@ fn infer_codegen_source_location(source: &str, panic_message: &str) -> Option { - let unit = local_import_unit_with_config(&module, already, base_dir, import_config)?; + let unit = + local_import_unit_with_config(&module, already, base_dir, import_config)?; if unit.ast.is_empty() { continue; @@ -563,6 +714,139 @@ fn expand_imports_for_codegen( expand_from_dir(base_dir, ast, &mut already, import_config) } +fn materialize_output_path( + generated_path: &str, + output: Option<&Path>, + file_path: &Path, + source: &str, + stage: &str, +) -> String { + let Some(output) = output else { + return generated_path.to_string(); + }; + + if output.as_os_str().is_empty() { + WaveError::new( + WaveErrorKind::FileWriteError(file_path.display().to_string()), + "output path must not be empty", + file_path.display().to_string(), + 0, + 0, + ) + .with_code("E1005") + .with_source_code(source.to_string()) + .with_context(stage) + .with_help("pass a valid path to -o ") + .display(); + process::exit(1); + } + + if Path::new(generated_path) == output { + return generated_path.to_string(); + } + + if let Some(parent) = output.parent() { + if !parent.as_os_str().is_empty() && !parent.exists() { + if let Err(err) = fs::create_dir_all(parent) { + WaveError::new( + WaveErrorKind::FileWriteError(output.display().to_string()), + format!( + "failed to create output directory `{}`: {}", + parent.display(), + err + ), + file_path.display().to_string(), + 0, + 0, + ) + .with_code("E1005") + .with_source_code(source.to_string()) + .with_context(stage) + .with_help("check path permissions for the output directory") + .display(); + process::exit(1); + } + } + } + + if let Err(err) = fs::copy(generated_path, output) { + WaveError::new( + WaveErrorKind::FileWriteError(output.display().to_string()), + format!( + "failed to write output `{}` from `{}`: {}", + output.display(), + generated_path, + err + ), + file_path.display().to_string(), + 0, + 0, + ) + .with_code("E1005") + .with_source_code(source.to_string()) + .with_context(stage) + .with_help("check output path permissions and available disk space") + .display(); + process::exit(1); + } + + output.display().to_string() +} + +fn resolve_output_target( + default_output: &str, + output: Option<&Path>, + file_path: &Path, + source: &str, + stage: &str, +) -> String { + let Some(output) = output else { + return default_output.to_string(); + }; + + if output.as_os_str().is_empty() { + WaveError::new( + WaveErrorKind::FileWriteError(file_path.display().to_string()), + "output path must not be empty", + file_path.display().to_string(), + 0, + 0, + ) + .with_code("E1005") + .with_source_code(source.to_string()) + .with_context(stage) + .with_help("pass a valid path to -o ") + .display(); + process::exit(1); + } + + if let Some(parent) = output.parent() { + if !parent.as_os_str().is_empty() && !parent.exists() { + if let Err(err) = fs::create_dir_all(parent) { + WaveError::new( + WaveErrorKind::FileWriteError(output.display().to_string()), + format!( + "failed to create output directory `{}`: {}", + parent.display(), + err + ), + file_path.display().to_string(), + 0, + 0, + ) + .with_code("E1005") + .with_source_code(source.to_string()) + .with_context(stage) + .with_help("check path permissions for the output directory") + .display(); + process::exit(1); + } + } + } + + output.display().to_string() +} + pub(crate) unsafe fn run_wave_file( file_path: &Path, opt_flag: &str, @@ -570,7 +854,7 @@ pub(crate) unsafe fn run_wave_file( link: &LinkFlags, dep: &DepFlags, ) { - let code = match fs::read_to_string(file_path) { + let raw_code = match fs::read_to_string(file_path) { Ok(c) => c, Err(_) => { WaveError::new( @@ -585,6 +869,7 @@ pub(crate) unsafe fn run_wave_file( process::exit(1); } }; + let code = preprocess_target_attrs(&raw_code); let mut lexer = Lexer::new_with_file(&code, file_path.display().to_string()); let tokens = lexer.tokenize().unwrap_or_else(|e| { @@ -656,12 +941,7 @@ pub(crate) unsafe fn run_wave_file( let exe_patch = format!("target/{}", file_stem); if let Err((msg, loc)) = run_panic_guarded(|| { - link_objects( - &[object_patch.clone()], - &exe_patch, - &link.libs, - &link.paths, - ); + link_objects(&[object_patch.clone()], &exe_patch, &link.libs, &link.paths); }) { emit_codegen_panic_and_exit(file_path, &code, "native-link", msg, loc); } @@ -686,8 +966,9 @@ pub(crate) unsafe fn object_build_wave_file( opt_flag: &str, debug: &DebugFlags, dep: &DepFlags, + output: Option<&Path>, ) -> String { - let code = fs::read_to_string(file_path).unwrap_or_else(|_| { + let raw_code = fs::read_to_string(file_path).unwrap_or_else(|_| { WaveError::new( WaveErrorKind::FileReadError(file_path.display().to_string()), format!("failed to read file `{}`", file_path.display()), @@ -695,9 +976,10 @@ pub(crate) unsafe fn object_build_wave_file( 0, 0, ) - .display(); + .display(); process::exit(1); }); + let code = preprocess_target_attrs(&raw_code); let mut lexer = Lexer::new_with_file(&code, file_path.display().to_string()); let tokens = lexer.tokenize().unwrap_or_else(|e| { @@ -739,12 +1021,20 @@ pub(crate) unsafe fn object_build_wave_file( } let file_stem = file_path.file_stem().unwrap().to_str().unwrap(); - let object_path = match run_panic_guarded(|| compile_ir_to_object(&ir, file_stem, opt_flag)) { - Ok(path) => path, - Err((msg, loc)) => { - emit_codegen_panic_and_exit(file_path, &code, "object-emission", msg, loc) - } - }; + let generated_object_path = + match run_panic_guarded(|| compile_ir_to_object(&ir, file_stem, opt_flag)) { + Ok(path) => path, + Err((msg, loc)) => { + emit_codegen_panic_and_exit(file_path, &code, "object-emission", msg, loc) + } + }; + let object_path = materialize_output_path( + &generated_object_path, + output, + file_path, + &code, + "object-emission", + ); if debug.mc { println!("\n===== MACHINE CODE PATH ====="); @@ -772,21 +1062,19 @@ pub(crate) unsafe fn build_wave_file( debug: &DebugFlags, link: &LinkFlags, dep: &DepFlags, + output: Option<&Path>, ) { - let object_path = object_build_wave_file(file_path, opt_flag, debug, dep); + let object_path = object_build_wave_file(file_path, opt_flag, debug, dep, None); let file_stem = file_path.file_stem().unwrap().to_str().unwrap(); - let exe_path = format!("target/{}", file_stem); + let default_exe_path = format!("target/{}", file_stem); + let source = fs::read_to_string(file_path).unwrap_or_default(); + let exe_path = + resolve_output_target(&default_exe_path, output, file_path, &source, "native-link"); if let Err((msg, loc)) = run_panic_guarded(|| { - link_objects( - &[object_path], - &exe_path, - &link.libs, - &link.paths, - ); + link_objects(&[object_path], &exe_path, &link.libs, &link.paths); }) { - let source = fs::read_to_string(file_path).unwrap_or_default(); emit_codegen_panic_and_exit(file_path, &source, "native-link", msg, loc); } @@ -795,54 +1083,3 @@ pub(crate) unsafe fn build_wave_file( println!("{}", exe_path); } } - -pub(crate) unsafe fn img_wave_file(file_path: &Path, dep: &DepFlags) { - let code = fs::read_to_string(file_path).expect("Failed to read file"); - - let mut lexer = Lexer::new_with_file(&code, file_path.display().to_string()); - let tokens = lexer.tokenize().unwrap_or_else(|e| { - e.display(); - process::exit(1); - }); - - let ast = parse_wave_tokens_or_exit(file_path, &code, &tokens); - let import_config = build_import_config(dep); - let ast = expand_imports_for_codegen(file_path, ast, &import_config).unwrap_or_else(|e| { - e.display(); - process::exit(1); - }); - validate_wave_ast_or_exit(file_path, &code, &ast); - - // println!("{}\n", code); - // for token in tokens { - // println!("{:?}", token); - // } - // println!("AST:\n{:#?}", ast); - - let ir = match run_panic_guarded(|| unsafe { generate_ir(&ast, "") }) { - Ok(ir) => ir, - Err((msg, loc)) => { - emit_codegen_panic_and_exit(file_path, &code, "llvm-ir-generation", msg, loc) - } - }; - let path = Path::new(file_path); - let file_stem = path.file_stem().unwrap().to_str().unwrap(); - let machine_code_path = match run_panic_guarded(|| compile_ir_to_img_code(&ir, file_stem)) { - Ok(path) => path, - Err((msg, loc)) => { - emit_codegen_panic_and_exit(file_path, &code, "image-emission", msg, loc) - } - }; - - if machine_code_path.is_empty() { - eprintln!("Failed to generate machine code"); - return; - } - - Command::new("qemu-system-x86_64") - .args(&["-drive", &format!("file={},format=raw", machine_code_path)]) - .status() - .expect("Failed to run QEMU"); - - // println!("Generated LLVM IR:\n{}", ir); -} diff --git a/src/std.rs b/src/std.rs index 9f75b424..4a02bc64 100644 --- a/src/std.rs +++ b/src/std.rs @@ -10,10 +10,10 @@ // SPDX-License-Identifier: MPL-2.0 use crate::errors::CliError; -use std::{env, fs}; use std::path::{Path, PathBuf}; use std::process::Command; use std::time::{SystemTime, UNIX_EPOCH}; +use std::{env, fs}; pub fn std_install() -> Result<(), CliError> { install_or_update_std(false) @@ -58,10 +58,12 @@ fn install_std_from_wave_repo_sparse(stage_dir: &Path) -> Result<(), CliError> { run_cmd( Command::new("git") .arg("clone") - .arg("--depth").arg("1") + .arg("--depth") + .arg("1") .arg("--filter=blob:none") .arg("--sparse") - .arg("--branch").arg(reference) + .arg("--branch") + .arg(reference) .arg(repo) .arg(&tmp), "git clone", @@ -69,7 +71,8 @@ fn install_std_from_wave_repo_sparse(stage_dir: &Path) -> Result<(), CliError> { run_cmd( Command::new("git") - .arg("-C").arg(&tmp) + .arg("-C") + .arg(&tmp) .arg("sparse-checkout") .arg("set") .arg("std"), @@ -90,7 +93,9 @@ fn install_std_from_wave_repo_sparse(stage_dir: &Path) -> Result<(), CliError> { .map_err(|e| CliError::CommandFailed(format!("invalid manifest.json: {}", e)))?; if manifest.get_str("name") != Some("std") { - return Err(CliError::CommandFailed("manifest.json name != 'std'".to_string())); + return Err(CliError::CommandFailed( + "manifest.json name != 'std'".to_string(), + )); } copy_dir_all(&src_std, stage_dir)?; @@ -148,8 +153,11 @@ fn run_cmd(cmd: &mut Command, label: &str) -> Result<(), CliError> { } fn make_tmp_dir(prefix: &str) -> Result { - let t = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos(); + let t = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); let p = env::temp_dir().join(format!("{}-{}", prefix, t)); fs::create_dir_all(&p)?; Ok(p) -} \ No newline at end of file +} diff --git a/std/env/cwd.wave b/std/env/cwd.wave index 1e994f02..4c3d7d4c 100644 --- a/std/env/cwd.wave +++ b/std/env/cwd.wave @@ -9,19 +9,26 @@ // // SPDX-License-Identifier: MPL-2.0 -import("std::sys::linux::syscall"); +import("std::sys::fs"); fun env_getcwd(dst: ptr, cap: i64) -> i64 { - // syscall: getcwd (79) - return syscall2(79, dst, cap); + var r: i64 = getcwd(dst, cap); + if (r <= 0) { + return -1; + } + + var n: i64 = 0; + while (dst[n] != 0) { + n += 1; + } + + return n; } fun env_chdir(path: str) -> i64 { - // syscall: chdir (80) - return syscall1(80, path); + return chdir(path); } fun env_access(path: str, mode: i32) -> i64 { - // syscall: access (21) - return syscall2(21, path, mode); + return access(path, mode); } diff --git a/std/env/environ.wave b/std/env/environ.wave index 8449b34d..24d2fa28 100644 --- a/std/env/environ.wave +++ b/std/env/environ.wave @@ -9,7 +9,7 @@ // // SPDX-License-Identifier: MPL-2.0 -import("std::sys::linux::fs"); +import("std::sys::env"); import("std::env::parse"); fun env_get(name: str, dst: ptr, dst_cap: i64) -> i64 { @@ -23,14 +23,8 @@ fun env_get(name: str, dst: ptr, dst_cap: i64) -> i64 { return -3; } - var fd: i64 = open("/proc/self/environ", 0, 0); - if (fd < 0) { - return fd; - } - var raw: array; - var n: i64 = read(fd, &raw[0], 32768); - close(fd); + var n: i64 = env_read(&raw[0], 32768); if (n <= 0) { return -2; diff --git a/std/libc/errno.wave b/std/libc/errno.wave index 3ec3de09..80a6b9d2 100644 --- a/std/libc/errno.wave +++ b/std/libc/errno.wave @@ -9,4 +9,4 @@ // // SPDX-License-Identifier: MPL-2.0 -extern(c) var errno: i32; \ No newline at end of file +extern(c) fun __errno_location() -> ptr; diff --git a/std/libc/time.wave b/std/libc/time.wave index 27ddcdb3..11a73fd7 100644 --- a/std/libc/time.wave +++ b/std/libc/time.wave @@ -17,12 +17,10 @@ struct TimeSpec { const CLOCK_REALTIME: i32 = 0; const CLOCK_MONOTONIC: i32 = 1; -extern(c) fun nanosleep( - req: ptr, - rem: ptr -) -> i32; - -extern(c) fun clock_gettime(clock_id: i32, tp: ptr) -> i32; -extern(c) fun time(out: ptr) -> i64; -extern(c) fun sleep(seconds: i32) -> i32; -extern(c) fun usleep(usec: i32) -> i32; +extern(c) { + fun nanosleep(ptr, ptr) -> i32; + fun clock_gettime(i32, ptr) -> i32; + fun c_time(ptr) -> i64 "time"; + fun sleep(i32) -> i32; + fun usleep(i32) -> i32; +} diff --git a/std/math/trig.wave b/std/math/trig.wave new file mode 100644 index 00000000..acd9608d --- /dev/null +++ b/std/math/trig.wave @@ -0,0 +1,84 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 + +const MATH_PI_F64: f64 = 3.141592653589793; +const MATH_TWO_PI_F64: f64 = 6.283185307179586; + +fun abs_f64(x: f64) -> f64 { + if (x < 0.0) { + return -x; + } + + return x; +} + +fun wrap_angle_pi_f64(x: f64) -> f64 { + var k: i64 = (x / MATH_TWO_PI_F64) as i64; + var y: f64 = x - (k as f64) * MATH_TWO_PI_F64; + + if (y > MATH_PI_F64) { + y -= MATH_TWO_PI_F64; + } + + if (y < -MATH_PI_F64) { + y += MATH_TWO_PI_F64; + } + + return y; +} + +fun sin_f64(x0: f64) -> f64 { + var x: f64 = wrap_angle_pi_f64(x0); + var x2: f64 = x * x; + var x3: f64 = x * x2; + var x5: f64 = x3 * x2; + var x7: f64 = x5 * x2; + var x9: f64 = x7 * x2; + + return x + - (x3 / 6.0) + + (x5 / 120.0) + - (x7 / 5040.0) + + (x9 / 362880.0); +} + +fun cos_f64(x0: f64) -> f64 { + var x: f64 = wrap_angle_pi_f64(x0); + var x2: f64 = x * x; + var x4: f64 = x2 * x2; + var x6: f64 = x4 * x2; + var x8: f64 = x6 * x2; + + return 1.0 + - (x2 / 2.0) + + (x4 / 24.0) + - (x6 / 720.0) + + (x8 / 40320.0); +} + +fun sqrt_f64(x: f64) -> f64 { + if (x <= 0.0) { + return 0.0; + } + + var g: f64 = x; + if (g < 1.0) { + g = 1.0; + } + + var i: i32 = 0; + while (i < 16) { + g = 0.5 * (g + (x / g)); + i += 1; + } + + return g; +} diff --git a/std/mem/alloc.wave b/std/mem/alloc.wave index 78666bc2..b412a56f 100644 --- a/std/mem/alloc.wave +++ b/std/mem/alloc.wave @@ -9,33 +9,10 @@ // // SPDX-License-Identifier: MPL-2.0 -fun mem_alloc(size: i64) -> ptr { - if (size <= 0) { - return null; - } - - var prot: i64 = 1 | 2; - var flags: i64 = 2 | 32; - var ret: ptr; +import("std::sys::memory"); - asm { - "syscall" - in("rax") 9 - in("rdi") 0 - in("rsi") size - in("rdx") prot - in("r10") flags - in("r8") -1 - in("r9") 0 - out("rax") ret - } - - // Linux syscall returns negative errno on failure. - if ((ret as i64) < 0) { - return null; - } - - return ret; +fun mem_alloc(size: i64) -> ptr { + return sys_alloc(size); } fun mem_alloc_zeroed(size: i64) -> ptr { @@ -53,19 +30,5 @@ fun mem_alloc_zeroed(size: i64) -> ptr { } fun mem_free(p: ptr, size: i64) -> i64 { - if (p == null || size <= 0) { - return 0; - } - - var ret: i64; - - asm { - "syscall" - in("rax") 11 - in("rdi") p - in("rsi") size - out("rax") ret - } - - return ret; + return sys_free(p, size); } diff --git a/std/net/tcp.wave b/std/net/tcp.wave index e719faf5..60e61d24 100644 --- a/std/net/tcp.wave +++ b/std/net/tcp.wave @@ -14,13 +14,13 @@ // ======================================================= // // Stream-based TCP API built on top of -// std::sys::linux::socket +// std::sys::socket // // Blocking, minimal, synchronous TCP abstraction. // ======================================================= -import("std::sys::linux::socket"); -import("std::sys::linux::fs"); +import("std::sys::socket"); +import("std::sys::fs"); // ----------------------- @@ -72,7 +72,7 @@ fun htonl(x: i32) -> i32 { fun _to_sockaddr(addr: TcpAddr) -> SockAddrIn { return SockAddrIn { - family: 2, + family: AF_INET as i16, port: addr.port, addr: addr.ip, zero: [0,0,0,0,0,0,0,0] @@ -86,13 +86,13 @@ fun _to_sockaddr(addr: TcpAddr) -> SockAddrIn { fun tcp_bind(port: i16) -> TcpListener { let fd: i64 = socket( - 2, - 1, - 6 + AF_INET, + SOCK_STREAM, + IPPROTO_TCP ); let addr: SockAddrIn = SockAddrIn { - family: 2, + family: AF_INET as i16, port: htons(port), addr: 0, zero: [0,0,0,0,0,0,0,0] @@ -110,7 +110,7 @@ fun tcp_accept(listener: TcpListener) -> TcpStream { } fun tcp_close_listener(listener: TcpListener) { - shutdown(listener.fd, 2); + shutdown(listener.fd, SHUT_RDWR); close(listener.fd); } @@ -121,9 +121,9 @@ fun tcp_close_listener(listener: TcpListener) { fun tcp_connect(addr: TcpAddr) -> TcpStream { let fd: i64 = socket( - 2, - 1, - 6 + AF_INET, + SOCK_STREAM, + IPPROTO_TCP ); let sa: SockAddrIn = _to_sockaddr(addr); @@ -141,6 +141,6 @@ fun tcp_write(stream: TcpStream, buf: ptr, len: i64) -> i64 { } fun tcp_close(stream: TcpStream) { - shutdown(stream.fd, 2); + shutdown(stream.fd, SHUT_RDWR); close(stream.fd); } diff --git a/std/net/udp.wave b/std/net/udp.wave index 377355e3..eb01b1a2 100644 --- a/std/net/udp.wave +++ b/std/net/udp.wave @@ -14,15 +14,15 @@ // ======================================================= // // Datagram-based UDP API built on top of -// std::sys::linux::socket +// std::sys::socket // // No connection abstraction. // No async. // No buffering. // ======================================================= -import("std::sys::linux::socket"); -import("std::sys::linux::fs"); +import("std::sys::socket"); +import("std::sys::fs"); // ----------------------- @@ -70,7 +70,7 @@ fun htonl(x: i32) -> i32 { fun _to_sockaddr(addr: UdpAddr) -> SockAddrIn { return SockAddrIn { - family: 2, + family: AF_INET as i16, port: addr.port, addr: addr.ip, zero: [0,0,0,0,0,0,0,0] @@ -84,13 +84,13 @@ fun _to_sockaddr(addr: UdpAddr) -> SockAddrIn { fun udp_bind(port: i16) -> UdpSocket { let fd: i64 = socket( - 2, - 2, - 17 + AF_INET, + SOCK_DGRAM, + IPPROTO_UDP ); let addr: SockAddrIn = SockAddrIn { - family: 2, + family: AF_INET as i16, port: htons(port), addr: 0, zero: [0,0,0,0,0,0,0,0] @@ -102,7 +102,7 @@ fun udp_bind(port: i16) -> UdpSocket { } fun udp_close(sock: UdpSocket) { - shutdown(sock.fd, 2); + shutdown(sock.fd, SHUT_RDWR); close(sock.fd); } diff --git a/std/sys/env.wave b/std/sys/env.wave new file mode 100644 index 00000000..dfceb61d --- /dev/null +++ b/std/sys/env.wave @@ -0,0 +1,20 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 + +// ======================================================= +// Environment sys dispatcher +// ======================================================= + +#[target(os="linux")] +import("std::sys::linux::env"); + +#[target(os="macos")] +import("std::sys::macos::env"); diff --git a/std/sys/fs.wave b/std/sys/fs.wave new file mode 100644 index 00000000..6719d1d2 --- /dev/null +++ b/std/sys/fs.wave @@ -0,0 +1,24 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 + +// ======================================================= +// Filesystem sys dispatcher +// ======================================================= +// +// This layer selects the OS-specific fs implementation. +// Keep target attributes only in this dispatch module. +// ======================================================= + +#[target(os="linux")] +import("std::sys::linux::fs"); + +#[target(os="macos")] +import("std::sys::macos::fs"); diff --git a/std/sys/linux/env.wave b/std/sys/linux/env.wave new file mode 100644 index 00000000..6ccd9141 --- /dev/null +++ b/std/sys/linux/env.wave @@ -0,0 +1,27 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 + +import("std::sys::linux::fs"); + +fun env_read(buf: ptr, cap: i64) -> i64 { + if (cap <= 0) { + return -22; + } + + var fd: i64 = open("/proc/self/environ", 0, 0); + if (fd < 0) { + return fd; + } + + var n: i64 = read(fd, buf, cap); + close(fd); + return n; +} diff --git a/std/sys/linux/fs.wave b/std/sys/linux/fs.wave index f4fe83fb..293383eb 100644 --- a/std/sys/linux/fs.wave +++ b/std/sys/linux/fs.wave @@ -52,6 +52,21 @@ fun write(fd: i64, buf: ptr, len: i64) -> i64 { return syscall3(1, fd, buf as i64, len); } +fun getcwd(buf: ptr, size: i64) -> i64 { + // syscall: getcwd (79) + return syscall2(79, buf as i64, size); +} + +fun chdir(path: str) -> i64 { + // syscall: chdir (80) + return syscall1(80, path as i64); +} + +fun access(path: str, mode: i32) -> i64 { + // syscall: access (21) + return syscall2(21, path as i64, mode as i64); +} + // ----------------------- // seek diff --git a/std/sys/linux/memory.wave b/std/sys/linux/memory.wave index 7f369b0a..3f32e3d1 100644 --- a/std/sys/linux/memory.wave +++ b/std/sys/linux/memory.wave @@ -20,6 +20,11 @@ import("std::sys::linux::syscall"); +const PROT_READ: i32 = 1; +const PROT_WRITE: i32 = 2; +const MAP_PRIVATE: i32 = 2; +const MAP_ANONYMOUS: i32 = 32; + // ----------------------- // mmap / munmap @@ -59,3 +64,32 @@ fun brk(addr: ptr) -> ptr { // syscall: brk (12) return syscall1(12, addr as i64) as ptr; } + +fun sys_alloc(size: i64) -> ptr { + if (size <= 0) { + return null; + } + + var p: ptr = mmap( + null, + size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, + 0 + ); + + if ((p as i64) < 0) { + return null; + } + + return p as ptr; +} + +fun sys_free(p: ptr, size: i64) -> i64 { + if (p == null || size <= 0) { + return 0; + } + + return munmap(p as ptr, size); +} diff --git a/std/sys/linux/tty.wave b/std/sys/linux/tty.wave new file mode 100644 index 00000000..ef70f48e --- /dev/null +++ b/std/sys/linux/tty.wave @@ -0,0 +1,127 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 + +// ======================================================= +// Linux x86_64 tty helpers +// ======================================================= +// +// Raw terminal helpers built on top of syscall.wave. +// No libc dependency. +// ======================================================= + +import("std::sys::linux::syscall"); + +const TTY_SYS_IOCTL: i64 = 16; +const TTY_SYS_FCNTL: i64 = 72; + +const TTY_TCGETS: i64 = 0x5401; +const TTY_TCSETS: i64 = 0x5402; +const TTY_TCSETSW: i64 = 0x5403; +const TTY_TCSETSF: i64 = 0x5404; + +const TTY_TCSANOW: i32 = 0; +const TTY_TCSADRAIN: i32 = 1; +const TTY_TCSAFLUSH: i32 = 2; + +const TTY_F_GETFL: i32 = 3; +const TTY_F_SETFL: i32 = 4; +const TTY_O_NONBLOCK: i32 = 2048; + +const TTY_ICANON: u32 = 2; +const TTY_ECHO: u32 = 8; +const TTY_VTIME_IDX: i32 = 5; +const TTY_VMIN_IDX: i32 = 6; + +struct Termios { + c_iflag: u32; + c_oflag: u32; + c_cflag: u32; + c_lflag: u32; + c_line: u8; + c_cc: array; + c_ispeed: u32; + c_ospeed: u32; +} + +struct TtyRawState { + term: Termios; + flags: i32; + active: i32; +} + +fun tty_getattr(fd: i32, t: ptr) -> i64 { + return syscall3(TTY_SYS_IOCTL, fd as i64, TTY_TCGETS, t as i64); +} + +fun tty_setattr(fd: i32, action: i32, t: ptr) -> i64 { + var req: i64 = TTY_TCSETS; + + if (action == TTY_TCSADRAIN) { + req = TTY_TCSETSW; + } else if (action == TTY_TCSAFLUSH) { + req = TTY_TCSETSF; + } + + return syscall3(TTY_SYS_IOCTL, fd as i64, req, t as i64); +} + +fun tty_getfl(fd: i32) -> i64 { + return syscall3(TTY_SYS_FCNTL, fd as i64, TTY_F_GETFL as i64, 0); +} + +fun tty_setfl(fd: i32, flags: i32) -> i64 { + return syscall3(TTY_SYS_FCNTL, fd as i64, TTY_F_SETFL as i64, flags as i64); +} + +fun tty_enable_raw_nonblock(fd: i32, st: ptr) -> i64 { + deref st.active = 0; + + var orig: Termios; + if (tty_getattr(fd, &orig) < 0) { + return -1; + } + + var raw: Termios = orig; + raw.c_lflag = raw.c_lflag & ~(TTY_ICANON | TTY_ECHO); + raw.c_cc[6] = 0; + raw.c_cc[5] = 0; + + if (tty_setattr(fd, TTY_TCSANOW, &raw) < 0) { + return -1; + } + + var flags_raw: i64 = tty_getfl(fd); + if (flags_raw < 0) { + tty_setattr(fd, TTY_TCSANOW, &orig); + return -1; + } + + var old_flags: i32 = flags_raw as i32; + if (tty_setfl(fd, old_flags | TTY_O_NONBLOCK) < 0) { + tty_setattr(fd, TTY_TCSANOW, &orig); + return -1; + } + + deref st.term = orig; + deref st.flags = old_flags; + deref st.active = 1; + return 0; +} + +fun tty_restore(fd: i32, st: ptr) { + if (deref st.active == 0) { + return; + } + + var orig: Termios = deref st.term; + tty_setattr(fd, TTY_TCSANOW, &orig); + tty_setfl(fd, deref st.flags); +} diff --git a/std/sys/macos/env.wave b/std/sys/macos/env.wave new file mode 100644 index 00000000..c2bbde51 --- /dev/null +++ b/std/sys/macos/env.wave @@ -0,0 +1,21 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 + +// ENOSYS-style return for not implemented path. +const ERR_NOT_IMPLEMENTED: i64 = -38; + +fun env_read(buf: ptr, cap: i64) -> i64 { + if (buf == null || cap <= 0) { + return -22; + } + + return ERR_NOT_IMPLEMENTED; +} diff --git a/std/sys/macos/fs.wave b/std/sys/macos/fs.wave new file mode 100644 index 00000000..c24cf87c --- /dev/null +++ b/std/sys/macos/fs.wave @@ -0,0 +1,85 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 + +// ======================================================= +// macOS filesystem syscalls +// ======================================================= + +import("std::sys::macos::syscall"); + +struct Stat { + dev: i64; + ino: i64; + nlink: i64; + mode: i32; + uid: i32; + gid: i32; + pad0: i32; + rdev: i64; + size: i64; + blksize: i64; + blocks: i64; + atime: i64; + mtime: i64; + ctime: i64; +} + +fun open(path: str, flags: i32, mode: i32) -> i64 { + return syscall3(5, path as i64, flags as i64, mode as i64); +} + +fun close(fd: i64) -> i64 { + return syscall1(6, fd); +} + +fun read(fd: i64, buf: ptr, len: i64) -> i64 { + return syscall3(3, fd, buf as i64, len); +} + +fun write(fd: i64, buf: ptr, len: i64) -> i64 { + return syscall3(4, fd, buf as i64, len); +} + +fun getcwd(buf: ptr, size: i64) -> i64 { + return syscall2(326, buf as i64, size); +} + +fun chdir(path: str) -> i64 { + return syscall1(12, path as i64); +} + +fun access(path: str, mode: i32) -> i64 { + return syscall2(33, path as i64, mode as i64); +} + +fun lseek(fd: i64, offset: i64, whence: i32) -> i64 { + return syscall3(199, fd, offset, whence as i64); +} + +fun unlink(path: str) -> i64 { + return syscall1(10, path as i64); +} + +fun mkdir(path: str, mode: i32) -> i64 { + return syscall2(136, path as i64, mode as i64); +} + +fun rmdir(path: str) -> i64 { + return syscall1(137, path as i64); +} + +fun stat(path: str, st: ptr) -> i64 { + return syscall2(338, path as i64, st as i64); +} + +fun fstat(fd: i64, st: ptr) -> i64 { + return syscall2(339, fd, st as i64); +} diff --git a/std/sys/macos/memory.wave b/std/sys/macos/memory.wave new file mode 100644 index 00000000..68fd8cb9 --- /dev/null +++ b/std/sys/macos/memory.wave @@ -0,0 +1,77 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 + +// ======================================================= +// macOS memory syscalls +// ======================================================= + +import("std::sys::macos::syscall"); + +const PROT_READ: i32 = 1; +const PROT_WRITE: i32 = 2; +const MAP_PRIVATE: i32 = 2; +const MAP_ANON: i32 = 0x1000; + +fun mmap( + addr: ptr, + length: i64, + prot: i32, + flags: i32, + fd: i64, + offset: i64 +) -> ptr { + return syscall6( + 197, + addr as i64, + length, + prot as i64, + flags as i64, + fd, + offset + ) as ptr; +} + +fun munmap(addr: ptr, length: i64) -> i64 { + return syscall2(73, addr as i64, length); +} + +fun brk(addr: ptr) -> ptr { + return syscall1(17, addr as i64) as ptr; +} + +fun sys_alloc(size: i64) -> ptr { + if (size <= 0) { + return null; + } + + var p: ptr = mmap( + null, + size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, + -1, + 0 + ); + + if ((p as i64) < 0) { + return null; + } + + return p as ptr; +} + +fun sys_free(p: ptr, size: i64) -> i64 { + if (p == null || size <= 0) { + return 0; + } + + return munmap(p as ptr, size); +} diff --git a/std/sys/macos/process.wave b/std/sys/macos/process.wave new file mode 100644 index 00000000..6722eb6d --- /dev/null +++ b/std/sys/macos/process.wave @@ -0,0 +1,49 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 + +// ======================================================= +// macOS process syscalls +// ======================================================= + +import("std::sys::macos::syscall"); + +fun exit(code: i32) -> ! { + syscall1(1, code as i64); + while (true) { } +} + +fun getpid() -> i64 { + return syscall0(20); +} + +fun getppid() -> i64 { + return syscall0(39); +} + +fun fork() -> i64 { + return syscall0(2); +} + +fun execve( + path: str, + argv: ptr>, + envp: ptr> +) -> i64 { + return syscall3(59, path as i64, argv as i64, envp as i64); +} + +fun waitpid(pid: i64, status: ptr, options: i32) -> i64 { + return syscall4(7, pid, status as i64, options as i64, 0); +} + +fun kill(pid: i64, sig: i32) -> i64 { + return syscall2(37, pid, sig as i64); +} diff --git a/std/sys/macos/socket.wave b/std/sys/macos/socket.wave new file mode 100644 index 00000000..4bc61622 --- /dev/null +++ b/std/sys/macos/socket.wave @@ -0,0 +1,123 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 + +// ======================================================= +// macOS socket syscalls +// ======================================================= +// +// Raw socket layer for macOS built on syscall.wave. +// ======================================================= + +import("std::sys::macos::syscall"); + +const AF_INET: i32 = 2; +const AF_INET6: i32 = 30; + +const SOCK_STREAM: i32 = 1; +const SOCK_DGRAM: i32 = 2; + +const IPPROTO_IP: i32 = 0; +const IPPROTO_TCP: i32 = 6; +const IPPROTO_UDP: i32 = 17; + +const SHUT_RD: i32 = 0; +const SHUT_WR: i32 = 1; +const SHUT_RDWR: i32 = 2; + +const SOL_SOCKET: i32 = 0xFFFF; +const SO_REUSEADDR: i32 = 0x0004; + +fun socket(domain: i32, ty: i32, protocol: i32) -> i64 { + return syscall3(97, domain as i64, ty as i64, protocol as i64); +} + +fun bind(fd: i64, addr: ptr, len: i32) -> i64 { + return syscall3(104, fd, addr as i64, len as i64); +} + +fun listen(fd: i64, backlog: i32) -> i64 { + return syscall2(106, fd, backlog as i64); +} + +fun accept(fd: i64, addr: ptr, len: ptr) -> i64 { + return syscall3(30, fd, addr as i64, len as i64); +} + +fun connect(fd: i64, addr: ptr, len: i32) -> i64 { + return syscall3(98, fd, addr as i64, len as i64); +} + +fun shutdown(fd: i64, how: i32) -> i64 { + return syscall2(134, fd, how as i64); +} + +fun setsockopt( + fd: i64, + level: i32, + optname: i32, + optval: ptr, + optlen: i32 +) -> i64 { + return syscall5( + 105, + fd, + level as i64, + optname as i64, + optval as i64, + optlen as i64 + ); +} + +fun send(fd: i64, buf: ptr, len: i64, flags: i32) -> i64 { + return sendto(fd, buf, len, flags, null, 0); +} + +fun recv(fd: i64, buf: ptr, len: i64, flags: i32) -> i64 { + return recvfrom(fd, buf, len, flags, null, null); +} + +fun sendto( + fd: i64, + buf: ptr, + len: i64, + flags: i32, + addr: ptr, + addrlen: i32 +) -> i64 { + return syscall6( + 133, + fd, + buf as i64, + len, + flags as i64, + addr as i64, + addrlen as i64 + ); +} + +fun recvfrom( + fd: i64, + buf: ptr, + len: i64, + flags: i32, + addr: ptr, + addrlen: ptr +) -> i64 { + return syscall6( + 29, + fd, + buf as i64, + len, + flags as i64, + addr as i64, + addrlen as i64 + ); +} diff --git a/std/sys/macos/syscall.wave b/std/sys/macos/syscall.wave new file mode 100644 index 00000000..45c8da71 --- /dev/null +++ b/std/sys/macos/syscall.wave @@ -0,0 +1,119 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 + +// ======================================================= +// macOS x86_64 syscall entry helpers +// ======================================================= +// +// Raw syscall helpers for Darwin. +// Return values follow kernel convention: +// negative return value = -errno +// ======================================================= + +const MACOS_SYSCALL_BASE: i64 = 0x2000000; + +fun syscall0(id: i64) -> i64 { + var ret: i64; + var sysno: i64 = MACOS_SYSCALL_BASE + id; + asm { + "syscall" + in("rax") sysno + out("rax") ret + } + return ret; +} + +fun syscall1(id: i64, a1: i64) -> i64 { + var ret: i64; + var sysno: i64 = MACOS_SYSCALL_BASE + id; + asm { + "syscall" + in("rax") sysno + in("rdi") a1 + out("rax") ret + } + return ret; +} + +fun syscall2(id: i64, a1: i64, a2: i64) -> i64 { + var ret: i64; + var sysno: i64 = MACOS_SYSCALL_BASE + id; + asm { + "syscall" + in("rax") sysno + in("rdi") a1 + in("rsi") a2 + out("rax") ret + } + return ret; +} + +fun syscall3(id: i64, a1: i64, a2: i64, a3: i64) -> i64 { + var ret: i64; + var sysno: i64 = MACOS_SYSCALL_BASE + id; + asm { + "syscall" + in("rax") sysno + in("rdi") a1 + in("rsi") a2 + in("rdx") a3 + out("rax") ret + } + return ret; +} + +fun syscall4(id: i64, a1: i64, a2: i64, a3: i64, a4: i64) -> i64 { + var ret: i64; + var sysno: i64 = MACOS_SYSCALL_BASE + id; + asm { + "syscall" + in("rax") sysno + in("rdi") a1 + in("rsi") a2 + in("rdx") a3 + in("r10") a4 + out("rax") ret + } + return ret; +} + +fun syscall5(id: i64, a1: i64, a2: i64, a3: i64, a4: i64, a5: i64) -> i64 { + var ret: i64; + var sysno: i64 = MACOS_SYSCALL_BASE + id; + asm { + "syscall" + in("rax") sysno + in("rdi") a1 + in("rsi") a2 + in("rdx") a3 + in("r10") a4 + in("r8") a5 + out("rax") ret + } + return ret; +} + +fun syscall6(id: i64, a1: i64, a2: i64, a3: i64, a4: i64, a5: i64, a6: i64) -> i64 { + var ret: i64; + var sysno: i64 = MACOS_SYSCALL_BASE + id; + asm { + "syscall" + in("rax") sysno + in("rdi") a1 + in("rsi") a2 + in("rdx") a3 + in("r10") a4 + in("r8") a5 + in("r9") a6 + out("rax") ret + } + return ret; +} diff --git a/std/sys/macos/time.wave b/std/sys/macos/time.wave new file mode 100644 index 00000000..3976c2d7 --- /dev/null +++ b/std/sys/macos/time.wave @@ -0,0 +1,29 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 + +// ======================================================= +// macOS time syscalls +// ======================================================= + +import("std::sys::macos::syscall"); + +struct TimeSpec { + sec: i64; + nsec: i64; +} + +fun nanosleep(req: ptr, rem: ptr) -> i64 { + return syscall2(240, req as i64, rem as i64); +} + +fun clock_gettime(clock_id: i32, tp: ptr) -> i64 { + return syscall2(116, clock_id as i64, tp as i64); +} diff --git a/std/sys/macos/tty.wave b/std/sys/macos/tty.wave new file mode 100644 index 00000000..285b2ab5 --- /dev/null +++ b/std/sys/macos/tty.wave @@ -0,0 +1,123 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 + +// ======================================================= +// macOS tty helpers +// ======================================================= + +import("std::sys::macos::syscall"); + +const TTY_SYS_IOCTL: i64 = 54; +const TTY_SYS_FCNTL: i64 = 92; + +const TTY_TCGETS: i64 = 0x40487413; +const TTY_TCSETS: i64 = 0x80487414; +const TTY_TCSETSW: i64 = 0x80487415; +const TTY_TCSETSF: i64 = 0x80487416; + +const TTY_TCSANOW: i32 = 0; +const TTY_TCSADRAIN: i32 = 1; +const TTY_TCSAFLUSH: i32 = 2; + +const TTY_F_GETFL: i32 = 3; +const TTY_F_SETFL: i32 = 4; +const TTY_O_NONBLOCK: i32 = 4; + +const TTY_ICANON: u32 = 0x00000100; +const TTY_ECHO: u32 = 0x00000008; +const TTY_VTIME_IDX: i32 = 17; +const TTY_VMIN_IDX: i32 = 16; + +struct Termios { + c_iflag: u32; + c_oflag: u32; + c_cflag: u32; + c_lflag: u32; + c_line: u8; + c_cc: array; + c_ispeed: u32; + c_ospeed: u32; +} + +struct TtyRawState { + term: Termios; + flags: i32; + active: i32; +} + +fun tty_getattr(fd: i32, t: ptr) -> i64 { + return syscall3(TTY_SYS_IOCTL, fd as i64, TTY_TCGETS, t as i64); +} + +fun tty_setattr(fd: i32, action: i32, t: ptr) -> i64 { + var req: i64 = TTY_TCSETS; + + if (action == TTY_TCSADRAIN) { + req = TTY_TCSETSW; + } else if (action == TTY_TCSAFLUSH) { + req = TTY_TCSETSF; + } + + return syscall3(TTY_SYS_IOCTL, fd as i64, req, t as i64); +} + +fun tty_getfl(fd: i32) -> i64 { + return syscall3(TTY_SYS_FCNTL, fd as i64, TTY_F_GETFL as i64, 0); +} + +fun tty_setfl(fd: i32, flags: i32) -> i64 { + return syscall3(TTY_SYS_FCNTL, fd as i64, TTY_F_SETFL as i64, flags as i64); +} + +fun tty_enable_raw_nonblock(fd: i32, st: ptr) -> i64 { + deref st.active = 0; + + var orig: Termios; + if (tty_getattr(fd, &orig) < 0) { + return -1; + } + + var raw: Termios = orig; + raw.c_lflag = raw.c_lflag & ~(TTY_ICANON | TTY_ECHO); + raw.c_cc[TTY_VMIN_IDX] = 0; + raw.c_cc[TTY_VTIME_IDX] = 0; + + if (tty_setattr(fd, TTY_TCSANOW, &raw) < 0) { + return -1; + } + + var flags_raw: i64 = tty_getfl(fd); + if (flags_raw < 0) { + tty_setattr(fd, TTY_TCSANOW, &orig); + return -1; + } + + var old_flags: i32 = flags_raw as i32; + if (tty_setfl(fd, old_flags | TTY_O_NONBLOCK) < 0) { + tty_setattr(fd, TTY_TCSANOW, &orig); + return -1; + } + + deref st.term = orig; + deref st.flags = old_flags; + deref st.active = 1; + return 0; +} + +fun tty_restore(fd: i32, st: ptr) { + if (deref st.active == 0) { + return; + } + + var orig: Termios = deref st.term; + tty_setattr(fd, TTY_TCSANOW, &orig); + tty_setfl(fd, deref st.flags); +} diff --git a/std/sys/memory.wave b/std/sys/memory.wave new file mode 100644 index 00000000..947bd1c8 --- /dev/null +++ b/std/sys/memory.wave @@ -0,0 +1,20 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 + +// ======================================================= +// Memory sys dispatcher +// ======================================================= + +#[target(os="linux")] +import("std::sys::linux::memory"); + +#[target(os="macos")] +import("std::sys::macos::memory"); diff --git a/std/sys/process.wave b/std/sys/process.wave new file mode 100644 index 00000000..47a766d8 --- /dev/null +++ b/std/sys/process.wave @@ -0,0 +1,20 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 + +// ======================================================= +// Process sys dispatcher +// ======================================================= + +#[target(os="linux")] +import("std::sys::linux::process"); + +#[target(os="macos")] +import("std::sys::macos::process"); diff --git a/std/sys/socket.wave b/std/sys/socket.wave new file mode 100644 index 00000000..17c86c49 --- /dev/null +++ b/std/sys/socket.wave @@ -0,0 +1,24 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 + +// ======================================================= +// Socket sys dispatcher +// ======================================================= +// +// This layer selects the OS-specific socket implementation. +// Keep target attributes only in this dispatch module. +// ======================================================= + +#[target(os="linux")] +import("std::sys::linux::socket"); + +#[target(os="macos")] +import("std::sys::macos::socket"); diff --git a/std/sys/time.wave b/std/sys/time.wave new file mode 100644 index 00000000..ecb18b22 --- /dev/null +++ b/std/sys/time.wave @@ -0,0 +1,20 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 + +// ======================================================= +// Time sys dispatcher +// ======================================================= + +#[target(os="linux")] +import("std::sys::linux::time"); + +#[target(os="macos")] +import("std::sys::macos::time"); diff --git a/std/sys/tty.wave b/std/sys/tty.wave new file mode 100644 index 00000000..a44f65ab --- /dev/null +++ b/std/sys/tty.wave @@ -0,0 +1,20 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 + +// ======================================================= +// TTY sys dispatcher +// ======================================================= + +#[target(os="linux")] +import("std::sys::linux::tty"); + +#[target(os="macos")] +import("std::sys::macos::tty"); diff --git a/std/time/clock.wave b/std/time/clock.wave index c5bac44f..151ca86e 100644 --- a/std/time/clock.wave +++ b/std/time/clock.wave @@ -9,7 +9,7 @@ // // SPDX-License-Identifier: MPL-2.0 -import("std::sys::linux::time"); +import("std::sys::time"); fun time_now_realtime(tp: ptr) -> i64 { return clock_gettime(0, tp); diff --git a/std/time/diff.wave b/std/time/diff.wave index ccb28f59..e679a388 100644 --- a/std/time/diff.wave +++ b/std/time/diff.wave @@ -9,7 +9,7 @@ // // SPDX-License-Identifier: MPL-2.0 -import("std::sys::linux::time"); +import("std::sys::time"); fun time_diff_ns(start_ts: TimeSpec, end_ts: TimeSpec) -> i64 { var sec: i64 = end_ts.sec - start_ts.sec; diff --git a/std/time/sleep.wave b/std/time/sleep.wave index 4972fecd..a1fa8115 100644 --- a/std/time/sleep.wave +++ b/std/time/sleep.wave @@ -9,7 +9,7 @@ // // SPDX-License-Identifier: MPL-2.0 -import("std::sys::linux::time"); +import("std::sys::time"); fun time_sleep_ns(ns: i64) -> i64 { if (ns <= 0) { diff --git a/utils/src/colorex.rs b/utils/src/colorex.rs index 6a2b0161..1458c11e 100644 --- a/utils/src/colorex.rs +++ b/utils/src/colorex.rs @@ -99,5 +99,3 @@ impl Colorize for &str { format!("\x1b[7m{}\x1b[0m", self) } } - - diff --git a/utils/src/formatx.rs b/utils/src/formatx.rs index ffd52d33..ae496757 100644 --- a/utils/src/formatx.rs +++ b/utils/src/formatx.rs @@ -33,7 +33,9 @@ pub fn parse_placeholders(input: &str) -> Vec { while i < bytes.len() && bytes[i] != b'}' { i += 1; } - if i >= bytes.len() { break; } + if i >= bytes.len() { + break; + } let spec = input[start..i].trim().to_string(); out.push(Placeholder { spec }); diff --git a/utils/src/json.rs b/utils/src/json.rs index cad673bd..b517a582 100644 --- a/utils/src/json.rs +++ b/utils/src/json.rs @@ -105,9 +105,18 @@ impl<'a> Parser<'a> { self.skip_ws(); let c = self.peek().ok_or("unexpected eof")?; match c { - b'n' => { self.consume_bytes(b"null")?; Ok(Json::Null) } - b't' => { self.consume_bytes(b"true")?; Ok(Json::Bool(true)) } - b'f' => { self.consume_bytes(b"false")?; Ok(Json::Bool(false)) } + b'n' => { + self.consume_bytes(b"null")?; + Ok(Json::Null) + } + b't' => { + self.consume_bytes(b"true")?; + Ok(Json::Bool(true)) + } + b'f' => { + self.consume_bytes(b"false")?; + Ok(Json::Bool(false)) + } b'"' => Ok(Json::Str(self.parse_string()?)), b'[' => Ok(Json::Arr(self.parse_array()?)), b'{' => Ok(Json::Obj(self.parse_object()?)), @@ -159,14 +168,18 @@ impl<'a> Parser<'a> { self.skip_ws(); let start = self.i; - if self.peek() == Some(b'-') { self.i += 1; } + if self.peek() == Some(b'-') { + self.i += 1; + } // int match self.peek() { Some(b'0') => self.i += 1, Some(b'1'..=b'9') => { self.i += 1; - while matches!(self.peek(), Some(b'0'..=b'9')) { self.i += 1; } + while matches!(self.peek(), Some(b'0'..=b'9')) { + self.i += 1; + } } _ => return Err("invalid number".into()), } @@ -177,17 +190,23 @@ impl<'a> Parser<'a> { if !matches!(self.peek(), Some(b'0'..=b'9')) { return Err("invalid fraction".into()); } - while matches!(self.peek(), Some(b'0'..=b'9')) { self.i += 1; } + while matches!(self.peek(), Some(b'0'..=b'9')) { + self.i += 1; + } } // exp if matches!(self.peek(), Some(b'e') | Some(b'E')) { self.i += 1; - if matches!(self.peek(), Some(b'+') | Some(b'-')) { self.i += 1; } + if matches!(self.peek(), Some(b'+') | Some(b'-')) { + self.i += 1; + } if !matches!(self.peek(), Some(b'0'..=b'9')) { return Err("invalid exponent".into()); } - while matches!(self.peek(), Some(b'0'..=b'9')) { self.i += 1; } + while matches!(self.peek(), Some(b'0'..=b'9')) { + self.i += 1; + } } let s = std::str::from_utf8(&self.s[start..self.i]).map_err(|_| "utf8 error")?; @@ -198,7 +217,10 @@ impl<'a> Parser<'a> { self.expect(b'[')?; self.skip_ws(); let mut out = Vec::new(); - if self.peek() == Some(b']') { self.i += 1; return Ok(out); } + if self.peek() == Some(b']') { + self.i += 1; + return Ok(out); + } loop { let v = self.parse_value()?; @@ -217,7 +239,10 @@ impl<'a> Parser<'a> { self.expect(b'{')?; self.skip_ws(); let mut out = Vec::new(); - if self.peek() == Some(b'}') { self.i += 1; return Ok(out); } + if self.peek() == Some(b'}') { + self.i += 1; + return Ok(out); + } loop { self.skip_ws(); @@ -265,35 +290,59 @@ impl Json { Json::Arr(arr) => { write!(w, "[")?; - if pretty && !arr.is_empty() { write!(w, "\n")?; } + if pretty && !arr.is_empty() { + write!(w, "\n")?; + } for (i, v) in arr.iter().enumerate() { - if pretty { write_indent(w, indent + 2)?; } + if pretty { + write_indent(w, indent + 2)?; + } v.write_into(w, pretty, indent + 2)?; - if i + 1 != arr.len() { write!(w, ",")?; } - if pretty { write!(w, "\n")?; } + if i + 1 != arr.len() { + write!(w, ",")?; + } + if pretty { + write!(w, "\n")?; + } } - if pretty && !arr.is_empty() { write_indent(w, indent)?; } + if pretty && !arr.is_empty() { + write_indent(w, indent)?; + } write!(w, "]") } Json::Obj(kv) => { write!(w, "{{")?; - if pretty && !kv.is_empty() { write!(w, "\n")?; } + if pretty && !kv.is_empty() { + write!(w, "\n")?; + } for (i, (k, v)) in kv.iter().enumerate() { - if pretty { write_indent(w, indent + 2)?; } + if pretty { + write_indent(w, indent + 2)?; + } write_json_string(w, k)?; - if pretty { write!(w, ": ")?; } else { write!(w, ":")?; } + if pretty { + write!(w, ": ")?; + } else { + write!(w, ":")?; + } v.write_into(w, pretty, indent + 2)?; - if i + 1 != kv.len() { write!(w, ",")?; } - if pretty { write!(w, "\n")?; } + if i + 1 != kv.len() { + write!(w, ",")?; + } + if pretty { + write!(w, "\n")?; + } } - if pretty && !kv.is_empty() { write_indent(w, indent)?; } + if pretty && !kv.is_empty() { + write_indent(w, indent)?; + } write!(w, "}}") } } @@ -301,7 +350,9 @@ impl Json { } fn write_indent(w: &mut W, n: usize) -> io::Result<()> { - for _ in 0..n { write!(w, " ")?; } + for _ in 0..n { + write!(w, " ")?; + } Ok(()) } @@ -324,4 +375,4 @@ fn write_json_string(w: &mut W, s: &str) -> io::Result<()> { } } write!(w, "\"") -} \ No newline at end of file +} diff --git a/utils/src/lib.rs b/utils/src/lib.rs index 1e8e3d7b..e2c1a472 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -13,4 +13,4 @@ pub mod colorex; pub mod formatx; pub mod json; -pub use colorex::Colorize; \ No newline at end of file +pub use colorex::Colorize;