diff --git a/bellos_scripts/basic_math.bellos b/bellos_scripts/basic_math.bellos index bdeea06..bf2e354 100644 --- a/bellos_scripts/basic_math.bellos +++ b/bellos_scripts/basic_math.bellos @@ -3,38 +3,43 @@ # Demonstrating arithmetic operations -echo Basic Math Operations +# Addition +result=$((5 + 3)) +echo Addition: 5 + 3 = $result -# Simple echo statements for arithmetic -echo Addition: -echo 5 + 3 = 8 +# Subtraction +result=$((10 - 4)) +echo Subtraction: 10 - 4 = $result -echo Subtraction: -echo 10 - 4 = 6 +# Multiplication +result=$((6 * 7)) +echo Multiplication: 6 * 7 = $result -echo Multiplication: -echo 6 * 7 = 42 +# Division +result=$((20 / 4)) +echo Division: 20 / 4 = $result -echo Division: -echo 20 / 4 = 5 +# Modulus +result=$((17 % 5)) +echo Modulus: 17 % 5 = $result -echo Modulus: -echo 17 % 5 = 2 +# Compound operation +result=$(( (10 + 5) * 2 )) +echo Compound: (10 + 5) * 2 = $result -echo Compound operation: -echo (10 + 5) * 2 = 30 - -# Using variables (without arithmetic) -echo Using variables: +# Using variables a=7 b=3 -echo a = $a -echo b = $b +result=$((a + b)) +echo Variables: $a + $b = $result -# Simple increments and decrements -echo Increment and Decrement: -echo count = 0 -echo count after increment: 1 -echo count after decrement: 0 +# Increment +count=0 +count=$((count + 1)) +echo Increment: count after increment = $count + +# Decrement +count=$((count - 1)) +echo Decrement: count after decrement = $count echo Basic math operations completed. diff --git a/dependencies.txt b/dependencies.txt index a25ed55..4e72c59 100644 --- a/dependencies.txt +++ b/dependencies.txt @@ -1,3 +1,4 @@ glob = "0.3.0" tempfile = "3.2" shellexpand = "3.1.0" +meval = "0.2" diff --git a/executable/bellos b/executable/bellos index 59a3bc1..fbca1db 100755 Binary files a/executable/bellos and b/executable/bellos differ diff --git a/src/bellos.rs b/src/bellos.rs index 7e915bc..e34b188 100644 --- a/src/bellos.rs +++ b/src/bellos.rs @@ -13,22 +13,19 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -mod executor; +mod executor_processes; mod interpreter; mod lexer; mod parser; mod utilities; -use crate::executor::executor::Executor; -use std::env; -use std::process; +use crate::executor_processes::executor::Executor; fn main() { - let args: Vec = env::args().collect(); + let args: Vec = std::env::args().collect(); let mut executor = Executor::new(); - if let Err(e) = executor.run(args) { eprintln!("Application error: {}", e); - process::exit(1); + std::process::exit(1); } } diff --git a/src/executor/executor.rs b/src/executor/executor.rs deleted file mode 100644 index 07cc9be..0000000 --- a/src/executor/executor.rs +++ /dev/null @@ -1,413 +0,0 @@ -// Copyright (C) 2024 Bellande Architecture Mechanism Research Innovation Center, Ronaldson Bellande - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use crate::interpreter::interpreter::Interpreter; -use crate::lexer::lexer::Lexer; -use crate::parser::parser::Parser; -use crate::utilities::utilities::{ASTNode, RedirectType, Token}; -use shellexpand; -use std::collections::HashMap; -use std::fs::{File, OpenOptions}; -use std::io::{self, BufRead, Read, Write}; -use std::process::{Command, Stdio}; - -pub struct Executor { - interpreter: Interpreter, - variables: HashMap, - functions: HashMap, - last_exit_status: i32, -} - -impl Executor { - pub fn new() -> Self { - Executor { - interpreter: Interpreter::new(), - variables: HashMap::new(), - functions: HashMap::new(), - last_exit_status: 0, - } - } - - pub fn run(&mut self, args: Vec) -> Result<(), String> { - if args.len() > 1 { - // Execute script file - self.execute_script(&args[1]) - } else { - // Interactive mode - self.run_interactive_mode() - } - } - - fn execute_script(&mut self, filename: &str) -> Result<(), String> { - let file = - File::open(filename).map_err(|e| format!("Error opening file {}: {}", filename, e))?; - let reader = io::BufReader::new(file); - - for (line_num, line) in reader.lines().enumerate() { - let line = line.map_err(|e| format!("Error reading line: {}", e))?; - self.process_line(&line, line_num + 1)?; - } - Ok(()) - } - - fn process_line(&mut self, line: &str, line_num: usize) -> Result<(), String> { - let trimmed_line = line.trim(); - if trimmed_line.is_empty() || trimmed_line.starts_with('#') { - return Ok(()); // Skip empty lines and comments - } - - let lexer = Lexer::new(line.to_string()); - let tokens: Vec = lexer.into_iter().collect(); - let mut parser = Parser::new(tokens); - match parser.parse() { - Ok(ast) => { - if let Err(e) = self.execute(ast) { - eprintln!("Error on line {}: {}", line_num, e); - } - } - Err(e) => eprintln!("Parse error on line {}: {}", line_num, e), - } - Ok(()) - } - - fn run_interactive_mode(&mut self) -> Result<(), String> { - loop { - print!("bellos> "); - io::stdout().flush().unwrap(); - let mut input = String::new(); - io::stdin().read_line(&mut input).unwrap(); - - if input.trim().is_empty() { - continue; - } - - let lexer = Lexer::new(input); - let tokens: Vec = lexer.into_iter().collect(); - let mut parser = Parser::new(tokens); - match parser.parse() { - Ok(ast) => { - if let Err(e) = self.execute(ast) { - eprintln!("Error: {}", e); - } - } - Err(e) => eprintln!("Parse error: {}", e), - } - } - } - - pub fn execute(&mut self, nodes: Vec) -> Result<(), String> { - for node in nodes { - self.execute_node(node)?; - } - Ok(()) - } - - fn execute_node(&mut self, node: ASTNode) -> Result { - match node { - ASTNode::Command { name, args } => self.execute_command(name, args), - ASTNode::Assignment { name, value } => { - let expanded_value = self.expand_variables(&value); - self.variables.insert(name, expanded_value); - Ok(String::new()) - } - ASTNode::Pipeline(commands) => self.execute_pipeline(commands), - ASTNode::Redirect { - node, - direction, - target, - } => self.execute_redirect(*node, direction, target), - ASTNode::Block(nodes) => { - let mut last_output = String::new(); - for node in nodes { - last_output = self.execute_node(node)?; - } - Ok(last_output) - } - ASTNode::If { - condition, - then_block, - else_block, - } => self.execute_if(*condition, *then_block, else_block.map(|b| *b)), - ASTNode::While { condition, block } => self.execute_while(*condition, *block), - ASTNode::For { var, list, block } => self.execute_for(var, list, *block), - ASTNode::Function { name, body } => { - self.functions.insert(name, *body); - Ok(String::new()) - } - ASTNode::Background(node) => self.execute_background(*node), - } - } - - fn execute_command(&mut self, name: String, args: Vec) -> Result { - let expanded_args: Vec = - args.iter().map(|arg| self.expand_variables(arg)).collect(); - - let result = match name.as_str() { - "cd" => self.change_directory(&expanded_args), - "echo" => { - let output = expanded_args.join(" "); - println!("{}", output); - Ok(output) - } - "exit" => std::process::exit(0), - "write" => self.handle_write(&expanded_args), - "append" => self.handle_append(&expanded_args), - "read" => self.handle_read(&expanded_args), - "read_lines" => self.handle_read_lines(&expanded_args), - "delete" => self.handle_delete(&expanded_args), - _ => { - if let Some(function) = self.functions.get(&name) { - self.execute_node(function.clone()) - } else { - // Execute external command - let output = Command::new(&name) - .args(&expanded_args) - .output() - .map_err(|e| format!("Failed to execute command: {}", e))?; - - if output.status.success() { - Ok(String::from_utf8_lossy(&output.stdout).to_string()) - } else { - Err(String::from_utf8_lossy(&output.stderr).to_string()) - } - } - } - }; - - self.last_exit_status = if result.is_ok() { 0 } else { 1 }; - result - } - - fn change_directory(&self, args: &[String]) -> Result { - let new_dir = args.get(0).map(|s| s.as_str()).unwrap_or("~"); - let path = shellexpand::tilde(new_dir); - std::env::set_current_dir(path.as_ref()) - .map_err(|e| format!("Failed to change directory: {}", e))?; - Ok(String::new()) - } - - fn execute_pipeline(&mut self, commands: Vec) -> Result { - let mut last_output = Vec::new(); - - for (i, command) in commands.iter().enumerate() { - let mut child = match command { - ASTNode::Command { name, args } => { - let mut cmd = Command::new(name); - cmd.args(args); - - if i > 0 { - cmd.stdin(Stdio::piped()); - } - if i < commands.len() - 1 { - cmd.stdout(Stdio::piped()); - } - - cmd.spawn() - .map_err(|e| format!("Failed to spawn command: {}", e))? - } - _ => return Err("Invalid command in pipeline".to_string()), - }; - - if i > 0 { - if let Some(mut stdin) = child.stdin.take() { - stdin - .write_all(&last_output) - .map_err(|e| format!("Failed to write to stdin: {}", e))?; - } - } - - let output = child - .wait_with_output() - .map_err(|e| format!("Failed to wait for command: {}", e))?; - last_output = output.stdout; - } - - Ok(String::from_utf8_lossy(&last_output).to_string()) - } - - fn execute_redirect( - &mut self, - node: ASTNode, - direction: RedirectType, - target: String, - ) -> Result { - let output = self.execute_node(node)?; - - match direction { - RedirectType::Out => { - let mut file = - File::create(&target).map_err(|e| format!("Failed to create file: {}", e))?; - file.write_all(output.as_bytes()) - .map_err(|e| format!("Failed to write to file: {}", e))?; - } - RedirectType::Append => { - let mut file = OpenOptions::new() - .append(true) - .create(true) - .open(&target) - .map_err(|e| format!("Failed to open file: {}", e))?; - file.write_all(output.as_bytes()) - .map_err(|e| format!("Failed to append to file: {}", e))?; - } - RedirectType::In => { - let mut file = - File::open(&target).map_err(|e| format!("Failed to open file: {}", e))?; - let mut content = String::new(); - file.read_to_string(&mut content) - .map_err(|e| format!("Failed to read file: {}", e))?; - return Ok(content); - } - } - - Ok(String::new()) - } - - fn execute_if( - &mut self, - condition: ASTNode, - then_block: ASTNode, - else_block: Option, - ) -> Result { - let condition_result = self.execute_node(condition)?; - if !condition_result.trim().is_empty() && condition_result.trim() != "0" { - self.execute_node(then_block) - } else if let Some(else_block) = else_block { - self.execute_node(else_block) - } else { - Ok(String::new()) - } - } - - fn execute_while(&mut self, condition: ASTNode, block: ASTNode) -> Result { - let mut last_output = String::new(); - while { - let condition_result = self.execute_node(condition.clone())?; - !condition_result.trim().is_empty() && condition_result.trim() != "0" - } { - last_output = self.execute_node(block.clone())?; - } - Ok(last_output) - } - - fn execute_for( - &mut self, - var: String, - list: Vec, - block: ASTNode, - ) -> Result { - let mut last_output = String::new(); - for item in list { - self.variables.insert(var.clone(), item); - last_output = self.execute_node(block.clone())?; - } - Ok(last_output) - } - - fn execute_background(&mut self, node: ASTNode) -> Result { - std::thread::spawn(move || { - let mut executor = Executor::new(); - if let Err(e) = executor.execute_node(node) { - eprintln!("Background job error: {}", e); - } - }); - Ok(String::new()) - } - - fn expand_variables(&self, input: &str) -> String { - let mut result = String::new(); - let mut chars = input.chars().peekable(); - while let Some(c) = chars.next() { - if c == '$' { - let var_name: String = chars - .by_ref() - .take_while(|&c| c.is_alphanumeric() || c == '_') - .collect(); - if var_name == "?" { - result.push_str(&self.last_exit_status.to_string()); - } else if var_name == "#" { - // Assuming we don't have access to script arguments in this context - result.push_str("0"); - } else if let Some(value) = self.variables.get(&var_name) { - result.push_str(value); - } else if let Ok(value) = std::env::var(&var_name) { - result.push_str(&value); - } - } else { - result.push(c); - } - } - result - } - - // File handling methods - fn handle_write(&self, args: &[String]) -> Result { - if args.len() != 2 { - return Err("Usage: write ".to_string()); - } - let filename = &args[0]; - let content = &args[1]; - - std::fs::write(filename, content).map_err(|e| format!("Failed to write to file: {}", e))?; - Ok(format!("Successfully wrote to {}", filename)) - } - - fn handle_append(&self, args: &[String]) -> Result { - if args.len() != 2 { - return Err("Usage: append ".to_string()); - } - let filename = &args[0]; - let content = &args[1]; - - use std::fs::OpenOptions; - use std::io::Write; - - let mut file = OpenOptions::new() - .append(true) - .create(true) - .open(filename) - .map_err(|e| format!("Failed to open file for appending: {}", e))?; - - writeln!(file, "{}", content).map_err(|e| format!("Failed to append to file: {}", e))?; - Ok(format!("Successfully appended to {}", filename)) - } - - fn handle_read(&self, args: &[String]) -> Result { - if args.len() != 1 { - return Err("Usage: read ".to_string()); - } - let filename = &args[0]; - - let content = - std::fs::read_to_string(filename).map_err(|e| format!("Failed to read file: {}", e))?; - Ok(content) - } - - fn handle_read_lines(&self, args: &[String]) -> Result { - if args.len() != 1 { - return Err("Usage: read_lines ".to_string()); - } - self.handle_read(args) - } - - fn handle_delete(&self, args: &[String]) -> Result { - if args.len() != 1 { - return Err("Usage: delete ".to_string()); - } - let filename = &args[0]; - - std::fs::remove_file(filename).map_err(|e| format!("Failed to delete file: {}", e))?; - Ok(format!("Successfully deleted {}", filename)) - } -} diff --git a/src/executor/mod.rs b/src/executor/mod.rs deleted file mode 100644 index 0c95fda..0000000 --- a/src/executor/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod executor; diff --git a/src/executor_processes/executor.rs b/src/executor_processes/executor.rs new file mode 100644 index 0000000..dab6f21 --- /dev/null +++ b/src/executor_processes/executor.rs @@ -0,0 +1,115 @@ +// Copyright (C) 2024 Bellande Architecture Mechanism Research Innovation Center, Ronaldson Bellande + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::executor_processes::processes::Processes; +use crate::interpreter::interpreter::Interpreter; +use crate::lexer::lexer::Lexer; +use crate::parser::parser::Parser; +use crate::utilities::utilities::ASTNode; +use std::fs::File; +use std::io::{self, BufRead, BufReader, Write}; +use std::sync::Arc; + +pub struct Executor { + interpreter: Interpreter, + processes: Arc, +} + +impl Executor { + pub fn new() -> Self { + Executor { + interpreter: Interpreter::new(), + processes: Arc::new(Processes::new()), + } + } + + pub fn run(&mut self, args: Vec) -> Result<(), String> { + if args.len() > 1 { + // Execute script file + self.execute_script(&args[1]) + } else { + // Interactive mode + self.run_interactive_mode() + } + } + + fn execute_script(&mut self, filename: &str) -> Result<(), String> { + let file = + File::open(filename).map_err(|e| format!("Error opening file {}: {}", filename, e))?; + let reader = BufReader::new(file); + for line in reader.lines() { + let line = line.map_err(|e| format!("Error reading line: {}", e))?; + self.process_content(&line)?; + } + Ok(()) + } + + fn run_interactive_mode(&mut self) -> Result<(), String> { + loop { + print!("bellos> "); + io::stdout().flush().unwrap(); + let mut input = String::new(); + io::stdin().read_line(&mut input).unwrap(); + + if input.trim().is_empty() { + continue; + } + + if let Err(e) = self.process_content(&input) { + eprintln!("Error: {}", e); + } + } + } + + fn process_content(&mut self, content: &str) -> Result<(), String> { + let ast_nodes = self.parse_content(content)?; + self.execute(ast_nodes) + } + + fn parse_content(&self, content: &str) -> Result, String> { + let mut lexer = Lexer::new(content.to_string()); + let tokens = lexer.tokenize(); + let mut parser = Parser::new(tokens); + parser.parse() + } + + pub fn execute(&mut self, nodes: Vec) -> Result<(), String> { + for node in nodes { + self.execute_node(node)?; + } + Ok(()) + } + + fn execute_node(&mut self, node: ASTNode) -> Result, String> { + match node { + ASTNode::Command { name, args } => { + self.processes + .execute_command(&mut self.interpreter, name, args) + } + ASTNode::Pipeline(commands) => { + self.processes.execute_pipeline(&self.interpreter, commands) + } + ASTNode::Redirect { + node, + direction, + target, + } => self + .processes + .execute_redirect(&mut self.interpreter, *node, direction, target), + ASTNode::Background(node) => self.processes.execute_background(*node), + _ => self.interpreter.interpret_node(Box::new(node)), + } + } +} diff --git a/src/executor_processes/mod.rs b/src/executor_processes/mod.rs new file mode 100644 index 0000000..74b3c27 --- /dev/null +++ b/src/executor_processes/mod.rs @@ -0,0 +1,2 @@ +pub mod executor; +pub mod processes; diff --git a/src/executor_processes/processes.rs b/src/executor_processes/processes.rs new file mode 100644 index 0000000..8221316 --- /dev/null +++ b/src/executor_processes/processes.rs @@ -0,0 +1,266 @@ +// Copyright (C) 2024 Bellande Architecture Mechanism Research Innovation Center, Ronaldson Bellande + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::interpreter::interpreter::Interpreter; +use crate::utilities::utilities::{ASTNode, RedirectType}; +use glob::glob; +use std::fs::{File, OpenOptions}; +use std::io::{self, BufReader, BufWriter, Cursor, Read, Write}; +use std::process::{Child, Command, Stdio}; +use std::sync::{Arc, Mutex}; +use std::thread; + +pub struct Processes { + background_jobs: Arc>>>>, +} + +impl Processes { + pub fn new() -> Self { + Processes { + background_jobs: Arc::new(Mutex::new(Vec::new())), + } + } + + pub fn execute_command( + &self, + interpreter: &mut Interpreter, + name: String, + args: Vec, + ) -> Result, String> { + let expanded_name = interpreter.expand_variables(&name); + let expanded_args: Vec = args + .iter() + .map(|arg| interpreter.expand_variables(arg)) + .collect(); + + match expanded_name.as_str() { + "echo" => { + println!("{}", expanded_args.join(" ")); + Ok(Some(0)) + } + "cd" => { + let path = if expanded_args.is_empty() { + std::env::var("HOME").unwrap_or_else(|_| ".".to_string()) + } else { + expanded_args[0].clone() + }; + if let Err(e) = std::env::set_current_dir(&path) { + Err(format!("cd: {}", e)) + } else { + Ok(Some(0)) + } + } + "exit" => std::process::exit(0), + "export" => { + for arg in expanded_args { + let parts: Vec<&str> = arg.splitn(2, '=').collect(); + if parts.len() == 2 { + std::env::set_var(parts[0], parts[1]); + } + } + Ok(Some(0)) + } + "jobs" => { + let jobs = self.background_jobs.lock().unwrap(); + for (i, _) in jobs.iter().enumerate() { + println!("[{}] Running", i + 1); + } + Ok(Some(0)) + } + _ => { + if let Some(func) = interpreter.functions.get(&expanded_name) { + return interpreter.interpret_node(Box::new(func.clone())); + } + match Command::new(&expanded_name).args(&expanded_args).spawn() { + Ok(mut child) => { + let status = child.wait().map_err(|e| e.to_string())?; + Ok(Some(status.code().unwrap_or(0))) + } + Err(e) => Err(format!("Failed to execute command: {}", e)), + } + } + } + } + + pub fn execute_pipeline( + &self, + interpreter: &Interpreter, + commands: Vec, + ) -> Result, String> { + let mut previous_stdout = None; + let mut processes = Vec::new(); + + for (i, command) in commands.iter().enumerate() { + match command { + ASTNode::Command { name, args } => { + let mut cmd = Command::new(interpreter.expand_variables(name)); + for arg in args { + cmd.arg(interpreter.expand_variables(arg)); + } + + if let Some(prev_stdout) = previous_stdout.take() { + cmd.stdin(prev_stdout); + } + + if i < commands.len() - 1 { + cmd.stdout(Stdio::piped()); + } + + let mut child = cmd.spawn().map_err(|e| e.to_string())?; + if i < commands.len() - 1 { + previous_stdout = child.stdout.take(); + } + processes.push(child); + } + _ => return Err("Pipeline can only contain commands".to_string()), + } + } + + let mut last_status = None; + for mut process in processes { + let status = process.wait().map_err(|e| e.to_string())?; + last_status = Some(status.code().unwrap_or(0)); + } + + Ok(last_status) + } + + pub fn execute_redirect( + &self, + interpreter: &mut Interpreter, + node: ASTNode, + direction: RedirectType, + target: String, + ) -> Result, String> { + let target = interpreter.expand_variables(&target); + match direction { + RedirectType::Out => { + let file = File::create(&target).map_err(|e| e.to_string())?; + let mut writer = BufWriter::new(file); + let result = self.capture_output(interpreter, Box::new(node))?; + writer + .write_all(result.as_bytes()) + .map_err(|e| e.to_string())?; + Ok(Some(0)) + } + RedirectType::Append => { + let file = OpenOptions::new() + .write(true) + .append(true) + .create(true) + .open(&target) + .map_err(|e| e.to_string())?; + let mut writer = BufWriter::new(file); + let result = self.capture_output(interpreter, Box::new(node))?; + writer + .write_all(result.as_bytes()) + .map_err(|e| e.to_string())?; + Ok(Some(0)) + } + RedirectType::In => { + let file = File::open(&target).map_err(|e| e.to_string())?; + let mut reader = BufReader::new(file); + let mut input = String::new(); + reader + .read_to_string(&mut input) + .map_err(|e| e.to_string())?; + self.execute_with_input(interpreter, Box::new(node), input) + } + } + } + + fn capture_output( + &self, + interpreter: &mut Interpreter, + node: Box, + ) -> Result { + let old_stdout = io::stdout(); + let mut handle = old_stdout.lock(); + let mut buffer = Vec::new(); + { + let mut cursor = Cursor::new(&mut buffer); + let result = interpreter.interpret_node(node)?; + writeln!(cursor, "{:?}", result).map_err(|e| e.to_string())?; + } + handle.write_all(&buffer).map_err(|e| e.to_string())?; + String::from_utf8(buffer).map_err(|e| e.to_string()) + } + + fn execute_with_input( + &self, + interpreter: &mut Interpreter, + node: Box, + input: String, + ) -> Result, String> { + std::env::set_var("BELLOS_INPUT", input); + interpreter.interpret_node(node) + } + + pub fn execute_background(&self, node: ASTNode) -> Result, String> { + let bg_jobs = Arc::clone(&self.background_jobs); + + // Create a new background process + let child = Arc::new(Mutex::new( + Command::new(std::env::current_exe().expect("Failed to get current executable path")) + .arg("--execute-bellos-script") + .stdin(Stdio::piped()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .map_err(|e| format!("Failed to spawn background process: {}", e))?, + )); + + // Add the new job to the list + bg_jobs.lock().unwrap().push(Arc::clone(&child)); + + thread::spawn(move || { + let mut interpreter = Interpreter::new(); + if let Err(e) = interpreter.interpret_node(Box::new(node)) { + eprintln!("Background job error: {}", e); + } + + let mut jobs = bg_jobs.lock().unwrap(); + jobs.retain(|job| { + let mut child = job.lock().unwrap(); + match child.try_wait() { + Ok(Some(_)) => { + println!("Job completed."); + false // Job has completed, remove it + } + Ok(None) => { + println!("Job still running."); + true // Job is still running, keep it + } + Err(err) => { + eprintln!("Error waiting for job: {}", err); + false // Error occurred, remove the job + } + } + }); + }); + + Ok(None) + } + + pub fn expand_wildcards(&self, pattern: &str) -> Vec { + match glob(pattern) { + Ok(paths) => paths + .filter_map(Result::ok) + .map(|path| path.to_string_lossy().into_owned()) + .collect(), + Err(_) => vec![pattern.to_string()], + } + } +} diff --git a/src/interpreter/interpreter.rs b/src/interpreter/interpreter.rs index 468d4dc..d8c4cf4 100644 --- a/src/interpreter/interpreter.rs +++ b/src/interpreter/interpreter.rs @@ -13,21 +13,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::utilities::utilities::{ASTNode, RedirectType}; -use glob::glob; +use crate::utilities::utilities::ASTNode; use std::collections::HashMap; use std::env; -use std::fs::{File, OpenOptions}; -use std::io::{self, Read, Write}; -use std::os::unix::io::AsRawFd; -use std::process::{Child, Command, Stdio}; -use std::sync::{Arc, Mutex}; -use std::thread; pub struct Interpreter { - variables: HashMap, - functions: HashMap, - background_jobs: Arc>>, + pub variables: HashMap, + pub functions: HashMap, } impl Interpreter { @@ -35,7 +27,6 @@ impl Interpreter { Interpreter { variables: HashMap::new(), functions: HashMap::new(), - background_jobs: Arc::new(Mutex::new(Vec::new())), } } @@ -46,20 +37,13 @@ impl Interpreter { Ok(()) } - fn interpret_node(&mut self, node: Box) -> Result, String> { + pub fn interpret_node(&mut self, node: Box) -> Result, String> { match *node { - ASTNode::Command { name, args } => self.execute_command(name, args), ASTNode::Assignment { name, value } => { let expanded_value = self.expand_variables(&value); self.variables.insert(name, expanded_value); Ok(None) } - ASTNode::Pipeline(commands) => self.execute_pipeline(commands), - ASTNode::Redirect { - node, - direction, - target, - } => self.execute_redirect(*node, direction, target), ASTNode::Block(statements) => { for statement in statements { self.interpret_node(Box::new(statement))?; @@ -95,72 +79,76 @@ impl Interpreter { self.functions.insert(name, *body); Ok(None) } - ASTNode::Background(node) => { - let bg_jobs = Arc::clone(&self.background_jobs); - thread::spawn(move || { - let mut interpreter = Interpreter::new(); - interpreter.background_jobs = bg_jobs; - if let Err(e) = interpreter.interpret_node(node) { - eprintln!("Background job error: {}", e); - } - }); - Ok(None) - } + _ => Err("Node type not handled by Interpreter".to_string()), } } - fn execute_command(&mut self, name: String, args: Vec) -> Result, String> { - let expanded_name = self.expand_variables(&name); - let expanded_args: Vec = - args.iter().map(|arg| self.expand_variables(arg)).collect(); + pub fn evaluate_condition(&mut self, condition: &ASTNode) -> Result { + match condition { + ASTNode::Command { name, args } => { + let expanded_args: Vec = + args.iter().map(|arg| self.expand_variables(arg)).collect(); + match name.as_str() { + "[" | "test" => { + if expanded_args.len() < 3 || expanded_args.last() != Some(&"]".to_string()) + { + return Err("Invalid test condition".to_string()); + } + match expanded_args[1].as_str() { + "-eq" => Ok(expanded_args[0] == expanded_args[2]), + "-ne" => Ok(expanded_args[0] != expanded_args[2]), + "-lt" => Ok(expanded_args[0].parse::().unwrap_or(0) + < expanded_args[2].parse::().unwrap_or(0)), + "-le" => Ok(expanded_args[0].parse::().unwrap_or(0) + <= expanded_args[2].parse::().unwrap_or(0)), + "-gt" => Ok(expanded_args[0].parse::().unwrap_or(0) + > expanded_args[2].parse::().unwrap_or(0)), + "-ge" => Ok(expanded_args[0].parse::().unwrap_or(0) + >= expanded_args[2].parse::().unwrap_or(0)), + "-z" => Ok(expanded_args[0].is_empty()), + "-n" => Ok(!expanded_args[0].is_empty()), + _ => Err(format!("Unsupported test condition: {}", expanded_args[1])), + } + } + _ => Err("Condition evaluation not supported for this command".to_string()), + } + } + _ => Err("Invalid condition node".to_string()), + } + } - match expanded_name.as_str() { - "echo" => { - println!("{}", expanded_args.join(" ")); - Ok(Some(0)) - } - "cd" => { - let path = if expanded_args.is_empty() { - env::var("HOME").unwrap_or_else(|_| ".".to_string()) + pub fn expand_variables(&self, input: &str) -> String { + let mut result = String::new(); + let mut chars = input.chars().peekable(); + while let Some(c) = chars.next() { + if c == '$' { + if chars.peek() == Some(&'(') { + chars.next(); // consume '(' + let expr: String = chars.by_ref().take_while(|&c| c != ')').collect(); + if expr.starts_with('(') && expr.ends_with(')') { + // Arithmetic expression + let arithmetic_expr = &expr[1..expr.len() - 1]; + match self.evaluate_arithmetic(arithmetic_expr) { + Ok(value) => result.push_str(&value.to_string()), + Err(e) => result.push_str(&format!("Error: {}", e)), + } + } } else { - expanded_args[0].clone() - }; - if let Err(e) = env::set_current_dir(&path) { - Err(format!("cd: {}", e)) - } else { - Ok(Some(0)) - } - } - "exit" => std::process::exit(0), - "export" => { - for arg in expanded_args { - let parts: Vec<&str> = arg.splitn(2, '=').collect(); - if parts.len() == 2 { - env::set_var(parts[0], parts[1]); + let var_name: String = chars + .by_ref() + .take_while(|&c| c.is_alphanumeric() || c == '_') + .collect(); + if let Some(value) = self.variables.get(&var_name) { + result.push_str(value); + } else if let Ok(value) = env::var(&var_name) { + result.push_str(&value); } } - Ok(Some(0)) - } - "jobs" => { - let jobs = self.background_jobs.lock().unwrap(); - for (i, _) in jobs.iter().enumerate() { - println!("[{}] Running", i + 1); - } - Ok(Some(0)) - } - _ => { - if let Some(func) = self.functions.get(&expanded_name) { - return self.interpret_node(Box::new(func.clone())); - } - match Command::new(&expanded_name).args(&expanded_args).spawn() { - Ok(mut child) => { - let status = child.wait().map_err(|e| e.to_string())?; - Ok(Some(status.code().unwrap_or(0))) - } - Err(e) => Err(format!("Failed to execute command: {}", e)), - } + } else { + result.push(c); } } + result } fn evaluate_arithmetic(&self, expr: &str) -> Result { @@ -208,201 +196,4 @@ impl Interpreter { .map_err(|_| format!("Invalid integer or undefined variable: {}", var)) } } - - fn evaluate_condition(&mut self, condition: &ASTNode) -> Result { - match condition { - ASTNode::Command { name, args } => { - let expanded_args: Vec = - args.iter().map(|arg| self.expand_variables(arg)).collect(); - match name.as_str() { - "[" | "test" => { - if expanded_args.len() < 3 || expanded_args.last() != Some(&"]".to_string()) - { - return Err("Invalid test condition".to_string()); - } - match expanded_args[1].as_str() { - "-eq" => Ok(expanded_args[0] == expanded_args[2]), - "-ne" => Ok(expanded_args[0] != expanded_args[2]), - "-lt" => Ok(expanded_args[0].parse::().unwrap_or(0) - < expanded_args[2].parse::().unwrap_or(0)), - "-le" => Ok(expanded_args[0].parse::().unwrap_or(0) - <= expanded_args[2].parse::().unwrap_or(0)), - "-gt" => Ok(expanded_args[0].parse::().unwrap_or(0) - > expanded_args[2].parse::().unwrap_or(0)), - "-ge" => Ok(expanded_args[0].parse::().unwrap_or(0) - >= expanded_args[2].parse::().unwrap_or(0)), - "-z" => Ok(expanded_args[0].is_empty()), - "-n" => Ok(!expanded_args[0].is_empty()), - _ => Err(format!("Unsupported test condition: {}", expanded_args[1])), - } - } - _ => { - let result = self.execute_command(name.clone(), expanded_args)?; - Ok(result == Some(0)) - } - } - } - _ => Err("Invalid condition node".to_string()), - } - } - - fn execute_pipeline(&mut self, commands: Vec) -> Result, String> { - let mut previous_stdout = None; - let mut processes = Vec::new(); - - for (i, command) in commands.iter().enumerate() { - match command { - ASTNode::Command { name, args } => { - let mut cmd = Command::new(self.expand_variables(name)); - for arg in args { - cmd.arg(self.expand_variables(arg)); - } - - if let Some(prev_stdout) = previous_stdout.take() { - cmd.stdin(prev_stdout); - } - - if i < commands.len() - 1 { - cmd.stdout(Stdio::piped()); - } - - let mut child = cmd.spawn().map_err(|e| e.to_string())?; - if i < commands.len() - 1 { - previous_stdout = child.stdout.take(); - } - processes.push(child); - } - _ => return Err("Pipeline can only contain commands".to_string()), - } - } - - let mut last_status = None; - for mut process in processes { - let status = process.wait().map_err(|e| e.to_string())?; - last_status = Some(status.code().unwrap_or(0)); - } - - Ok(last_status) - } - - fn execute_redirect( - &mut self, - node: ASTNode, - direction: RedirectType, - target: String, - ) -> Result, String> { - let target = self.expand_variables(&target); - match direction { - RedirectType::Out => { - let mut file = File::create(&target).map_err(|e| e.to_string())?; - let result = self.capture_output(Box::new(node))?; - file.write_all(result.as_bytes()) - .map_err(|e| e.to_string())?; - Ok(Some(0)) - } - RedirectType::Append => { - let mut file = OpenOptions::new() - .write(true) - .append(true) - .create(true) - .open(&target) - .map_err(|e| e.to_string())?; - let result = self.capture_output(Box::new(node))?; - file.write_all(result.as_bytes()) - .map_err(|e| e.to_string())?; - Ok(Some(0)) - } - RedirectType::In => { - let mut file = File::open(&target).map_err(|e| e.to_string())?; - let mut input = String::new(); - file.read_to_string(&mut input).map_err(|e| e.to_string())?; - self.execute_with_input(Box::new(node), input) - } - } - } - - fn capture_output(&mut self, node: Box) -> Result { - let old_stdout = io::stdout(); - let mut handle = old_stdout.lock(); - let mut buffer = Vec::new(); - { - let mut cursor = io::Cursor::new(&mut buffer); - let result = self.interpret_node(node)?; - write!(cursor, "{:?}", result).map_err(|e| e.to_string())?; - } - handle.write_all(&buffer).map_err(|e| e.to_string())?; - String::from_utf8(buffer).map_err(|e| e.to_string()) - } - - fn execute_with_input( - &mut self, - node: Box, - input: String, - ) -> Result, String> { - let mut temp_file = tempfile::NamedTempFile::new().map_err(|e| e.to_string())?; - temp_file - .write_all(input.as_bytes()) - .map_err(|e| e.to_string())?; - temp_file.flush().map_err(|e| e.to_string())?; - - let input_file = File::open(temp_file.path()).map_err(|e| e.to_string())?; - - let stdin = io::stdin(); - let old_stdin = stdin.lock(); - let new_stdin = unsafe { - use std::os::unix::io::FromRawFd; - std::fs::File::from_raw_fd(input_file.as_raw_fd()) - }; - - let result = self.interpret_node(node); - - drop(new_stdin); - drop(old_stdin); - - result - } - - fn expand_variables(&self, input: &str) -> String { - let mut result = String::new(); - let mut chars = input.chars().peekable(); - while let Some(c) = chars.next() { - if c == '$' { - if chars.peek() == Some(&'(') { - chars.next(); // consume '(' - let expr: String = chars.by_ref().take_while(|&c| c != ')').collect(); - if expr.starts_with('(') && expr.ends_with(')') { - // Arithmetic expression - let arithmetic_expr = &expr[1..expr.len() - 1]; - match self.evaluate_arithmetic(arithmetic_expr) { - Ok(value) => result.push_str(&value.to_string()), - Err(e) => result.push_str(&format!("Error: {}", e)), - } - } - } else { - let var_name: String = chars - .by_ref() - .take_while(|&c| c.is_alphanumeric() || c == '_') - .collect(); - if let Some(value) = self.variables.get(&var_name) { - result.push_str(value); - } else if let Ok(value) = env::var(&var_name) { - result.push_str(&value); - } - } - } else { - result.push(c); - } - } - result - } - - fn expand_wildcards(&self, pattern: &str) -> Vec { - match glob(pattern) { - Ok(paths) => paths - .filter_map(Result::ok) - .map(|path| path.to_string_lossy().into_owned()) - .collect(), - Err(_) => vec![pattern.to_string()], - } - } } diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 118402b..36e9d6f 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -13,7 +13,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::utilities::utilities::{ASTNode, RedirectType, Token}; +use crate::utilities::utilities::{ASTNode, Token}; pub struct Parser { tokens: Vec, @@ -91,18 +91,7 @@ impl Parser { if self.position < self.tokens.len() && self.tokens[self.position] == Token::Assignment { self.position += 1; - let value = if self.position < self.tokens.len() { - match &self.tokens[self.position] { - Token::Word(w) => w.clone(), - Token::String(s) => s.clone(), - _ => String::new(), // Allow empty assignments - } - } else { - String::new() - }; - if self.position < self.tokens.len() { - self.position += 1; - } + let value = self.parse_expression()?; Ok(ASTNode::Assignment { name, value }) } else { let mut args = Vec::new(); @@ -129,6 +118,41 @@ impl Parser { } } + fn parse_expression(&mut self) -> Result { + let mut expression = String::new(); + let mut paren_count = 0; + + while self.position < self.tokens.len() { + match &self.tokens[self.position] { + Token::Word(w) => expression.push_str(w), + Token::String(s) => { + expression.push('"'); + expression.push_str(s); + expression.push('"'); + } + Token::LeftParen => { + expression.push('('); + paren_count += 1; + } + Token::RightParen if paren_count > 0 => { + expression.push(')'); + paren_count -= 1; + } + Token::Semicolon | Token::NewLine if paren_count == 0 => break, + Token::Assignment => expression.push('='), + _ if paren_count == 0 => break, + _ => expression.push_str(&format!("{:?}", self.tokens[self.position])), + } + self.position += 1; + } + + if paren_count != 0 { + return Err("Mismatched parentheses in expression".to_string()); + } + + Ok(expression) + } + fn parse_pipeline_or_redirect(&mut self, left: ASTNode) -> Result { if self.position >= self.tokens.len() { return Ok(left);