diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 977e75e..53e9083 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -14,7 +14,7 @@ jobs: - name: Set up Rust uses: dtolnay/rust-toolchain@master with: - toolchain: "1.85" + toolchain: "1.87" components: rustfmt, clippy - name: Build run: cargo build --verbose diff --git a/README.md b/README.md index a547651..f71f116 100644 --- a/README.md +++ b/README.md @@ -89,56 +89,25 @@ The WASM backend will: * ๐Ÿ”ข Increment / decrement (`i++`, `i--`) * ๐Ÿ”ง Functions with parameters, return values, and recursion * ๐Ÿงฉ Nested function calls (`add(add(1,2), 3)`) +* ๐Ÿ“ฆ Arrays (literals, index read/write, pass to functions) * ๐Ÿ–จ๏ธ Print statements (`print x;`) * ๐Ÿ” REPL (interactive shell) * ๐Ÿ“‚ File execution (`rusty run file.rts`) +* โœ… CI pipeline (GitHub Actions) --- -## ๐Ÿงช Example - -```ts -let x = (2 + 3) * 4; -print "x = " + x; - -function max(a, b) { - if (a > b) { - return a; - } else { - return b; - } -} - -print "max is " + max(x, 15); - -function factorial(n) { - if (n < 2) { return 1; } - return n * factorial(n - 1); -} -print "5! = " + factorial(5); - -for (let i = 0; i < 5; i++) { - if (i == 2) { continue; } - if (i == 4) { break; } - print i; -} - -let name = "Rusty"; -if (name && x > 10) { - print name + " works!"; -} -``` +## ๐Ÿงช Examples -Output: +Check out the [`example/`](example/) folder for sample RTS programs, including: -``` -x = 20 -max is 20 -5! = 120 -0 -1 -3 -Rusty works! +- [`test.rts`](example/test.rts) โ€” array basics (literals, indexing, mutation) +- [`merge_sort.rts`](example/merge_sort.rts) โ€” merge sort implementation using arrays, functions, and recursion + +Run any example with: + +```bash +cargo run -- run example/merge_sort.rts ``` --- @@ -210,15 +179,19 @@ cargo run -- repl * [x] Functions (declaration, params, return) * [x] Nested / recursive function calls * [x] Frame-based call stack +* [x] Arrays (literals, indexing, mutation) +* [x] Array pass/return from functions * [ ] Type annotations -* [ ] Arrays and objects +* [ ] Objects / maps * [ ] Closures +* [ ] `<=`, `>=` operators --- -### Runtime Evolution +### Runtime & Tooling * [x] Bytecode VM +* [x] CI pipeline (build, test, clippy, fmt) * [ ] WASM backend (in progress) * [ ] Bytecode optimizations * [ ] Register-based VM (optional) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index b85d194..71427e3 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -15,13 +15,13 @@ pub enum Statement { }, While { condition: Expression, - body: Vec + body: Vec, }, For { init: Box, condition: Expression, update: Box, - body: Vec + body: Vec, }, Assignment { name: String, @@ -32,16 +32,16 @@ pub enum Statement { Function { name: String, params: Vec, - body: Vec + body: Vec, }, Return { - value: Expression + value: Expression, }, AssignmentIndex { array: String, index: Expression, value: Expression, - } + }, } #[derive(Debug, Clone, PartialEq)] @@ -55,7 +55,7 @@ pub enum BinaryOperation { Equal, NotEqual, And, - Or + Or, } #[derive(Debug, Clone)] @@ -70,17 +70,17 @@ pub enum Expression { }, Unary { op: BinaryOperation, - expr: Box + expr: Box, }, Call { name: String, - args: Vec + args: Vec, }, ArrayLiteral(Vec), Index { array: Box, index: Box, - } + }, } #[derive(Debug, Clone, PartialEq, PartialOrd)] @@ -88,4 +88,4 @@ pub enum Value { Number(i32), String(String), Array(Vec), -} \ No newline at end of file +} diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 7d06b8a..2b268b3 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -5,7 +5,7 @@ use crate::ast::{BinaryOperation, Expression, Statement, Value}; #[derive(Debug)] pub struct FunctionBytecode { pub params: Vec, - pub instructions: Vec + pub instructions: Vec, } pub struct LoopContext { @@ -17,7 +17,7 @@ pub struct LoopContext { #[derive(Debug)] pub struct Program { pub main: Vec, - pub functions: HashMap + pub functions: HashMap, } #[derive(Debug, Clone)] @@ -45,13 +45,15 @@ pub enum Instruction { CallFunction(String, usize), CreateArray(usize), LoadIndex, - StoreIndex + StoreIndex, } fn compile_expr(instructions: &mut Vec, expr: &Expression) { match expr { Expression::Number(x) => instructions.push(Instruction::LoadConst(Value::Number(*x))), - Expression::String(s) => instructions.push(Instruction::LoadConst(Value::String(s.clone()))), + Expression::String(s) => { + instructions.push(Instruction::LoadConst(Value::String(s.clone()))) + } Expression::Identifier(val) => instructions.push(Instruction::LoadVar(val.to_string())), Expression::Binary { left, op, right } => { if *op != BinaryOperation::And && *op != BinaryOperation::Or { @@ -75,7 +77,8 @@ fn compile_expr(instructions: &mut Vec, expr: &Expression) { let jump_instructions_index = instructions.len() - 1; instructions.push(Instruction::PopTop); compile_expr(instructions, right); - instructions[jump_instructions_index] = Instruction::JumpIfFalse(instructions.len()); + instructions[jump_instructions_index] = + Instruction::JumpIfFalse(instructions.len()); } BinaryOperation::Or => { instructions.push(Instruction::DuplicateTop); @@ -83,29 +86,29 @@ fn compile_expr(instructions: &mut Vec, expr: &Expression) { let jump_instructions_index = instructions.len() - 1; instructions.push(Instruction::PopTop); compile_expr(instructions, right); - instructions[jump_instructions_index] = Instruction::JumpIfTrue(instructions.len()); + instructions[jump_instructions_index] = + Instruction::JumpIfTrue(instructions.len()); } } } Expression::Unary { op, expr } => { compile_expr(instructions, expr); - match op { - BinaryOperation::Subtraction => instructions.push(Instruction::Negate), - _ => {} + if op == &BinaryOperation::Subtraction { + instructions.push(Instruction::Negate); } - }, + } Expression::Call { name, args } => { for arg in args { compile_expr(instructions, arg); } instructions.push(Instruction::CallFunction(name.clone(), args.len())); - }, + } Expression::ArrayLiteral(elements) => { for element in elements { compile_expr(instructions, element); } instructions.push(Instruction::CreateArray(elements.len())); - }, + } Expression::Index { array, index } => { compile_expr(instructions, array); compile_expr(instructions, index); @@ -124,12 +127,16 @@ pub fn compile_statements( Statement::VarDecl { name, value } => { compile_expr(instructions, &value); instructions.push(Instruction::DeclareVar(name)); - }, + } Statement::Print { value } => { compile_expr(instructions, &value); instructions.push(Instruction::Print); } - Statement::If { condition, body, else_body } => { + Statement::If { + condition, + body, + else_body, + } => { compile_expr(instructions, &condition); let jump_if_false_index = instructions.len(); @@ -140,11 +147,13 @@ pub fn compile_statements( if let Some(else_body) = else_body { let jump_to_end_index = instructions.len(); instructions.push(Instruction::Jump(0)); - instructions[jump_if_false_index] = Instruction::JumpIfFalse(instructions.len()); + instructions[jump_if_false_index] = + Instruction::JumpIfFalse(instructions.len()); compile_statements(else_body, instructions, loop_ctx.as_deref_mut()); instructions[jump_to_end_index] = Instruction::Jump(instructions.len()); } else { - instructions[jump_if_false_index] = Instruction::JumpIfFalse(instructions.len()); + instructions[jump_if_false_index] = + Instruction::JumpIfFalse(instructions.len()); } } Statement::While { condition, body } => { @@ -171,8 +180,13 @@ pub fn compile_statements( for idx in ctx.break_placeholders { instructions[idx] = Instruction::Jump(instructions.len()); } - }, - Statement::For { init, condition, update, body } => { + } + Statement::For { + init, + condition, + update, + body, + } => { compile_statements(vec![*init], instructions, loop_ctx.as_deref_mut()); let loop_start_index = instructions.len(); @@ -201,11 +215,11 @@ pub fn compile_statements( for idx in ctx.break_placeholders { instructions[idx] = Instruction::Jump(instructions.len()); } - }, + } Statement::Assignment { name, value } => { compile_expr(instructions, &value); instructions.push(Instruction::AssignVar(name)); - }, + } Statement::Break => { if let Some(ctx) = loop_ctx.as_deref_mut() { instructions.push(Instruction::Jump(0)); @@ -213,7 +227,7 @@ pub fn compile_statements( } else { panic!("'break' used outside of a loop"); } - }, + } Statement::Continue => { if let Some(ctx) = loop_ctx.as_deref_mut() { instructions.push(Instruction::Jump(0)); @@ -221,24 +235,30 @@ pub fn compile_statements( } else { panic!("'continue' used outside of a loop"); } - }, - Statement::Function { name:_, params:_, body:_ } => {}, + } + Statement::Function { + name: _, + params: _, + body: _, + } => {} Statement::Return { value } => { compile_expr(instructions, &value); instructions.push(Instruction::Return); - }, - Statement::Expression(expr) => { - match expr { - Expression::Call { name, args } => { - for arg in &args { - compile_expr(instructions, arg); - } - instructions.push(Instruction::CallFunction(name.clone(), args.len())); - }, - _ => compile_expr(instructions, &expr) + } + Statement::Expression(expr) => match expr { + Expression::Call { name, args } => { + for arg in &args { + compile_expr(instructions, arg); + } + instructions.push(Instruction::CallFunction(name.clone(), args.len())); } + _ => compile_expr(instructions, &expr), }, - Statement::AssignmentIndex { array, index, value } => { + Statement::AssignmentIndex { + array, + index, + value, + } => { instructions.push(Instruction::LoadVar(array.clone())); // index is expression @@ -262,11 +282,20 @@ pub fn compile_program(statements: Vec) -> Program { if let Statement::Function { name, params, body } = statement { let mut func_instructions = Vec::new(); compile_statements(body, &mut func_instructions, None); - functions.insert(name, FunctionBytecode { params, instructions: func_instructions }); + functions.insert( + name, + FunctionBytecode { + params, + instructions: func_instructions, + }, + ); } else { compile_statements(vec![statement], &mut main_instructions, None); } } - Program { main: main_instructions, functions } -} \ No newline at end of file + Program { + main: main_instructions, + functions, + } +} diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index a78f2d9..b3871d5 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -32,10 +32,9 @@ pub enum Token { Comma, Return, SquareLeft, - SquareRight + SquareRight, } - pub fn tokenize(input: &str) -> Vec { let mut tokens = Vec::new(); let mut chars = input.chars().peekable(); @@ -192,7 +191,7 @@ pub fn tokenize(input: &str) -> Vec { } tokens.push(Token::Number(num.parse().unwrap())); - }, + } '&' => { chars.next(); @@ -206,7 +205,7 @@ pub fn tokenize(input: &str) -> Vec { } else { panic!("Unexpected character: {}", ch); } - }, + } '|' => { chars.next(); @@ -220,7 +219,7 @@ pub fn tokenize(input: &str) -> Vec { } else { panic!("Unexpected character: {}", ch); } - }, + } 'a'..='z' | 'A'..='Z' => { let mut ident = String::new(); @@ -255,4 +254,4 @@ pub fn tokenize(input: &str) -> Vec { } tokens -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index f9e6b14..d3c14cf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,35 +1,39 @@ -use std::env::{args}; +use std::env::args; use std::panic; -mod lexer; -mod parser; mod ast; mod compiler; +mod lexer; +mod parser; mod vm; +use compiler::compile_program; use lexer::tokenize; use parser::Parser; -use compiler::compile_program; -use vm::execute; use vm::Runtime; +use vm::execute; fn repl_execution(runtime: &mut Runtime, input: &str) { - let tokens = tokenize(&input); - let mut parser = Parser::new(tokens); - let ast = parser.parse(); - let program = compile_program(ast); - execute(program, runtime); + let tokens = tokenize(input); + let mut parser = Parser::new(tokens); + let ast = parser.parse(); + let program = compile_program(ast); + execute(program, runtime); } -fn repl(){ +fn repl() { use std::io::{self, Write}; let mut runtime = Runtime::new(); loop { print!("> "); io::stdout().flush().unwrap(); let mut input = String::new(); - io::stdin().read_line(&mut input).expect("Failed to read line"); - let res = panic::catch_unwind(panic::AssertUnwindSafe(|| repl_execution(&mut runtime, &input))); - if let Err(_) = res { + io::stdin() + .read_line(&mut input) + .expect("Failed to read line"); + let res = panic::catch_unwind(panic::AssertUnwindSafe(|| { + repl_execution(&mut runtime, &input) + })); + if res.is_err() { eprintln!("Error occurred while executing input"); } } @@ -69,4 +73,4 @@ fn main() { println!("Unknown command: {}", command); println!("Usage: {} run ", run_args[0]); } -} \ No newline at end of file +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 852cd5d..4b918f8 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,5 +1,5 @@ -use crate::lexer::Token; use crate::ast::{BinaryOperation, Expression, Statement}; +use crate::lexer::Token; pub struct Parser { tokens: Vec, @@ -12,7 +12,7 @@ fn is_arithmetic_operator(tkn: &Token) -> Option { Token::Subtraction => Some(BinaryOperation::Subtraction), Token::Multiplication => Some(BinaryOperation::Multiplication), Token::Division => Some(BinaryOperation::Division), - _ => None + _ => None, } } @@ -22,7 +22,7 @@ fn is_compare_operator(tkn: &Token) -> Option { Token::Less => Some(BinaryOperation::Less), Token::EqualEqual => Some(BinaryOperation::Equal), Token::NotEqual => Some(BinaryOperation::NotEqual), - _ => None + _ => None, } } @@ -55,10 +55,9 @@ impl Parser { match self.peek() { Some(Token::Let) => self.parse_var_decl(), Some(Token::Identifier(name)) => { - if name == "print"{ + if name == "print" { self.parse_print() - } - else { + } else { self.parse_call(name.clone()) } } @@ -71,14 +70,14 @@ impl Parser { panic!("Expected ';' after 'break'"); } Statement::Break - }, + } Some(Token::Continue) => { self.advance(); if self.advance() != Some(&Token::Semicolon) { panic!("Expected ';' after 'continue'"); } Statement::Continue - }, + } Some(Token::Return) => { self.advance(); let value = self.parse_first(); @@ -86,7 +85,7 @@ impl Parser { panic!("Expected ';' after return value"); } Statement::Return { value } - }, + } Some(Token::Function) => self.parse_function(), _ => panic!("Unexpected token: {:?}", self.peek()), } @@ -114,8 +113,10 @@ impl Parser { Token::Identifier(param) => { params.push(param.clone()); self.advance(); - }, - Token::Comma => { self.advance(); }, + } + Token::Comma => { + self.advance(); + } _ => panic!("Expected parameter name or ','"), } } @@ -171,7 +172,7 @@ impl Parser { }, _ => panic!("Expected '++' or '--' in for update"), } - } + } fn parse_assignment(&mut self, name: String) -> Statement { match self.advance() { @@ -186,8 +187,8 @@ impl Parser { Some(Token::Semicolon) => {} _ => panic!("Expected ';'"), } - return Statement::Assignment { name, value } - }, + return Statement::Assignment { name, value }; + } Some(Token::Decrement) => { let value = Expression::Binary { left: Box::new(Expression::Identifier(name.clone())), @@ -198,8 +199,8 @@ impl Parser { Some(Token::Semicolon) => {} _ => panic!("Expected ';'"), } - return Statement::Assignment { name, value } - }, + return Statement::Assignment { name, value }; + } _ => panic!("Expected '='"), } @@ -211,7 +212,7 @@ impl Parser { } Statement::Assignment { name, value } - } + } fn parse_var_decl(&mut self) -> Statement { self.advance(); // consume 'let' @@ -283,7 +284,7 @@ impl Parser { let mut left = self.parse_expression(); while let Some(token) = self.peek() { - if let Some(opr) = is_compare_operator(token){ + if let Some(opr) = is_compare_operator(token) { self.advance(); let right = self.parse_expression(); left = Expression::Binary { @@ -302,7 +303,7 @@ impl Parser { let mut left = self.parse_term(); while let Some(token) = self.peek() { - if let Some(opr) = is_arithmetic_operator(token){ + if let Some(opr) = is_arithmetic_operator(token) { if opr != BinaryOperation::Addition && opr != BinaryOperation::Subtraction { break; } @@ -324,7 +325,7 @@ impl Parser { let mut left = self.parse_unary(); while let Some(token) = self.peek() { - if let Some(opr) = is_arithmetic_operator(token){ + if let Some(opr) = is_arithmetic_operator(token) { if opr != BinaryOperation::Multiplication && opr != BinaryOperation::Division { break; } @@ -343,17 +344,16 @@ impl Parser { } fn parse_unary(&mut self) -> Expression { - if let Some(token) = self.peek() { - if let Some(opr) = is_arithmetic_operator(token){ - if opr == BinaryOperation::Subtraction { - self.advance(); - let expr = self.parse_unary(); - return Expression::Unary { - op: opr, - expr: Box::new(expr) - } - } - } + if let Some(token) = self.peek() + && let Some(opr) = is_arithmetic_operator(token) + && opr == BinaryOperation::Subtraction + { + self.advance(); + let expr = self.parse_unary(); + return Expression::Unary { + op: opr, + expr: Box::new(expr), + }; } self.parse_primary() } @@ -379,19 +379,17 @@ impl Parser { _ => panic!("Expected ';' after array assignment"), } match index { - Expression::Index { array, index } => { - Statement::AssignmentIndex { - array: match *array { - Expression::Identifier(name) => name, - _ => panic!("Expected identifier for array name in assignment"), - }, - index: *index, - value, - } + Expression::Index { array, index } => Statement::AssignmentIndex { + array: match *array { + Expression::Identifier(name) => name, + _ => panic!("Expected identifier for array name in assignment"), + }, + index: *index, + value, }, _ => panic!("Expected array indexing expression for array assignment"), } - }, + } _ => Statement::Expression(index), // Just an array access expression } } else { @@ -441,11 +439,10 @@ impl Parser { self.parse_call_args(name) } else if let Some(Token::SquareLeft) = self.peek() { self.parse_index(name) - } - else { + } else { Expression::Identifier(name) } - }, + } Some(Token::SquareLeft) => self.parse_array_literal(), Some(Token::String(s)) => Expression::String(s.clone()), _ => panic!("Invalid expression"), @@ -468,7 +465,7 @@ impl Parser { panic!("Expected '[' after identifier for array indexing"); } } - + fn parse_array_literal(&mut self) -> Expression { let mut elements = Vec::new(); while let Some(token) = self.peek() { @@ -487,7 +484,7 @@ impl Parser { _ => panic!("Expected ']' after array literal"), } Expression::ArrayLiteral(elements) - } + } fn parse_if(&mut self) -> Statement { self.advance(); // consume 'if' @@ -526,7 +523,8 @@ impl Parser { if let Some(Token::Else) = self.peek() { self.advance(); match self.advance() { - Some(Token::LeftBrace) => {}_ => panic!("Expected '{{' after 'else'"), + Some(Token::LeftBrace) => {} + _ => panic!("Expected '{{' after 'else'"), } while let Some(token) = self.peek() { @@ -540,10 +538,18 @@ impl Parser { Some(Token::RightBrace) => {} _ => panic!("Expected '}}' after else block"), } - return Statement::If { condition, body, else_body: Some(else_body)}; + return Statement::If { + condition, + body, + else_body: Some(else_body), + }; } - Statement::If { condition, body, else_body: None } + Statement::If { + condition, + body, + else_body: None, + } } fn parse_while(&mut self) -> Statement { @@ -580,7 +586,7 @@ impl Parser { } Statement::While { condition, body } - } + } fn parse_for(&mut self) -> Statement { self.advance(); // consume 'for' @@ -624,7 +630,12 @@ impl Parser { _ => panic!("Expected '}}' after for block"), } - Statement::For { init: Box::new(init), condition, update: Box::new(update), body } + Statement::For { + init: Box::new(init), + condition, + update: Box::new(update), + body, + } } fn parse_first(&mut self) -> Expression { diff --git a/src/vm/mod.rs b/src/vm/mod.rs index cadc1f3..bc32866 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -1,7 +1,9 @@ use std::{collections::HashMap, fmt}; -use crate::{ast::{BinaryOperation, Value}, compiler::{FunctionBytecode, Instruction, Program}}; - +use crate::{ + ast::{BinaryOperation, Value}, + compiler::{FunctionBytecode, Instruction, Program}, +}; impl fmt::Display for Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -9,7 +11,11 @@ impl fmt::Display for Value { Value::Number(n) => write!(f, "{}", n), Value::String(s) => write!(f, "{}", s), Value::Array(arr) => { - let elements = arr.iter().map(|v| format!("{}", v)).collect::>().join(", "); + let elements = arr + .iter() + .map(|v| format!("{}", v)) + .collect::>() + .join(", "); write!(f, "[{}]", elements) } } @@ -30,13 +36,21 @@ pub struct Runtime { } impl Runtime { - pub fn new() -> Self { - Self { stack: Vec::new(), frames: Vec::new(), functions: HashMap::new() } + Self { + stack: Vec::new(), + frames: Vec::new(), + functions: HashMap::new(), + } } fn load_variable(&mut self, var: &str) { - if let Some(value) = self.frames.iter().rev().find_map(|frame| frame.variables.get(var)) { + if let Some(value) = self + .frames + .iter() + .rev() + .find_map(|frame| frame.variables.get(var)) + { self.stack.push((*value).clone()); } else { panic!("{} is not defined", var); @@ -44,7 +58,7 @@ impl Runtime { } fn store_variable(&mut self, var: String) { - if let Some(value) = self.stack.pop(){ + if let Some(value) = self.stack.pop() { self.frames.last_mut().unwrap().variables.insert(var, value); } else { panic!("No defined value to store in {}", var); @@ -52,8 +66,13 @@ impl Runtime { } fn assign_variable(&mut self, var: String) { - if let Some(value) = self.frames.iter_mut().rev().find_map(|frame| frame.variables.get_mut(&var)) { - if let Some(val) = self.stack.pop(){ + if let Some(value) = self + .frames + .iter_mut() + .rev() + .find_map(|frame| frame.variables.get_mut(&var)) + { + if let Some(val) = self.stack.pop() { *value = val; } else { panic!("No defined value to store in {}", var); @@ -68,22 +87,22 @@ impl Runtime { } fn print(&mut self) { - if let Some(value) = self.stack.pop(){ + if let Some(value) = self.stack.pop() { println!("{}", value); } else { panic!("No defined value to print"); } } fn pop_or_panic_stack(&mut self) -> Value { - if let Some(value) = self.stack.pop(){ - return value; + if let Some(value) = self.stack.pop() { + value } else { panic!("Invalid expression"); } } fn arithmetic_opr(&mut self, opr_type: BinaryOperation) { let value1 = self.pop_or_panic_stack(); - let value2= self.pop_or_panic_stack(); + let value2 = self.pop_or_panic_stack(); match opr_type { BinaryOperation::Addition => { @@ -94,15 +113,15 @@ impl Runtime { let final_value = format!("{}{}", value2, value1); self.stack.push(Value::String(final_value)); } - }, + } BinaryOperation::Subtraction => { if let (Value::Number(v1), Value::Number(v2)) = (value1, value2) { - let final_value = v2 - v1; + let final_value = v2 - v1; self.stack.push(Value::Number(final_value)); - }else { + } else { panic!("Subtraction is only supported for numbers"); } - }, + } BinaryOperation::Multiplication => { if let (Value::Number(v1), Value::Number(v2)) = (value1, value2) { let final_value = v1 * v2; @@ -110,7 +129,7 @@ impl Runtime { } else { panic!("Multiplication is only supported for numbers"); } - }, + } BinaryOperation::Division => { if let (Value::Number(v1), Value::Number(v2)) = (value1, value2) { if v1 == 0 { @@ -122,45 +141,47 @@ impl Runtime { panic!("Division is only supported for numbers"); } } - _ => panic!("Invalid arithmetic operator") + _ => panic!("Invalid arithmetic operator"), } } - fn compare_opr(&mut self, opr_type: BinaryOperation) { let value1 = self.pop_or_panic_stack(); - let value2= self.pop_or_panic_stack(); + let value2 = self.pop_or_panic_stack(); let result = match opr_type { BinaryOperation::Greater => { - if matches!((&value1, &value2), - (Value::Number(_), Value::Number(_)) | - (Value::String(_), Value::String(_)) + if matches!( + (&value1, &value2), + (Value::Number(_), Value::Number(_)) | (Value::String(_), Value::String(_)) ) { value2 > value1 } else { panic!("Greater comparison is only supported between values of the same type"); } - }, + } BinaryOperation::Less => { - if matches!((&value1, &value2), - (Value::Number(_), Value::Number(_)) | - (Value::String(_), Value::String(_)) + if matches!( + (&value1, &value2), + (Value::Number(_), Value::Number(_)) | (Value::String(_), Value::String(_)) ) { value2 < value1 } else { panic!("Less comparison is only supported between values of the same type"); } - }, + } BinaryOperation::Equal => value2 == value1, BinaryOperation::NotEqual => value2 != value1, - _ => panic!("Invalid comparison operator") + _ => panic!("Invalid comparison operator"), }; - self.stack.push(if result { Value::Number(1) } else { Value::Number(0) }); + self.stack.push(if result { + Value::Number(1) + } else { + Value::Number(0) + }); } } - pub fn execute(program: Program, runtime: &mut Runtime) { runtime.functions = program.functions; runtime.frames.push(Frame { @@ -168,42 +189,42 @@ pub fn execute(program: Program, runtime: &mut Runtime) { instructions: program.main, ip: 0, }); - + while let Some(frame) = runtime.frames.last() { if frame.ip >= frame.instructions.len() { - runtime.frames.pop(); // function ended without explicit return + runtime.frames.pop(); // function ended without explicit return continue; } - + let instruction = frame.instructions[frame.ip].clone(); match instruction { Instruction::LoadConst(val) => { runtime.load_const((val).clone()); - }, - Instruction::DeclareVar(val) => { + } + Instruction::DeclareVar(val) => { runtime.store_variable(val.to_string()); - }, + } Instruction::AssignVar(val) => { runtime.assign_variable(val.to_string()); - }, + } Instruction::LoadVar(val) => { runtime.load_variable(&val); - }, + } Instruction::Print => { runtime.print(); - }, + } Instruction::Add => { runtime.arithmetic_opr(BinaryOperation::Addition); - }, + } Instruction::Subtract => { runtime.arithmetic_opr(BinaryOperation::Subtraction); - }, + } Instruction::Multiply => { runtime.arithmetic_opr(BinaryOperation::Multiplication); - }, + } Instruction::Divide => { runtime.arithmetic_opr(BinaryOperation::Division); - }, + } Instruction::Negate => { let value = runtime.pop_or_panic_stack(); if let Value::Number(num) = value { @@ -211,57 +232,61 @@ pub fn execute(program: Program, runtime: &mut Runtime) { } else { panic!("Negation is only supported for numbers"); } - }, + } Instruction::Greater => { runtime.compare_opr(BinaryOperation::Greater); - }, + } Instruction::Less => { runtime.compare_opr(BinaryOperation::Less); - }, + } Instruction::Equal => { runtime.compare_opr(BinaryOperation::Equal); - }, + } Instruction::NotEqual => { runtime.compare_opr(BinaryOperation::NotEqual); - }, + } Instruction::JumpIfFalse(idx) => { let condition = runtime.pop_or_panic_stack(); - if matches!(condition, Value::Number(0)) || - matches!(condition, Value::String(s) if s.is_empty()) + if matches!(condition, Value::Number(0)) + || matches!(condition, Value::String(s) if s.is_empty()) { runtime.frames.last_mut().unwrap().ip = idx; continue; } - }, + } Instruction::Jump(idx) => { runtime.frames.last_mut().unwrap().ip = idx; continue; - }, + } Instruction::JumpIfTrue(idx) => { let condition = runtime.pop_or_panic_stack(); - if matches!(condition, Value::Number(n) if n != 0) || - matches!(condition, Value::String(s) if !s.is_empty()) + if matches!(condition, Value::Number(n) if n != 0) + || matches!(condition, Value::String(s) if !s.is_empty()) { runtime.frames.last_mut().unwrap().ip = idx; continue; - } - }, + } + } Instruction::DuplicateTop => { if let Some(value) = runtime.stack.last() { runtime.stack.push((*value).clone()); } else { panic!("No defined value to duplicate"); } - }, + } Instruction::PopTop => { runtime.pop_or_panic_stack(); - }, + } Instruction::CallFunction(name, arg_count) => { let func = runtime.functions.get(&name).expect("undefined function"); let mut locals = HashMap::new(); // Pop args in reverse (last arg was pushed last) if arg_count != func.params.len() { - panic!("Expected {} arguments but got {}", func.params.len(), arg_count); + panic!( + "Expected {} arguments but got {}", + func.params.len(), + arg_count + ); } for param in func.params.iter().rev() { locals.insert(param.clone(), runtime.stack.pop().unwrap()); @@ -275,13 +300,13 @@ pub fn execute(program: Program, runtime: &mut Runtime) { variables: locals, }); continue; // don't increment ip again - }, + } Instruction::Return => { - let return_value = runtime.stack.pop().unwrap(); + let return_value = runtime.stack.pop().unwrap(); runtime.frames.pop(); runtime.stack.push(return_value); continue; - }, + } Instruction::CreateArray(len) => { let mut elements = Vec::new(); for _ in 0..len { @@ -293,7 +318,7 @@ pub fn execute(program: Program, runtime: &mut Runtime) { } elements.reverse(); runtime.stack.push(Value::Array(elements)); - }, + } Instruction::LoadIndex => { let index = runtime.pop_or_panic_stack(); let array = runtime.pop_or_panic_stack(); @@ -305,12 +330,12 @@ pub fn execute(program: Program, runtime: &mut Runtime) { } else { panic!("LoadIndex requires an array and a number index"); } - }, + } Instruction::StoreIndex => { let value = runtime.pop_or_panic_stack(); let index = runtime.pop_or_panic_stack(); let array = runtime.pop_or_panic_stack(); - + if let (Value::Array(mut arr), Value::Number(idx)) = (array, index) { if idx < 0 || (idx as usize) >= arr.len() { panic!("Array index out of bounds"); @@ -324,4 +349,4 @@ pub fn execute(program: Program, runtime: &mut Runtime) { } runtime.frames.last_mut().unwrap().ip += 1; // Move to next instruction } -} \ No newline at end of file +} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 5d073c3..dc650eb 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -41,7 +41,10 @@ fn run_rts_should_fail(code: &str) -> String { let _ = std::fs::remove_file(&file_path); - assert!(!output.status.success(), "Expected program to fail but it succeeded"); + assert!( + !output.status.success(), + "Expected program to fail but it succeeded" + ); String::from_utf8_lossy(&output.stderr).to_string() } @@ -427,16 +430,20 @@ fn test_number_concat_with_string() { #[test] fn test_string_variable() { - let out = run_rts(r#"let x = "hello"; -print x;"#); + let out = run_rts( + r#"let x = "hello"; +print x;"#, + ); assert_eq!(out, "hello"); } #[test] fn test_string_variable_concat() { - let out = run_rts(r#"let a = "foo"; + let out = run_rts( + r#"let a = "foo"; let b = "bar"; -print a + b;"#); +print a + b;"#, + ); assert_eq!(out, "foobar"); } @@ -474,9 +481,11 @@ fn test_string_number_equality_false() { #[test] fn test_string_concat_in_variable() { - let out = run_rts(r#"let x = 5; + let out = run_rts( + r#"let x = 5; let n = "nope" + x; -print n;"#); +print n;"#, + ); assert_eq!(out, "nope5"); } @@ -833,7 +842,8 @@ fn test_array_index_write() { #[test] fn test_array_index_write_persists() { - let out = run_rts("let arr = [1, 2, 3];\narr[0] = 10;\narr[2] = 30;\nprint arr[0];\nprint arr[2];"); + let out = + run_rts("let arr = [1, 2, 3];\narr[0] = 10;\narr[2] = 30;\nprint arr[0];\nprint arr[2];"); assert_eq!(out, "10\n30"); } @@ -898,8 +908,10 @@ print arr; #[test] fn test_array_with_strings() { - let out = run_rts(r#"let arr = ["a", "b", "c"]; -print arr[1];"#); + let out = run_rts( + r#"let arr = ["a", "b", "c"]; +print arr[1];"#, + ); assert_eq!(out, "b"); }