diff --git a/bellos_scripts/control_structures.bellos b/bellos_scripts/control_structures.bellos index 82df9d4..46498e4 100644 --- a/bellos_scripts/control_structures.bellos +++ b/bellos_scripts/control_structures.bellos @@ -1,52 +1,80 @@ #!/usr/bin/env bellos -# File: control_structures.bellos +# File: control_structures_with_seq.bellos -# Demonstrating if-else statements +# 1. Simple echo and variable assignment echo "Demonstrating if-else statements:" x=10 -If [ $x -gt 5 ] Then - echo "x is greater than 5" -Else - echo "x is not greater than 5" -Fi -# Demonstrating nested if-else +# 2. If-else statement +if [ $x -gt 5 ] +then + echo "x is greater than 5" +else + echo "x is not greater than 5" +fi + +# 3. Nested if-else echo "Demonstrating nested if-else:" y=20 -If [ $x -gt 5 ] Then - If [ $y -gt 15 ] Then +if [ $x -gt 5 ] +then + if [ $y -gt 15 ] + then echo "x is greater than 5 and y is greater than 15" - Else + else echo "x is greater than 5 but y is not greater than 15" - Fi -Else + fi +else echo "x is not greater than 5" -Fi +fi -# Demonstrating while loop +# 4. While loop echo "Demonstrating while loop:" counter=0 -While [ $counter -lt 5 ] Do +while [ $counter -lt 5 ] +do echo "Counter: $counter" counter=$((counter + 1)) -Done +done -# Demonstrating for loop +# 5. For loop echo "Demonstrating for loop:" -For i In 1 2 3 4 5 Do +for i in 1 2 3 4 5 +do echo "Iteration: $i" -Done +done -# Demonstrating for loop with command substitution -echo "Demonstrating for loop with command substitution:" -For i In $(seq 1 5) Do - echo "Number: $i" -Done +# 6. For loop with seq command +echo "Demonstrating for loop with seq command:" +for i in $(seq 1 5) +do + echo "Number from seq: $i" +done -# Demonstrating case statement +# 7. Using seq with different arguments +echo "Demonstrating seq with different arguments:" +echo "seq 3 (implicit start at 1, increment by 1):" +for i in $(seq 3) +do + echo "Value: $i" +done + +echo "seq 2 5 (start at 2, increment by 1):" +for i in $(seq 2 5) +do + echo "Value: $i" +done + +echo "seq 0 2 10 (start at 0, increment by 2):" +for i in $(seq 0 2 10) +do + echo "Value: $i" +done + +# 8. Case statement echo "Demonstrating case statement:" fruit="apple" -Case $fruit In +case $fruit in "apple") echo "It's an apple" ;; @@ -59,14 +87,16 @@ Case $fruit In *) echo "Unknown fruit" ;; -Esac +esac -# Demonstrating function -echo "Demonstrating function:" -Function greet ( - echo "Hello, $1!" -) -greet "World" -greet "Bellos" +# 9. Using seq in arithmetic operations +echo "Using seq in arithmetic operations:" +sum=0 +for i in $(seq 1 5) +do + sum=$((sum + i)) + echo "Running sum: $sum" +done +echo "Final sum of numbers 1 to 5: $sum" -echo "Control structures demonstration completed." +echo "Control structures and seq demonstration completed." diff --git a/bellos_scripts/file_operations.bellos b/bellos_scripts/file_operations.bellos index 019bb67..7614abd 100755 --- a/bellos_scripts/file_operations.bellos +++ b/bellos_scripts/file_operations.bellos @@ -8,37 +8,37 @@ echo "Creating test file..." write test.txt "Hello, World!" # Read the contents of the file -echo "\nReading test file:" +echo "Reading test file:" read test.txt # Append to the file -echo "\nAppending to test file..." +echo "Appending to test file..." append test.txt "This is a new line" # Read the updated contents -echo "\nReading updated test file:" +echo "Reading updated test file:" read test.txt # Write to a new file -echo "\nWriting to a new file..." +echo "Writing to a new file..." write new_file.txt "This is a new file" # Read the new file -echo "\nReading new file:" +echo "Reading new file:" read new_file.txt # List files in the current directory -echo "\nListing files in the current directory:" +echo "Listing files in the current directory:" ls -l # Rename a file -echo "\nRenaming file..." +echo "Renaming file..." mv new_file.txt renamed_file.txt # Delete files -echo "\nDeleting files..." +echo "Deleting files..." rm test.txt renamed_file.txt # List files again to confirm deletion -echo "\nListing files after deletion:" +echo "Listing files after deletion:" ls -l diff --git a/executable/bellos b/executable/bellos index 079a1bf..4636b1f 100755 Binary files a/executable/bellos and b/executable/bellos differ diff --git a/src/bellos.rs b/src/bellos.rs index 144db67..b4a654a 100644 --- a/src/bellos.rs +++ b/src/bellos.rs @@ -14,9 +14,10 @@ // along with this program. If not, see . mod executor_processes; -mod interpreter; +mod interpreter_logic; mod lexer; mod parser; +mod shell; mod utilities; use crate::executor_processes::executor::Executor; diff --git a/src/executor_processes/executor.rs b/src/executor_processes/executor.rs index a00388f..c97678a 100644 --- a/src/executor_processes/executor.rs +++ b/src/executor_processes/executor.rs @@ -13,26 +13,19 @@ // 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 crate::shell::shell::Shell; use std::fs::File; use std::io::{self, BufRead, BufReader, Write}; use std::path::Path; -use std::sync::Arc; pub struct Executor { - interpreter: Interpreter, - processes: Arc, + shell: Shell, } impl Executor { pub fn new() -> Self { Executor { - interpreter: Interpreter::new(), - processes: Arc::new(Processes::new()), + shell: Shell::new(), } } @@ -65,7 +58,7 @@ impl Executor { continue; } - if let Err(e) = self.process_content(trimmed_line) { + if let Err(e) = self.shell.run(trimmed_line) { eprintln!("Error on line {}: {}", index + 1, e); } io::stdout().flush().unwrap(); @@ -84,56 +77,9 @@ impl Executor { continue; } - if let Err(e) = self.process_content(&input) { + if let Err(e) = self.shell.run(&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 } => Arc::get_mut(&mut self.processes) - .unwrap() - .execute_command(&mut self.interpreter, name, args), - ASTNode::Assignment { name, value } => { - let expanded_value = self.interpreter.expand_variables(&value); - self.interpreter.variables.insert(name, expanded_value); - Ok(None) - } - ASTNode::Pipeline(commands) => { - self.processes.execute_pipeline(&self.interpreter, commands) - } - ASTNode::Redirect { - 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 076185b..35026b3 100644 --- a/src/executor_processes/processes.rs +++ b/src/executor_processes/processes.rs @@ -13,7 +13,8 @@ // 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::interpreter_logic::interpreter::Interpreter; +use crate::interpreter_logic::logic::Logic; use crate::utilities::utilities::{ASTNode, RedirectType}; use glob::glob; use std::fs::{File, OpenOptions}; @@ -24,165 +25,178 @@ use std::thread; pub struct Processes { background_jobs: Arc>>>>, + pub logic: Logic, } impl Processes { pub fn new() -> Self { Processes { background_jobs: Arc::new(Mutex::new(Vec::new())), + logic: Logic::new(), } } pub fn execute_command( &mut self, interpreter: &mut Interpreter, - name: String, - args: Vec, + name: &str, + args: &[String], + ) -> Result, String> { + match name { + "echo" => self.builtin_echo(interpreter, args), + "exit" => std::process::exit(0), + "export" => self.builtin_export(interpreter, args), + "jobs" => self.builtin_jobs(), + "write" => self.builtin_write(args), + "read" => self.builtin_read(args), + "append" => self.builtin_append(args), + "delete" => self.builtin_delete(args), + "[" => self.evaluate_condition(interpreter, args), + "seq" => self.builtin_seq(args), + _ => self.execute_external_command(name, args), + } + } + + fn builtin_echo( + &self, + interpreter: &mut Interpreter, + args: &[String], ) -> 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" => { - let output = expanded_args.join(" "); - println!("{}", output); - io::stdout().flush().unwrap(); - 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)) - } - "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 it's not a built-in command, 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())?; - Ok(Some(status.code().unwrap_or(0))) - } - Err(e) => Err(format!("Failed to execute command: {}", e)), - } - } - } + println!("{}", expanded_args.join(" ")); + Ok(Some(0)) } - pub fn execute_pipeline( + fn builtin_export( &self, - interpreter: &Interpreter, - commands: Vec, + interpreter: &mut Interpreter, + args: &[String], ) -> 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()), + for arg in args { + let parts: Vec<&str> = arg.splitn(2, '=').collect(); + if parts.len() == 2 { + std::env::set_var(parts[0], parts[1]); + interpreter + .variables + .insert(parts[0].to_string(), parts[1].to_string()); } } + Ok(Some(0)) + } - 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)); + fn builtin_jobs(&self) -> Result, String> { + let jobs = self.background_jobs.lock().unwrap(); + for (i, _) in jobs.iter().enumerate() { + println!("[{}] Running", i + 1); + } + Ok(Some(0)) + } + + fn builtin_write(&self, args: &[String]) -> Result, String> { + if args.len() != 2 { + return Err("Usage: write ".to_string()); + } + let filename = &args[0]; + let content = &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)) + } + + fn builtin_read(&self, args: &[String]) -> Result, String> { + if args.len() != 1 { + return Err("Usage: read ".to_string()); + } + let filename = &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)) + } + + fn builtin_append(&self, args: &[String]) -> Result, String> { + if args.len() != 2 { + return Err("Usage: append ".to_string()); + } + let filename = &args[0]; + let content = &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)) + } + + fn builtin_delete(&self, args: &[String]) -> Result, String> { + 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 {}: {}", filename, e))?; + Ok(Some(0)) + } + + fn builtin_seq(&self, args: &[String]) -> Result, String> { + if args.len() < 1 || args.len() > 3 { + return Err("Usage: seq [START] [STEP] END".to_string()); } - Ok(last_status) + let (start, step, end) = match args.len() { + 1 => ( + 1, + 1, + args[0] + .parse::() + .map_err(|_| "Invalid number".to_string())?, + ), + 2 => ( + args[0] + .parse::() + .map_err(|_| "Invalid number".to_string())?, + 1, + args[1] + .parse::() + .map_err(|_| "Invalid number".to_string())?, + ), + 3 => ( + args[0] + .parse::() + .map_err(|_| "Invalid number".to_string())?, + args[1] + .parse::() + .map_err(|_| "Invalid number".to_string())?, + args[2] + .parse::() + .map_err(|_| "Invalid number".to_string())?, + ), + _ => unreachable!(), + }; + + for i in (start..=end).step_by(step as usize) { + println!("{}", i); + } + Ok(Some(0)) + } + + fn execute_external_command(&self, name: &str, args: &[String]) -> Result, String> { + match Command::new(name).args(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_redirect( @@ -194,39 +208,128 @@ impl Processes { ) -> 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) + RedirectType::Output => self.execute_output_redirect(interpreter, node, &target), + RedirectType::Append => self.execute_append_redirect(interpreter, node, &target), + RedirectType::Input => self.execute_input_redirect(interpreter, node, &target), + } + } + + fn execute_output_redirect( + &self, + interpreter: &mut Interpreter, + node: ASTNode, + target: &str, + ) -> Result, String> { + 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)) + } + + fn execute_append_redirect( + &self, + interpreter: &mut Interpreter, + node: ASTNode, + target: &str, + ) -> Result, String> { + 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)) + } + + fn execute_input_redirect( + &self, + interpreter: &mut Interpreter, + node: ASTNode, + target: &str, + ) -> Result, String> { + 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, node, input) + } + + pub fn execute_pipeline( + &self, + interpreter: &mut 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 process = self.setup_pipeline_command( + interpreter, + name, + args, + i, + &commands.len(), + &mut previous_stdout, + )?; + processes.push(process); + } + _ => return Err("Pipeline can only contain commands".to_string()), } } + + self.wait_for_processes(processes) + } + + fn setup_pipeline_command( + &self, + interpreter: &mut Interpreter, + name: &str, + args: &[String], + index: usize, + total_commands: &usize, + previous_stdout: &mut Option, + ) -> Result { + 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 index < total_commands - 1 { + cmd.stdout(Stdio::piped()); + } + + let mut child = cmd.spawn().map_err(|e| e.to_string())?; + + if index < total_commands - 1 { + *previous_stdout = child.stdout.take().map(Stdio::from); + } + + Ok(child) + } + + fn wait_for_processes(&self, processes: Vec) -> Result, 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 capture_output( @@ -239,7 +342,7 @@ impl Processes { let mut buffer = Vec::new(); { let mut cursor = Cursor::new(&mut buffer); - let result = interpreter.interpret_node(node)?; + 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())?; @@ -249,44 +352,47 @@ impl Processes { fn execute_with_input( &self, interpreter: &mut Interpreter, - node: Box, + node: ASTNode, input: String, ) -> Result, String> { std::env::set_var("BELLOS_INPUT", input); - interpreter.interpret_node(node) + interpreter.interpret_node(&node) } - pub fn execute_background(&self, node: ASTNode) -> Result, String> { + pub fn execute_background( + &mut self, + interpreter: &mut Interpreter, + node: ASTNode, + ) -> Result, String> { let bg_jobs = Arc::clone(&self.background_jobs); + let interpreter_clone = interpreter.clone(); thread::spawn(move || { - let mut interpreter = Interpreter::new(); - if let Err(e) = interpreter.interpret_node(Box::new(node)) { + let mut local_interpreter = interpreter_clone; + if let Err(e) = local_interpreter.interpret_node(&node) { eprintln!("Background job error: {}", e); } - // Remove completed jobs from bg_jobs 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 + false } Ok(None) => { println!("Job still running."); - true // Job is still running, keep it + true } Err(err) => { eprintln!("Error waiting for job: {}", err); - false // Error occurred, remove the job + false } } }); }); - // Add a placeholder Child process to the background_jobs list let placeholder = Arc::new(Mutex::new(Command::new("sleep").arg("1").spawn().map_err( |e| format!("Failed to create placeholder process: {}", e), @@ -305,4 +411,18 @@ impl Processes { Err(_) => vec![pattern.to_string()], } } + + fn evaluate_condition( + &self, + interpreter: &mut Interpreter, + args: &[String], + ) -> Result, String> { + if args.len() != 3 { + return Err("Invalid condition syntax".to_string()); + } + let result = + self.logic + .compare_values(&interpreter.variables, &args[0], &args[1], &args[2])?; + Ok(Some(if result { 0 } else { 1 })) + } } diff --git a/src/interpreter/interpreter.rs b/src/interpreter/interpreter.rs deleted file mode 100644 index 0ce26dd..0000000 --- a/src/interpreter/interpreter.rs +++ /dev/null @@ -1,263 +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::utilities::utilities::ASTNode; -use std::collections::HashMap; -use std::env; - -pub struct Interpreter { - pub variables: HashMap, - pub functions: HashMap, -} - -impl Interpreter { - pub fn new() -> Self { - Interpreter { - variables: HashMap::new(), - functions: HashMap::new(), - } - } - - pub fn interpret(&mut self, nodes: Vec) -> Result<(), String> { - for node in nodes { - self.interpret_node(Box::new(node))?; - } - Ok(()) - } - - pub fn interpret_node(&mut self, node: Box) -> Result, String> { - match *node { - ASTNode::Assignment { name, value } => { - let expanded_value = self.expand_variables(&value); - self.variables.insert(name, expanded_value); - Ok(None) - } - ASTNode::Block(statements) => { - for statement in statements { - self.interpret_node(Box::new(statement))?; - } - Ok(None) - } - ASTNode::If { - condition, - then_block, - else_block, - } => { - if self.evaluate_condition(&condition)? { - self.interpret_node(then_block)?; - } else if let Some(else_block) = else_block { - self.interpret_node(else_block)?; - } - Ok(None) - } - ASTNode::While { condition, block } => { - while self.evaluate_condition(&condition)? { - self.interpret_node(Box::new(*block.clone()))?; - } - Ok(None) - } - ASTNode::For { var, list, block } => { - for item in list { - self.variables.insert(var.clone(), item); - self.interpret_node(Box::new(*block.clone()))?; - } - Ok(None) - } - ASTNode::Function { name, body } => { - 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()), - } - } - - 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()), - } - } - - 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(&'(') { - 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 - .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('$'); - result.push_str(&var_name); - } - } - } else { - result.push(c); - } - } - result - } - - 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); - } - } - _ => {} - } - } - } - - // Split the expression into tokens - let tokens: Vec<&str> = inner_expr.split_whitespace().collect(); - - // 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])?; - - let result = 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()) - } - } - _ => Err(format!("Unsupported operation: {}", tokens[1])), - }; - - result - } else { - Err("Invalid arithmetic expression".to_string()) - } - } - - fn get_var_value(&self, var: &str) -> Result { - if let Some(value) = self.variables.get(var) { - value - .parse() - .map_err(|_| format!("Invalid integer: {}", value)) - } else if let Ok(value) = var.parse() { - Ok(value) - } else { - Err(format!("Undefined variable or invalid integer: {}", var)) - } - } -} diff --git a/src/interpreter_logic/interpreter.rs b/src/interpreter_logic/interpreter.rs new file mode 100644 index 0000000..51efdf1 --- /dev/null +++ b/src/interpreter_logic/interpreter.rs @@ -0,0 +1,210 @@ +// 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_logic::logic::Logic; +use crate::utilities::utilities::ASTNode; +use std::collections::HashMap; + +#[derive(Clone)] +pub struct Interpreter { + pub variables: HashMap, + pub functions: HashMap, + pub logic: Logic, +} + +impl Interpreter { + pub fn new() -> Self { + Interpreter { + variables: HashMap::new(), + functions: HashMap::new(), + logic: Logic::new(), + } + } + + pub fn interpret(&mut self, nodes: Vec) -> Result<(), String> { + for node in nodes { + if let Err(e) = self.interpret_node(&node) { + eprintln!("Error executing command: {}", e); + } + } + Ok(()) + } + + pub fn interpret_node(&mut self, node: &ASTNode) -> Result, String> { + match node { + ASTNode::Assignment { name, value } => self.assignment(name, value), + ASTNode::Block(statements) => self.execute_block(statements), + ASTNode::If { + condition, + then_block, + else_block, + } => self.execute_if(condition, then_block, else_block), + ASTNode::While { condition, block } => self.execute_while(condition, block), + ASTNode::For { var, list, block } => self.execute_for(var, list, block), + ASTNode::Case { var, cases } => self.execute_case(var, cases), + ASTNode::Comparison { left, op, right } => self.execute_comparison(left, op, right), + ASTNode::Expression(expr) => self.execute_expression(expr), + ASTNode::Function { name, body } => self.define_function(name, body), + _ => Err(format!("Unsupported node type in Interpreter: {:?}", node)), + } + } + + fn assignment(&mut self, name: &str, value: &str) -> Result, String> { + let expanded_value = self.expand_variables(value); + self.variables.insert(name.to_string(), expanded_value); + Ok(None) + } + + fn execute_block(&mut self, statements: &[ASTNode]) -> Result, String> { + let mut last_result = Ok(None); + for statement in statements { + last_result = self.interpret_node(statement); + if last_result.is_err() { + break; + } + } + last_result + } + + fn execute_if( + &mut self, + condition: &ASTNode, + then_block: &ASTNode, + else_block: &Option>, + ) -> Result, String> { + if self.logic.evaluate_condition(&self.variables, condition)? { + self.interpret_node(then_block) + } else if let Some(else_block) = else_block { + self.interpret_node(else_block) + } else { + Ok(None) + } + } + + fn execute_while( + &mut self, + condition: &ASTNode, + block: &ASTNode, + ) -> Result, String> { + while self.logic.evaluate_condition(&self.variables, condition)? { + self.interpret_node(block)?; + } + Ok(None) + } + + fn execute_for( + &mut self, + var: &str, + list: &[String], + block: &ASTNode, + ) -> Result, String> { + for item in list { + let expanded_item = self.expand_variables(item); + self.variables.insert(var.to_string(), expanded_item); + self.interpret_node(block)?; + } + Ok(None) + } + + fn execute_case( + &mut self, + var: &ASTNode, + cases: &[(ASTNode, ASTNode)], + ) -> Result, String> { + let var_str = match var { + ASTNode::Expression(expr) => self.expand_variables(expr), + _ => return Err("Invalid case variable".to_string()), + }; + for (pattern, block) in cases { + let expanded_pattern = match pattern { + ASTNode::Expression(expr) => self.expand_variables(expr), + _ => return Err("Invalid case pattern".to_string()), + }; + if expanded_pattern == "*" || expanded_pattern == var_str { + return self.interpret_node(block); + } + } + Ok(None) + } + + fn execute_comparison( + &mut self, + left: &str, + op: &str, + right: &str, + ) -> Result, String> { + let result = self + .logic + .compare_values(&self.variables, left, op, right)?; + Ok(Some(if result { 0 } else { 1 })) + } + + fn execute_expression(&mut self, expr: &str) -> Result, String> { + let expanded = self.expand_variables(expr); + Ok(Some(self.logic.evaluate_arithmetic(&expanded)?)) + } + + fn define_function(&mut self, name: &str, body: &ASTNode) -> Result, String> { + self.functions.insert(name.to_string(), body.clone()); + Ok(None) + } + + pub fn expand_variables(&self, input: &str) -> String { + self.logic.expand_variables(&self.variables, input) + } + + pub fn call_function(&mut self, name: &str, args: &[String]) -> Result, String> { + if let Some(function_body) = self.functions.get(name).cloned() { + // Save current variables + let saved_variables = self.variables.clone(); + + // Set up function arguments as variables + if let ASTNode::Function { name: _, body } = function_body { + if let ASTNode::Block(statements) = *body { + // Assume the first statement is a parameter list + if let Some(ASTNode::Assignment { + name: params, + value: _, + }) = statements.first() + { + let param_names: Vec<&str> = params.split_whitespace().collect(); + for (i, param_name) in param_names.iter().enumerate() { + if i < args.len() { + self.variables + .insert(param_name.to_string(), args[i].clone()); + } else { + self.variables.insert(param_name.to_string(), String::new()); + } + } + } + + // Execute function body + let result = self.execute_block(&statements[1..]); + + // Restore original variables + self.variables = saved_variables; + + result + } else { + Err("Invalid function body".to_string()) + } + } else { + Err("Invalid function definition".to_string()) + } + } else { + Err(format!("Function '{}' not found", name)) + } + } +} diff --git a/src/interpreter_logic/logic.rs b/src/interpreter_logic/logic.rs new file mode 100644 index 0000000..57929b1 --- /dev/null +++ b/src/interpreter_logic/logic.rs @@ -0,0 +1,210 @@ +// 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::utilities::utilities::ASTNode; +use std::collections::HashMap; + +#[derive(Clone)] +pub struct Logic; + +impl Logic { + pub fn new() -> Self { + Logic + } + + pub fn expand_variables(&self, variables: &HashMap, 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 '(' + if chars.peek() == Some(&'(') { + chars.next(); // Consume second '(' + let expr = self.extract_arithmetic_expression(&mut chars); + if let Ok(value) = self.evaluate_arithmetic(&expr) { + result.push_str(&value.to_string()); + } else { + result.push_str(&format!("$(({})", expr)); + } + } else { + let cmd = self.extract_command_substitution(&mut chars); + // For now, we'll just insert the command as-is + result.push_str(&format!("$({})", cmd)); + } + } else { + let var_name: String = chars + .by_ref() + .take_while(|&c| c.is_alphanumeric() || c == '_') + .collect(); + if let Some(value) = variables.get(&var_name) { + result.push_str(value); + } else { + result.push('$'); + result.push_str(&var_name); + } + } + } else { + result.push(c); + } + } + result + } + + pub fn extract_arithmetic_expression( + &self, + chars: &mut std::iter::Peekable, + ) -> String { + let mut expr = String::new(); + let mut depth = 2; // We've already consumed "((" + while let Some(c) = chars.next() { + match c { + '(' => depth += 1, + ')' => { + depth -= 1; + if depth == 0 { + break; + } + } + _ => {} + } + expr.push(c); + } + expr + } + + 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 + }; + + self.evaluate_arithmetic_expression(inner_expr) + } + + fn extract_command_substitution( + &self, + chars: &mut std::iter::Peekable, + ) -> String { + let mut depth = 1; + let mut cmd = String::new(); + for c in chars.by_ref() { + match c { + '(' => depth += 1, + ')' => { + depth -= 1; + if depth == 0 { + break; + } + } + _ => {} + } + cmd.push(c); + } + cmd + } + + fn evaluate_arithmetic_expression(&self, expr: &str) -> Result { + let tokens: Vec<&str> = expr.split_whitespace().collect(); + if tokens.len() != 3 { + return Err("Invalid arithmetic expression".to_string()); + } + + let left: i32 = self.parse_value(tokens[0])?; + let right: i32 = self.parse_value(tokens[2])?; + + match tokens[1] { + "+" => Ok(left + right), + "-" => Ok(left - right), + "*" => Ok(left * right), + "/" => { + if right != 0 { + Ok(left / right) + } else { + Err("Division by zero".to_string()) + } + } + "%" => { + if right != 0 { + Ok(left % right) + } else { + Err("Modulo by zero".to_string()) + } + } + _ => Err(format!("Unsupported operation: {}", tokens[1])), + } + } + + fn parse_value(&self, value: &str) -> Result { + value + .parse() + .map_err(|_| format!("Invalid integer: {}", value)) + } + + pub fn compare_values( + &self, + variables: &HashMap, + left: &str, + op: &str, + right: &str, + ) -> Result { + let left_val = self.expand_variables(variables, left); + let right_val = self.expand_variables(variables, right); + + match op { + "-eq" => Ok(left_val == right_val), + "-ne" => Ok(left_val != right_val), + "-lt" => self.compare_numbers(&left_val, &right_val, |a, b| a < b), + "-le" => self.compare_numbers(&left_val, &right_val, |a, b| a <= b), + "-gt" => self.compare_numbers(&left_val, &right_val, |a, b| a > b), + "-ge" => self.compare_numbers(&left_val, &right_val, |a, b| a >= b), + _ => Err(format!("Unknown comparison operator: {}", op)), + } + } + + fn compare_numbers(&self, left: &str, right: &str, compare: F) -> Result + where + F: Fn(i32, i32) -> bool, + { + let left_num = left + .parse::() + .map_err(|_| format!("Invalid number: {}", left))?; + let right_num = right + .parse::() + .map_err(|_| format!("Invalid number: {}", right))?; + Ok(compare(left_num, right_num)) + } + + pub fn evaluate_condition( + &self, + variables: &HashMap, + condition: &ASTNode, + ) -> Result { + match condition { + ASTNode::Comparison { left, op, right } => { + self.compare_values(variables, left, op, right) + } + ASTNode::Expression(expr) => { + let result = self.evaluate_arithmetic(&self.expand_variables(variables, expr))?; + Ok(result != 0) + } + _ => Err("Invalid condition".to_string()), + } + } +} diff --git a/src/interpreter/mod.rs b/src/interpreter_logic/mod.rs similarity index 58% rename from src/interpreter/mod.rs rename to src/interpreter_logic/mod.rs index 144a532..149fc03 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter_logic/mod.rs @@ -1 +1,2 @@ pub mod interpreter; +pub mod logic; diff --git a/src/lexer/lexer.rs b/src/lexer/lexer.rs index 4771321..af4ce2a 100644 --- a/src/lexer/lexer.rs +++ b/src/lexer/lexer.rs @@ -44,6 +44,14 @@ impl Lexer { } Some(match self.current_char() { + '[' => { + self.advance(); + Token::LeftBracket + } + ']' => { + self.advance(); + Token::RightBracket + } ' ' | '\t' => { self.advance(); return self.next_token(); @@ -54,7 +62,12 @@ impl Lexer { } ';' => { self.advance(); - Token::Semicolon + if self.current_char() == ';' { + self.advance(); + Token::DoubleSemicolon + } else { + Token::Semicolon + } } '|' => { self.advance(); @@ -82,12 +95,12 @@ impl Lexer { self.advance(); Token::Redirect(RedirectType::Append) } else { - Token::Redirect(RedirectType::Out) + Token::Redirect(RedirectType::Output) } } '<' => { self.advance(); - Token::Redirect(RedirectType::In) + Token::Redirect(RedirectType::Input) } '"' => self.read_string(), '$' => { @@ -135,13 +148,18 @@ impl Lexer { "if" => Token::If, "then" => Token::Then, "else" => Token::Else, + "elif" => Token::Elif, "fi" => Token::Fi, "while" => Token::While, "do" => Token::Do, "done" => Token::Done, "for" => Token::For, "in" => Token::In, + "case" => Token::Case, + "esac" => Token::Esac, "function" => Token::Function, + "[" => Token::LeftBracket, + "]" => Token::RightBracket, _ => Token::Word(word), } } diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 378a288..89a2091 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -40,78 +40,120 @@ impl Parser { Ok(nodes) } - fn parse_statement(&mut self) -> Result { - if self.position >= self.tokens.len() { - return Err("Unexpected end of input".to_string()); + fn current_token(&self) -> Option<&Token> { + self.tokens.get(self.position) + } + + fn consume_token(&mut self) -> Result<(), String> { + if self.position < self.tokens.len() { + self.position += 1; + Ok(()) + } else { + Err("Unexpected end of input".to_string()) } - match &self.tokens[self.position] { - Token::Word(w) if w.eq_ignore_ascii_case("if") => self.parse_if(), - Token::Word(w) if w.eq_ignore_ascii_case("while") => self.parse_while(), - Token::Word(w) if w.eq_ignore_ascii_case("for") => self.parse_for(), - Token::Word(w) if w.eq_ignore_ascii_case("case") => self.parse_case(), - Token::Word(w) if w.eq_ignore_ascii_case("function") => self.parse_function(), + } + + fn parse_statement(&mut self) -> Result { + match self.current_token() { + Some(Token::Word(w)) if w == "if" => self.parse_if(), + Some(Token::Word(w)) if w == "while" => self.parse_while(), + Some(Token::Word(w)) if w == "for" => self.parse_for(), + Some(Token::Word(w)) if w == "case" => self.parse_case(), _ => self.parse_command_or_assignment(), } } fn parse_if(&mut self) -> Result { - self.position += 1; // Consume 'if' - let condition = Box::new(self.parse_command()?); - self.skip_newlines_and_expect("then")?; - let then_block = Box::new(self.parse_block("else", "fi")?); + self.consume_token()?; // Consume 'if' + let condition = self.parse_condition()?; + self.expect_token(&Token::Then)?; + let then_block = self.parse_block("else", "fi")?; let else_block = if self.current_token_is("else") { - self.position += 1; - self.skip_newlines(); + self.consume_token()?; Some(Box::new(self.parse_block("fi", "fi")?)) } else { None }; - self.skip_newlines_and_expect("fi")?; + self.expect_token(&Token::Fi)?; Ok(ASTNode::If { - condition, - then_block, + condition: Box::new(condition), + then_block: Box::new(then_block), else_block, }) } - fn parse_while(&mut self) -> Result { - self.position += 1; // Consume 'while' - let condition = Box::new(self.parse_command()?); - self.skip_newlines_and_expect("do")?; - let block = Box::new(self.parse_block("done", "done")?); - self.skip_newlines_and_expect("done")?; - Ok(ASTNode::While { condition, block }) + fn parse_condition(&mut self) -> Result { + self.expect_token(&Token::LeftBracket)?; + let left = self.parse_expression()?.to_string(); + let op = self.expect_word()?; + let right = self.parse_expression()?.to_string(); + self.expect_token(&Token::RightBracket)?; + Ok(ASTNode::Comparison { left, op, right }) } - fn parse_for(&mut self) -> Result { - self.position += 1; // Consume 'for' - let var = self.expect_word()?; - self.skip_newlines_and_expect("in")?; - let list = self.parse_list()?; - self.skip_newlines_and_expect("do")?; - let block = Box::new(self.parse_block("done", "done")?); - self.skip_newlines_and_expect("done")?; - Ok(ASTNode::For { var, list, block }) + fn parse_expression(&mut self) -> Result { + let left = self.expect_word()?; + if self.current_token_is("-eq") + || self.current_token_is("-ne") + || self.current_token_is("-lt") + || self.current_token_is("-le") + || self.current_token_is("-gt") + || self.current_token_is("-ge") + { + let op = self.expect_word()?; + let right = self.expect_word()?; + Ok(ASTNode::Comparison { left, op, right }) + } else { + Ok(ASTNode::Expression(left)) + } } fn parse_case(&mut self) -> Result { - self.position += 1; // Consume 'case' + self.consume_token()?; // Consume 'case' let var = self.parse_expression()?; - self.skip_newlines_and_expect("in")?; + self.expect_token(&Token::In)?; let mut cases = Vec::new(); while !self.current_token_is("esac") { - self.skip_newlines(); let pattern = self.parse_expression()?; self.expect_token(&Token::RightParen)?; let block = self.parse_block(";;", "esac")?; cases.push((pattern, block)); - self.skip_newlines(); if self.current_token_is(";;") { - self.position += 1; + self.consume_token()?; } } - self.skip_newlines_and_expect("esac")?; - Ok(ASTNode::Case { var, cases }) + self.expect_token(&Token::Esac)?; + Ok(ASTNode::Case { + var: Box::new(var), + cases, + }) + } + + fn parse_while(&mut self) -> Result { + self.consume_token()?; // Consume 'while' + let condition = self.parse_condition()?; + self.expect_token(&Token::Do)?; + let block = self.parse_block("done", "done")?; + self.expect_token(&Token::Done)?; + Ok(ASTNode::While { + condition: Box::new(condition), + block: Box::new(block), + }) + } + + fn parse_for(&mut self) -> Result { + self.consume_token()?; // Consume 'for' + let var = self.expect_word()?; + self.expect_token(&Token::In)?; + let list = self.parse_list()?; + self.expect_token(&Token::Do)?; + let block = self.parse_block("done", "done")?; + self.expect_token(&Token::Done)?; + Ok(ASTNode::For { + var, + list, + block: Box::new(block), + }) } fn parse_function(&mut self) -> Result { @@ -127,7 +169,10 @@ impl Parser { fn parse_block(&mut self, end_token1: &str, end_token2: &str) -> Result { let mut statements = Vec::new(); - while !self.current_token_is(end_token1) && !self.current_token_is(end_token2) { + while self.position < self.tokens.len() + && !self.current_token_is(end_token1) + && !self.current_token_is(end_token2) + { self.skip_newlines(); if self.current_token_is(end_token1) || self.current_token_is(end_token2) { break; @@ -144,6 +189,14 @@ impl Parser { } if args.is_empty() { Err("Expected command".to_string()) + } else if args[0] == "[" { + if args.last() != Some(&"]".to_string()) { + return Err("Condition must end with ]".to_string()); + } + Ok(ASTNode::Command { + name: "[".to_string(), + args, + }) } else { Ok(ASTNode::Command { name: args[0].clone(), @@ -161,10 +214,6 @@ impl Parser { Ok(list) } - fn parse_expression(&mut self) -> Result { - self.expect_word() - } - fn expect_word(&mut self) -> Result { if self.position >= self.tokens.len() { return Err("Unexpected end of input".to_string()); @@ -174,6 +223,21 @@ impl Parser { self.position += 1; Ok(w.clone()) } + Token::If + | Token::Then + | Token::Else + | Token::Fi + | Token::While + | Token::Do + | Token::Done + | Token::For + | Token::In + | Token::Case + | Token::Esac => { + let word = format!("{:?}", self.tokens[self.position]); + self.position += 1; + Ok(word) + } _ => Err(format!( "Expected word, found {:?}", self.tokens[self.position] @@ -247,7 +311,7 @@ impl Parser { let name = self.expect_word()?; if self.position < self.tokens.len() && self.tokens[self.position] == Token::Assignment { self.position += 1; - let value = self.parse_expression()?; + let value = self.expect_word()?; Ok(ASTNode::Assignment { name, value }) } else { let mut args = Vec::new(); diff --git a/src/shell/mod.rs b/src/shell/mod.rs new file mode 100644 index 0000000..327cf1b --- /dev/null +++ b/src/shell/mod.rs @@ -0,0 +1 @@ +pub mod shell; diff --git a/src/shell/shell.rs b/src/shell/shell.rs new file mode 100644 index 0000000..9a5df2c --- /dev/null +++ b/src/shell/shell.rs @@ -0,0 +1,272 @@ +// 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_logic::interpreter::Interpreter; +use crate::lexer::lexer::Lexer; +use crate::parser::parser::Parser; +use crate::utilities::utilities::{ASTNode, RedirectType}; +use std::io::{self, Write}; +use std::process::{Command, Stdio}; + +pub struct Shell { + pub interpreter: Interpreter, +} + +impl Shell { + pub fn new() -> Self { + Shell { + interpreter: Interpreter::new(), + } + } + + pub fn run(&mut self, input: &str) -> Result<(), String> { + let mut lexer = Lexer::new(input.to_string()); + let tokens = lexer.tokenize(); + let mut parser = Parser::new(tokens); + let ast = parser.parse()?; + self.interpret(ast) + } + + pub fn interpret(&mut self, nodes: Vec) -> Result<(), String> { + for node in nodes { + if let Err(e) = self.interpret_node(&node) { + eprintln!("Error executing command: {}", e); + } + } + Ok(()) + } + + pub fn interpret_node(&mut self, node: &ASTNode) -> Result, String> { + match node { + ASTNode::Command { name, args } => self.execute_command(name, args), + ASTNode::Pipeline(commands) => self.execute_pipeline(commands), + ASTNode::Redirect { + node, + direction, + target, + } => self.execute_redirect(node, direction, target), + ASTNode::Background(node) => self.execute_background(node), + _ => self.interpreter.interpret_node(node), + } + } + + pub fn execute_command(&mut self, name: &str, args: &[String]) -> Result, String> { + let expanded_name = self.interpreter.expand_variables(name); + let expanded_args: Vec = args + .iter() + .map(|arg| self.interpreter.expand_variables(arg)) + .collect(); + + let output = Command::new(&expanded_name) + .args(&expanded_args) + .output() + .map_err(|e| format!("Failed to execute command: {}", e))?; + + io::stdout() + .write_all(&output.stdout) + .map_err(|e| e.to_string())?; + io::stderr() + .write_all(&output.stderr) + .map_err(|e| e.to_string())?; + + Ok(Some(output.status.code().unwrap_or(-1))) + } + + pub fn execute_pipeline(&mut self, commands: &[ASTNode]) -> Result, String> { + let mut last_output = Vec::new(); + let mut last_exit_code = None; + + for (i, command) in commands.iter().enumerate() { + if let ASTNode::Command { name, args } = command { + let expanded_name = self.interpreter.expand_variables(name); + let expanded_args: Vec = args + .iter() + .map(|arg| self.interpreter.expand_variables(arg)) + .collect(); + + let mut process = Command::new(&expanded_name); + process.args(&expanded_args); + + if i == 0 { + process.stdin(Stdio::inherit()); + } else { + process.stdin(Stdio::piped()); + } + + if i == commands.len() - 1 { + process.stdout(Stdio::inherit()); + } else { + process.stdout(Stdio::piped()); + } + + let mut child = process + .spawn() + .map_err(|e| format!("Failed to spawn process: {}", e))?; + + 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 process: {}", e))?; + + last_output = output.stdout; + last_exit_code = Some(output.status.code().unwrap_or(-1)); + } else { + return Err("Invalid command in pipeline".to_string()); + } + } + + Ok(last_exit_code) + } + + pub fn execute_redirect( + &mut self, + node: &ASTNode, + direction: &RedirectType, + target: &str, + ) -> Result, String> { + let expanded_target = self.interpreter.expand_variables(target); + match direction { + RedirectType::Input => self.execute_input_redirect(node, &expanded_target), + RedirectType::Output => self.execute_output_redirect(node, &expanded_target), + RedirectType::Append => self.execute_append_redirect(node, &expanded_target), + } + } + + fn execute_input_redirect( + &mut self, + node: &ASTNode, + target: &str, + ) -> Result, String> { + if let ASTNode::Command { name, args } = node { + let expanded_name = self.interpreter.expand_variables(name); + let expanded_args: Vec = args + .iter() + .map(|arg| self.interpreter.expand_variables(arg)) + .collect(); + + let input = std::fs::File::open(target) + .map_err(|e| format!("Failed to open input file '{}': {}", target, e))?; + + let output = std::process::Command::new(&expanded_name) + .args(&expanded_args) + .stdin(input) + .output() + .map_err(|e| format!("Failed to execute command: {}", e))?; + + io::stdout() + .write_all(&output.stdout) + .map_err(|e| e.to_string())?; + io::stderr() + .write_all(&output.stderr) + .map_err(|e| e.to_string())?; + + Ok(Some(output.status.code().unwrap_or(-1))) + } else { + Err("Invalid command for input redirection".to_string()) + } + } + + fn execute_output_redirect( + &mut self, + node: &ASTNode, + target: &str, + ) -> Result, String> { + if let ASTNode::Command { name, args } = node { + let expanded_name = self.interpreter.expand_variables(name); + let expanded_args: Vec = args + .iter() + .map(|arg| self.interpreter.expand_variables(arg)) + .collect(); + + let output_file = std::fs::File::create(target) + .map_err(|e| format!("Failed to create output file '{}': {}", target, e))?; + + let output = std::process::Command::new(&expanded_name) + .args(&expanded_args) + .stdout(output_file) + .output() + .map_err(|e| format!("Failed to execute command: {}", e))?; + + io::stderr() + .write_all(&output.stderr) + .map_err(|e| e.to_string())?; + + Ok(Some(output.status.code().unwrap_or(-1))) + } else { + Err("Invalid command for output redirection".to_string()) + } + } + + fn execute_append_redirect( + &mut self, + node: &ASTNode, + target: &str, + ) -> Result, String> { + if let ASTNode::Command { name, args } = node { + let expanded_name = self.interpreter.expand_variables(name); + let expanded_args: Vec = args + .iter() + .map(|arg| self.interpreter.expand_variables(arg)) + .collect(); + + let output_file = std::fs::OpenOptions::new() + .write(true) + .append(true) + .open(target) + .map_err(|e| format!("Failed to open file '{}' for appending: {}", target, e))?; + + let output = std::process::Command::new(&expanded_name) + .args(&expanded_args) + .stdout(output_file) + .output() + .map_err(|e| format!("Failed to execute command: {}", e))?; + + io::stderr() + .write_all(&output.stderr) + .map_err(|e| e.to_string())?; + + Ok(Some(output.status.code().unwrap_or(-1))) + } else { + Err("Invalid command for append redirection".to_string()) + } + } + + pub fn execute_background(&mut self, node: &ASTNode) -> Result, String> { + if let ASTNode::Command { name, args } = node { + let expanded_name = self.interpreter.expand_variables(name); + let expanded_args: Vec = args + .iter() + .map(|arg| self.interpreter.expand_variables(arg)) + .collect(); + + let child = Command::new(&expanded_name) + .args(&expanded_args) + .spawn() + .map_err(|e| format!("Failed to spawn background process: {}", e))?; + + println!("Started background process with PID: {}", child.id()); + Ok(Some(0)) + } else { + Err("Invalid command for background execution".to_string()) + } + } +} diff --git a/src/utilities/utilities.rs b/src/utilities/utilities.rs index d2f27ca..5d3d945 100644 --- a/src/utilities/utilities.rs +++ b/src/utilities/utilities.rs @@ -37,21 +37,25 @@ pub enum Token { Case, Esac, Function, + Elif, + LeftBracket, + RightBracket, + DoubleSemicolon, } #[derive(Debug, Clone, PartialEq)] pub enum RedirectType { - Out, + Input, + Output, Append, - In, } impl RedirectType { pub fn as_str(&self) -> &'static str { match self { - RedirectType::Out => ">", + RedirectType::Output => ">", RedirectType::Append => ">>", - RedirectType::In => "<", + RedirectType::Input => "<", } } } @@ -87,15 +91,21 @@ pub enum ASTNode { list: Vec, block: Box, }, + Comparison { + left: String, + op: String, + right: String, + }, + Case { + var: Box, + cases: Vec<(ASTNode, ASTNode)>, + }, Function { name: String, body: Box, }, Background(Box), - Case { - var: String, - cases: Vec<(String, ASTNode)>, - }, + Expression(String), } impl ASTNode { @@ -106,3 +116,29 @@ impl ASTNode { } } } + +impl ToString for ASTNode { + fn to_string(&self) -> String { + match self { + ASTNode::Command { name, args } => format!("{} {}", name, args.join(" ")), + ASTNode::Assignment { name, value } => format!("{}={}", name, value), + ASTNode::Expression(expr) => expr.clone(), + _ => format!("{:?}", self), + } + } +} + +impl PartialEq for ASTNode { + fn eq(&self, other: &str) -> bool { + match self { + ASTNode::Expression(s) => s == other, + _ => false, + } + } +} + +impl PartialEq for ASTNode { + fn eq(&self, other: &String) -> bool { + self == other.as_str() + } +}