diff --git a/bellos_scripts/string_manipulation.bellos b/bellos_scripts/string_manipulation.bellos index 14907c9..b0b1129 100644 --- a/bellos_scripts/string_manipulation.bellos +++ b/bellos_scripts/string_manipulation.bellos @@ -4,66 +4,33 @@ # Demonstrating string manipulation # String concatenation -str1="Hello" -str2="World" -concat="$str1 $str2" -echo "Concatenated string: $concat" +first_name=John +last_name=Doe +full_name=$first_name $last_name +echo Full name: $full_name # String length -echo "Length of '$concat': ${#concat}" +string="Hello, World!" +echo "The string '$string' has ${#string} characters." # Substring extraction -echo "Substring (index 0-4): ${concat:0:5}" -echo "Substring (from index 6): ${concat:6}" +echo "The first 5 characters are: ${string:0:5}" # String replacement sentence="The quick brown fox jumps over the lazy dog" echo "Original sentence: $sentence" -replaced=${sentence/fox/cat} -echo "After replacing 'fox' with 'cat': $replaced" +new_sentence=${sentence/fox/cat} +echo "Modified sentence: $new_sentence" -# Replace all occurrences -many_the="the the the dog the cat the mouse" -replaced_all=${many_the//the/a} -echo "Replace all 'the' with 'a': $replaced_all" +# Converting to uppercase and lowercase +echo "Uppercase: ${string^^}" +echo "Lowercase: ${string,,}" -# String to uppercase -uppercase=${sentence^^} -echo "Uppercase: $uppercase" - -# String to lowercase -lowercase=${sentence,,} -echo "Lowercase: $lowercase" - -# Check if string contains substring -if [[ $sentence == *"fox"* ]]; then - echo "The sentence contains 'fox'" -else - echo "The sentence does not contain 'fox'" -fi - -# Split string into array -IFS=' ' read -ra words <<< "$sentence" -echo "Words in the sentence:" -for word in "${words[@]}"; do - echo " $word" -done - -# Join array elements into string -joined=$(IFS=", "; echo "${words[*]}") -echo "Joined words: $joined" - -# Trim whitespace -whitespace_string=" trim me " -trimmed_string=$(echo $whitespace_string | xargs) -echo "Original string: '$whitespace_string'" +# Trimming whitespace +padded_string=" trim me " +echo "Original string: '$padded_string'" +trimmed_string=${padded_string## } +trimmed_string=${trimmed_string%% } echo "Trimmed string: '$trimmed_string'" -# String comparison -str_a="apple" -str_b="banana" -if [[ $str_a < $str_b ]]; then - echo "$str_a comes before $str_b alphabetically" -else - echo "$str_b comes before $str_a alphabetically" -fi +echo "String manipulation operations completed." diff --git a/executable/bellos b/executable/bellos index 97e4141..c1e5d1c 100755 Binary files a/executable/bellos and b/executable/bellos differ diff --git a/src/bellos.rs b/src/bellos.rs index e34b188..2786f4c 100644 --- a/src/bellos.rs +++ b/src/bellos.rs @@ -23,6 +23,8 @@ use crate::executor_processes::executor::Executor; fn main() { let args: Vec = std::env::args().collect(); + println!("Arguments: {:?}", args); + let mut executor = Executor::new(); if let Err(e) = executor.run(args) { eprintln!("Application error: {}", e); diff --git a/src/executor_processes/executor.rs b/src/executor_processes/executor.rs index dab6f21..7f3388e 100644 --- a/src/executor_processes/executor.rs +++ b/src/executor_processes/executor.rs @@ -20,6 +20,7 @@ use crate::parser::parser::Parser; use crate::utilities::utilities::ASTNode; use std::fs::File; use std::io::{self, BufRead, BufReader, Write}; +use std::path::Path; use std::sync::Arc; pub struct Executor { @@ -46,12 +47,53 @@ impl Executor { } fn execute_script(&mut self, filename: &str) -> Result<(), String> { + println!("Executing script: {}", filename); + + if !filename.ends_with(".bellos") { + return Err(format!("Not a .bellos script: {}", filename)); + } + + let path = Path::new(filename); + if !path.exists() { + return Err(format!("Script file does not exist: {}", filename)); + } + let file = - File::open(filename).map_err(|e| format!("Error opening file {}: {}", filename, e))?; + File::open(path).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)?; + + for (index, line) in reader.lines().enumerate() { + let line = line.map_err(|e| format!("Error reading line {}: {}", index + 1, e))?; + let trimmed_line = line.trim(); + if trimmed_line.is_empty() || trimmed_line.starts_with('#') { + continue; // Skip empty lines and comments + } + + // Handle variable assignments and arithmetic operations + if trimmed_line.contains('=') { + let parts: Vec<&str> = trimmed_line.splitn(2, '=').collect(); + if parts.len() == 2 { + let var_name = parts[0].trim().to_string(); + let var_value = parts[1].trim().to_string(); + + if var_value.starts_with("$((") && var_value.ends_with("))") { + // Arithmetic expression + let result = self.interpreter.evaluate_arithmetic(&var_value)?; + self.interpreter + .variables + .insert(var_name, result.to_string()); + } else { + // Regular variable assignment + let expanded_value = self.interpreter.expand_variables(&var_value); + self.interpreter.variables.insert(var_name, expanded_value); + } + continue; + } + } + + if let Err(e) = self.process_content(trimmed_line) { + return Err(format!("Error on line {}: {}", index + 1, e)); + } } Ok(()) } @@ -74,6 +116,28 @@ impl Executor { } fn process_content(&mut self, content: &str) -> Result<(), String> { + // Handle variable assignments and arithmetic operations + if content.contains('=') { + let parts: Vec<&str> = content.splitn(2, '=').collect(); + if parts.len() == 2 { + let var_name = parts[0].trim().to_string(); + let var_value = parts[1].trim().to_string(); + + if var_value.starts_with("$((") && var_value.ends_with("))") { + // Arithmetic expression + let result = self.interpreter.evaluate_arithmetic(&var_value)?; + self.interpreter + .variables + .insert(var_name, result.to_string()); + } else { + // Regular variable assignment + let expanded_value = self.interpreter.expand_variables(&var_value); + self.interpreter.variables.insert(var_name, expanded_value); + } + return Ok(()); + } + } + let ast_nodes = self.parse_content(content)?; self.execute(ast_nodes) } @@ -94,10 +158,9 @@ impl Executor { 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::Command { name, args } => Arc::get_mut(&mut self.processes) + .unwrap() + .execute_command(&mut self.interpreter, name, args), ASTNode::Pipeline(commands) => { self.processes.execute_pipeline(&self.interpreter, commands) } @@ -105,9 +168,12 @@ impl Executor { node, direction, target, - } => self - .processes - .execute_redirect(&mut self.interpreter, *node, direction, target), + } => Arc::get_mut(&mut self.processes).unwrap().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/processes.rs b/src/executor_processes/processes.rs index ae5e66a..8345099 100644 --- a/src/executor_processes/processes.rs +++ b/src/executor_processes/processes.rs @@ -14,13 +14,10 @@ // 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}; use glob::glob; use std::fs::{File, OpenOptions}; use std::io::{self, BufReader, BufWriter, Cursor, Read, Write}; -use std::path::Path; use std::process::{Child, Command, Stdio}; use std::sync::{Arc, Mutex}; use std::thread; @@ -37,7 +34,7 @@ impl Processes { } pub fn execute_command( - &self, + &mut self, interpreter: &mut Interpreter, name: String, args: Vec, @@ -48,9 +45,30 @@ impl Processes { .map(|arg| interpreter.expand_variables(arg)) .collect(); + // Handle arithmetic assignments + if expanded_name.contains('=') { + let parts: Vec<&str> = expanded_name.splitn(2, '=').collect(); + if parts.len() == 2 { + let var_name = parts[0].trim().to_string(); + let var_value = parts[1].trim().to_string(); + + if var_value.starts_with("$((") && var_value.ends_with("))") { + // Arithmetic expression + let result = interpreter.evaluate_arithmetic(&var_value)?; + interpreter.variables.insert(var_name, result.to_string()); + } else { + // Regular variable assignment + let expanded_value = interpreter.expand_variables(&var_value); + interpreter.variables.insert(var_name, expanded_value); + } + return Ok(Some(0)); + } + } + match expanded_name.as_str() { "echo" => { - println!("{}", expanded_args.join(" ")); + let output = expanded_args.join(" "); + println!("{}", output); Ok(Some(0)) } "cd" => { @@ -82,39 +100,60 @@ impl Processes { } Ok(Some(0)) } + "write" => { + if expanded_args.len() != 2 { + return Err("Usage: write ".to_string()); + } + let filename = &expanded_args[0]; + let content = &expanded_args[1]; + let mut file = File::create(filename) + .map_err(|e| format!("Failed to create file {}: {}", filename, e))?; + file.write_all(content.as_bytes()) + .map_err(|e| format!("Failed to write to file {}: {}", filename, e))?; + Ok(Some(0)) + } + "read" => { + if expanded_args.len() != 1 { + return Err("Usage: read ".to_string()); + } + let filename = &expanded_args[0]; + let mut content = String::new(); + File::open(filename) + .map_err(|e| format!("Failed to open file {}: {}", filename, e))? + .read_to_string(&mut content) + .map_err(|e| format!("Failed to read file {}: {}", filename, e))?; + println!("{}", content); + Ok(Some(0)) + } + "append" => { + if expanded_args.len() != 2 { + return Err("Usage: append ".to_string()); + } + let filename = &expanded_args[0]; + let content = &expanded_args[1]; + let mut file = OpenOptions::new() + .append(true) + .open(filename) + .map_err(|e| format!("Failed to open file {}: {}", filename, e))?; + file.write_all(content.as_bytes()) + .map_err(|e| format!("Failed to append to file {}: {}", filename, e))?; + Ok(Some(0)) + } + "delete" => { + if expanded_args.len() != 1 { + return Err("Usage: delete ".to_string()); + } + let filename = &expanded_args[0]; + std::fs::remove_file(filename) + .map_err(|e| format!("Failed to delete file {}: {}", filename, e))?; + Ok(Some(0)) + } _ => { if let Some(func) = interpreter.functions.get(&expanded_name) { return interpreter.interpret_node(Box::new(func.clone())); } - // Check if the command is a .bellos script - if expanded_name.ends_with(".bellos") { - let path = Path::new(&expanded_name); - if path.exists() { - // Read the script file - let mut file = File::open(path) - .map_err(|e| format!("Failed to open script file: {}", e))?; - let mut content = String::new(); - file.read_to_string(&mut content) - .map_err(|e| format!("Failed to read script file: {}", e))?; - - // Parse and execute the script content - let mut lexer = Lexer::new(content); - let tokens = lexer.tokenize(); - let mut parser = Parser::new(tokens); - let ast = parser - .parse() - .map_err(|e| format!("Failed to parse script: {}", e))?; - - for node in ast { - interpreter.interpret_node(Box::new(node))?; - } - - return Ok(Some(0)); - } - } - - // If it's not a .bellos script, try to execute it as a system command + // If it's not a built-in command or variable assignment, try to execute as external command match Command::new(&expanded_name).args(&expanded_args).spawn() { Ok(mut child) => { let status = child.wait().map_err(|e| e.to_string())?; diff --git a/src/interpreter/interpreter.rs b/src/interpreter/interpreter.rs index d8c4cf4..7db7d1d 100644 --- a/src/interpreter/interpreter.rs +++ b/src/interpreter/interpreter.rs @@ -79,6 +79,9 @@ impl Interpreter { self.functions.insert(name, *body); Ok(None) } + ASTNode::Command { name: _, args: _ } => { + Err("Commands should be handled by Processes".to_string()) + } _ => Err("Node type not handled by Interpreter".to_string()), } } @@ -123,15 +126,27 @@ impl Interpreter { 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) { + let mut depth = 0; + let mut expr = String::new(); + for c in chars.by_ref() { + expr.push(c); + if c == '(' { + depth += 1; + } else if c == ')' { + depth -= 1; + if depth == 0 { + break; + } + } + } + if expr.starts_with("((") && expr.ends_with("))") { + match self.evaluate_arithmetic(&expr) { Ok(value) => result.push_str(&value.to_string()), Err(e) => result.push_str(&format!("Error: {}", e)), } + } else { + result.push('$'); + result.push_str(&expr); } } else { let var_name: String = chars @@ -142,6 +157,9 @@ impl Interpreter { result.push_str(value); } else if let Ok(value) = env::var(&var_name) { result.push_str(&value); + } else { + result.push('$'); + result.push_str(&var_name); } } } else { @@ -151,34 +169,81 @@ impl Interpreter { result } - fn evaluate_arithmetic(&self, expr: &str) -> Result { - let tokens: Vec<&str> = expr.split_whitespace().collect(); - if tokens.len() != 3 { - return Err("Invalid arithmetic expression".to_string()); + pub fn evaluate_arithmetic(&self, expr: &str) -> Result { + let expr = expr.trim(); + let inner_expr = if expr.starts_with("$((") && expr.ends_with("))") { + &expr[3..expr.len() - 2] + } else if expr.starts_with("((") && expr.ends_with("))") { + &expr[2..expr.len() - 2] + } else { + expr + }; + + // Handle parentheses + if inner_expr.contains('(') { + let mut depth = 0; + let mut start = 0; + for (i, c) in inner_expr.chars().enumerate() { + match c { + '(' => { + if depth == 0 { + start = i + 1; + } + depth += 1; + } + ')' => { + depth -= 1; + if depth == 0 { + let sub_result = self.evaluate_arithmetic(&inner_expr[start..i])?; + let new_expr = format!( + "{} {} {}", + &inner_expr[..start - 1], + sub_result, + &inner_expr[i + 1..] + ); + return self.evaluate_arithmetic(&new_expr); + } + } + _ => {} + } + } } - let a = self.get_var_value(tokens[0])?; - let b = self.get_var_value(tokens[2])?; + // Split the expression into tokens + let tokens: Vec<&str> = inner_expr.split_whitespace().collect(); - match tokens[1] { - "+" => Ok(a + b), - "-" => Ok(a - b), - "*" => Ok(a * b), - "/" => { - if b != 0 { - Ok(a / b) - } else { - Err("Division by zero".to_string()) + // Handle single number or variable + if tokens.len() == 1 { + return self.get_var_value(tokens[0]); + } + + // Handle binary operations + if tokens.len() == 3 { + let a = self.get_var_value(tokens[0])?; + let b = self.get_var_value(tokens[2])?; + + match tokens[1] { + "+" => Ok(a + b), + "-" => Ok(a - b), + "*" => Ok(a * b), + "/" => { + if b != 0 { + Ok(a / b) + } else { + Err("Division by zero".to_string()) + } } - } - "%" => { - if b != 0 { - Ok(a % b) - } else { - Err("Modulo by zero".to_string()) + "%" => { + if b != 0 { + Ok(a % b) + } else { + Err("Modulo by zero".to_string()) + } } + _ => Err(format!("Unsupported operation: {}", tokens[1])), } - _ => Err(format!("Unsupported operation: {}", tokens[1])), + } else { + Err("Invalid arithmetic expression".to_string()) } } @@ -187,13 +252,10 @@ impl Interpreter { value .parse() .map_err(|_| format!("Invalid integer: {}", value)) - } else if let Ok(value) = env::var(var) { - value - .parse() - .map_err(|_| format!("Invalid integer: {}", value)) + } else if let Ok(value) = var.parse() { + Ok(value) } else { - var.parse() - .map_err(|_| format!("Invalid integer or undefined variable: {}", var)) + Err(format!("Undefined variable or invalid integer: {}", var)) } } }