latest pushes
This commit is contained in:
@@ -21,9 +21,14 @@ mod utilities;
|
||||
|
||||
use crate::executor::executor::Executor;
|
||||
use std::env;
|
||||
use std::process;
|
||||
|
||||
fn main() -> Result<(), String> {
|
||||
let mut executor = Executor::new();
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
executor.run(args)
|
||||
let mut executor = Executor::new();
|
||||
|
||||
if let Err(e) = executor.run(args) {
|
||||
eprintln!("Application error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
@@ -13,12 +13,11 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::utilities::utilities::ASTNode;
|
||||
|
||||
use crate::utilities::utilities::{ASTNode, RedirectType};
|
||||
use glob::glob;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::process::{Child, Command, Stdio};
|
||||
@@ -289,25 +288,36 @@ impl Interpreter {
|
||||
fn execute_redirect(
|
||||
&mut self,
|
||||
node: ASTNode,
|
||||
direction: String,
|
||||
direction: RedirectType,
|
||||
target: String,
|
||||
) -> Result<Option<i32>, String> {
|
||||
let target = self.expand_variables(&target);
|
||||
match direction.as_str() {
|
||||
">" => {
|
||||
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)
|
||||
}
|
||||
_ => Err(format!("Unsupported redirection: {}", direction)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -13,7 +13,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::utilities::utilities::Token;
|
||||
use crate::utilities::utilities::{RedirectType, Token};
|
||||
|
||||
pub struct Lexer {
|
||||
input: Vec<char>,
|
||||
@@ -27,6 +27,15 @@ impl Lexer {
|
||||
position: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tokenize(&mut self) -> Vec<Token> {
|
||||
let mut tokens = Vec::new();
|
||||
while let Some(token) = self.next_token() {
|
||||
tokens.push(token);
|
||||
}
|
||||
tokens
|
||||
}
|
||||
|
||||
fn next_token(&mut self) -> Option<Token> {
|
||||
self.skip_whitespace();
|
||||
|
||||
@@ -34,65 +43,93 @@ impl Lexer {
|
||||
return None;
|
||||
}
|
||||
|
||||
match self.input[self.position] {
|
||||
'=' => {
|
||||
self.position += 1;
|
||||
Some(Token::Assignment)
|
||||
}
|
||||
'|' => {
|
||||
self.position += 1;
|
||||
Some(Token::Pipe)
|
||||
}
|
||||
'>' => {
|
||||
self.position += 1;
|
||||
Some(Token::Redirect(">".to_string()))
|
||||
}
|
||||
'<' => {
|
||||
self.position += 1;
|
||||
Some(Token::Redirect("<".to_string()))
|
||||
}
|
||||
'(' => {
|
||||
self.position += 1;
|
||||
Some(Token::LeftParen)
|
||||
}
|
||||
')' => {
|
||||
self.position += 1;
|
||||
Some(Token::RightParen)
|
||||
}
|
||||
';' => {
|
||||
self.position += 1;
|
||||
Some(Token::Semicolon)
|
||||
Some(match self.current_char() {
|
||||
' ' | '\t' => {
|
||||
self.advance();
|
||||
return self.next_token();
|
||||
}
|
||||
'\n' => {
|
||||
self.position += 1;
|
||||
Some(Token::NewLine)
|
||||
self.advance();
|
||||
Token::NewLine
|
||||
}
|
||||
';' => {
|
||||
self.advance();
|
||||
Token::Semicolon
|
||||
}
|
||||
'|' => {
|
||||
self.advance();
|
||||
Token::Pipe
|
||||
}
|
||||
'&' => {
|
||||
self.position += 1;
|
||||
Some(Token::Ampersand)
|
||||
self.advance();
|
||||
Token::Ampersand
|
||||
}
|
||||
'"' => Some(self.read_string()),
|
||||
_ => Some(self.read_word()),
|
||||
}
|
||||
'=' => {
|
||||
self.advance();
|
||||
Token::Assignment
|
||||
}
|
||||
'(' => {
|
||||
self.advance();
|
||||
Token::LeftParen
|
||||
}
|
||||
')' => {
|
||||
self.advance();
|
||||
Token::RightParen
|
||||
}
|
||||
'>' => {
|
||||
self.advance();
|
||||
if self.current_char() == '>' {
|
||||
self.advance();
|
||||
Token::Redirect(RedirectType::Append)
|
||||
} else {
|
||||
Token::Redirect(RedirectType::Out)
|
||||
}
|
||||
}
|
||||
'<' => {
|
||||
self.advance();
|
||||
Token::Redirect(RedirectType::In)
|
||||
}
|
||||
'"' => self.read_string(),
|
||||
'$' => {
|
||||
if self.peek_next() == Some('(') {
|
||||
Token::Word(self.read_command_substitution())
|
||||
} else {
|
||||
self.read_word()
|
||||
}
|
||||
}
|
||||
_ => self.read_word(),
|
||||
})
|
||||
}
|
||||
|
||||
fn current_char(&self) -> char {
|
||||
self.input[self.position]
|
||||
}
|
||||
|
||||
fn advance(&mut self) {
|
||||
self.position += 1;
|
||||
}
|
||||
|
||||
fn peek_next(&self) -> Option<char> {
|
||||
self.input.get(self.position + 1).copied()
|
||||
}
|
||||
|
||||
fn skip_whitespace(&mut self) {
|
||||
while self.position < self.input.len() && self.input[self.position].is_whitespace() {
|
||||
self.position += 1;
|
||||
while self.position < self.input.len() && matches!(self.input[self.position], ' ' | '\t') {
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
|
||||
fn read_word(&mut self) -> Token {
|
||||
let start = self.position;
|
||||
while self.position < self.input.len()
|
||||
&& !self.input[self.position].is_whitespace()
|
||||
&& !matches!(
|
||||
self.input[self.position],
|
||||
'=' | '|' | '>' | '<' | '(' | ')' | ';' | '&' | '\n'
|
||||
self.current_char(),
|
||||
' ' | '\t' | '\n' | ';' | '|' | '&' | '=' | '(' | ')' | '>' | '<' | '"'
|
||||
)
|
||||
{
|
||||
self.position += 1;
|
||||
self.advance();
|
||||
}
|
||||
|
||||
let word: String = self.input[start..self.position].iter().collect();
|
||||
match word.as_str() {
|
||||
"if" => Token::If,
|
||||
@@ -100,28 +137,53 @@ impl Lexer {
|
||||
"else" => Token::Else,
|
||||
"fi" => Token::Fi,
|
||||
"while" => Token::While,
|
||||
"for" => Token::For,
|
||||
"do" => Token::Do,
|
||||
"done" => Token::Done,
|
||||
"for" => Token::For,
|
||||
"in" => Token::In,
|
||||
"function" => Token::Function,
|
||||
_ => Token::Word(word),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_string(&mut self) -> Token {
|
||||
self.position += 1; // Skip opening quote
|
||||
self.advance(); // Skip opening quote
|
||||
let start = self.position;
|
||||
while self.position < self.input.len() && self.input[self.position] != '"' {
|
||||
self.position += 1;
|
||||
while self.position < self.input.len() && self.current_char() != '"' {
|
||||
if self.current_char() == '\\' && self.peek_next() == Some('"') {
|
||||
self.advance(); // Skip the backslash
|
||||
}
|
||||
self.advance();
|
||||
}
|
||||
let result = Token::Word(self.input[start..self.position].iter().collect());
|
||||
self.position += 1; // Skip closing quote
|
||||
result
|
||||
let string: String = self.input[start..self.position].iter().collect();
|
||||
if self.position < self.input.len() {
|
||||
self.advance(); // Skip closing quote
|
||||
}
|
||||
Token::String(string)
|
||||
}
|
||||
|
||||
fn read_command_substitution(&mut self) -> String {
|
||||
let mut cmd = String::from("$(");
|
||||
self.advance(); // Skip $
|
||||
self.advance(); // Skip (
|
||||
let mut depth = 1;
|
||||
|
||||
while self.position < self.input.len() && depth > 0 {
|
||||
match self.current_char() {
|
||||
'(' => depth += 1,
|
||||
')' => depth -= 1,
|
||||
_ => {}
|
||||
}
|
||||
cmd.push(self.current_char());
|
||||
self.advance();
|
||||
}
|
||||
cmd
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Lexer {
|
||||
type Item = Token;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.next_token()
|
||||
}
|
||||
|
@@ -13,11 +13,13 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::utilities::utilities::{ASTNode, Token};
|
||||
use crate::utilities::utilities::{ASTNode, RedirectType, Token};
|
||||
|
||||
pub struct Parser {
|
||||
tokens: Vec<Token>,
|
||||
position: usize,
|
||||
recursion_depth: usize,
|
||||
max_recursion_depth: usize,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
@@ -25,9 +27,24 @@ impl Parser {
|
||||
Parser {
|
||||
tokens,
|
||||
position: 0,
|
||||
recursion_depth: 0,
|
||||
max_recursion_depth: 1000,
|
||||
}
|
||||
}
|
||||
|
||||
fn increment_recursion(&mut self) -> Result<(), String> {
|
||||
self.recursion_depth += 1;
|
||||
if self.recursion_depth > self.max_recursion_depth {
|
||||
Err("Maximum recursion depth exceeded".to_string())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn decrement_recursion(&mut self) {
|
||||
self.recursion_depth -= 1;
|
||||
}
|
||||
|
||||
pub fn parse(&mut self) -> Result<Vec<ASTNode>, String> {
|
||||
let mut nodes = Vec::new();
|
||||
while self.position < self.tokens.len() {
|
||||
@@ -39,18 +56,25 @@ impl Parser {
|
||||
}
|
||||
|
||||
fn parse_statement(&mut self) -> Result<ASTNode, String> {
|
||||
match &self.tokens[self.position] {
|
||||
Token::Word(_) => self.parse_command_or_assignment(),
|
||||
Token::LeftParen => self.parse_block(),
|
||||
Token::If => self.parse_if(),
|
||||
Token::While => self.parse_while(),
|
||||
Token::For => self.parse_for(),
|
||||
Token::Function => self.parse_function(),
|
||||
_ => Err(format!(
|
||||
"Unexpected token: {:?}",
|
||||
self.tokens[self.position]
|
||||
)),
|
||||
}
|
||||
self.increment_recursion()?;
|
||||
let result = if self.position >= self.tokens.len() {
|
||||
Err("Unexpected end of input".to_string())
|
||||
} else {
|
||||
match &self.tokens[self.position] {
|
||||
Token::Word(_) => self.parse_command_or_assignment(),
|
||||
Token::LeftParen => self.parse_block(),
|
||||
Token::If => self.parse_if(),
|
||||
Token::While => self.parse_while(),
|
||||
Token::For => self.parse_for(),
|
||||
Token::Function => self.parse_function(),
|
||||
_ => Err(format!(
|
||||
"Unexpected token: {:?}",
|
||||
self.tokens[self.position]
|
||||
)),
|
||||
}
|
||||
};
|
||||
self.decrement_recursion();
|
||||
result
|
||||
}
|
||||
|
||||
fn parse_command_or_assignment(&mut self) -> Result<ASTNode, String> {
|
||||
@@ -67,9 +91,14 @@ impl Parser {
|
||||
|
||||
if self.position < self.tokens.len() && self.tokens[self.position] == Token::Assignment {
|
||||
self.position += 1;
|
||||
let value = match &self.tokens[self.position] {
|
||||
Token::Word(w) => w.clone(),
|
||||
_ => String::new(), // Allow empty assignments
|
||||
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;
|
||||
@@ -88,14 +117,15 @@ impl Parser {
|
||||
| Token::Assignment
|
||||
)
|
||||
{
|
||||
if let Token::Word(w) = &self.tokens[self.position] {
|
||||
args.push(w.clone());
|
||||
self.position += 1;
|
||||
} else {
|
||||
break;
|
||||
match &self.tokens[self.position] {
|
||||
Token::Word(w) => args.push(w.clone()),
|
||||
Token::String(s) => args.push(s.clone()),
|
||||
_ => break,
|
||||
}
|
||||
self.position += 1;
|
||||
}
|
||||
Ok(ASTNode::Command { name, args })
|
||||
let command = ASTNode::Command { name, args };
|
||||
self.parse_pipeline_or_redirect(command)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,14 +143,19 @@ impl Parser {
|
||||
}
|
||||
Token::Redirect(direction) => {
|
||||
self.position += 1;
|
||||
let target = match &self.tokens[self.position] {
|
||||
Token::Word(w) => w.clone(),
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"Expected word after redirect, found {:?}",
|
||||
self.tokens[self.position]
|
||||
))
|
||||
let target = if self.position < self.tokens.len() {
|
||||
match &self.tokens[self.position] {
|
||||
Token::Word(w) => w.clone(),
|
||||
Token::String(s) => s.clone(),
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"Expected word after redirect, found {:?}",
|
||||
self.tokens[self.position]
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err("Unexpected end of input after redirect".to_string());
|
||||
};
|
||||
self.position += 1;
|
||||
let redirect = ASTNode::Redirect {
|
||||
@@ -138,6 +173,18 @@ impl Parser {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_block(&mut self) -> Result<ASTNode, String> {
|
||||
self.position += 1; // Consume left paren
|
||||
let mut statements = Vec::new();
|
||||
while self.position < self.tokens.len() && self.tokens[self.position] != Token::RightParen {
|
||||
statements.push(self.parse_statement()?);
|
||||
self.consume_if(Token::Semicolon);
|
||||
self.consume_if(Token::NewLine);
|
||||
}
|
||||
self.expect_token(Token::RightParen)?;
|
||||
Ok(ASTNode::Block(statements))
|
||||
}
|
||||
|
||||
fn parse_if(&mut self) -> Result<ASTNode, String> {
|
||||
self.position += 1; // Consume 'if'
|
||||
let condition = Box::new(self.parse_command()?);
|
||||
@@ -175,12 +222,12 @@ impl Parser {
|
||||
self.expect_token(Token::In)?;
|
||||
let mut list = Vec::new();
|
||||
while self.position < self.tokens.len() && self.tokens[self.position] != Token::Do {
|
||||
if let Token::Word(w) = &self.tokens[self.position] {
|
||||
list.push(w.clone());
|
||||
self.position += 1;
|
||||
} else {
|
||||
break;
|
||||
match &self.tokens[self.position] {
|
||||
Token::Word(w) => list.push(w.clone()),
|
||||
Token::String(s) => list.push(s.clone()),
|
||||
_ => break,
|
||||
}
|
||||
self.position += 1;
|
||||
}
|
||||
self.expect_token(Token::Do)?;
|
||||
let block = Box::new(self.parse_block()?);
|
||||
@@ -188,46 +235,6 @@ impl Parser {
|
||||
Ok(ASTNode::For { var, list, block })
|
||||
}
|
||||
|
||||
fn parse_block(&mut self) -> Result<ASTNode, String> {
|
||||
let mut statements = Vec::new();
|
||||
while self.position < self.tokens.len()
|
||||
&& !matches!(
|
||||
self.tokens[self.position],
|
||||
Token::Fi | Token::Done | Token::Else
|
||||
)
|
||||
{
|
||||
statements.push(self.parse_statement()?);
|
||||
self.consume_if(Token::Semicolon);
|
||||
self.consume_if(Token::NewLine);
|
||||
}
|
||||
Ok(ASTNode::Block(statements))
|
||||
}
|
||||
|
||||
fn parse_command(&mut self) -> Result<ASTNode, String> {
|
||||
let mut args = Vec::new();
|
||||
while self.position < self.tokens.len()
|
||||
&& !matches!(
|
||||
self.tokens[self.position],
|
||||
Token::Then | Token::Do | Token::Done | Token::Fi | Token::Else
|
||||
)
|
||||
{
|
||||
if let Token::Word(w) = &self.tokens[self.position] {
|
||||
args.push(w.clone());
|
||||
self.position += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if args.is_empty() {
|
||||
Err("Expected command".to_string())
|
||||
} else {
|
||||
Ok(ASTNode::Command {
|
||||
name: args[0].clone(),
|
||||
args: args[1..].to_vec(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_function(&mut self) -> Result<ASTNode, String> {
|
||||
self.position += 1; // Consume 'function'
|
||||
let name = match &self.tokens[self.position] {
|
||||
@@ -244,6 +251,31 @@ impl Parser {
|
||||
Ok(ASTNode::Function { name, body })
|
||||
}
|
||||
|
||||
fn parse_command(&mut self) -> Result<ASTNode, String> {
|
||||
let mut args = Vec::new();
|
||||
while self.position < self.tokens.len()
|
||||
&& !matches!(
|
||||
self.tokens[self.position],
|
||||
Token::Then | Token::Do | Token::Done | Token::Fi | Token::Else
|
||||
)
|
||||
{
|
||||
match &self.tokens[self.position] {
|
||||
Token::Word(w) => args.push(w.clone()),
|
||||
Token::String(s) => args.push(s.clone()),
|
||||
_ => break,
|
||||
}
|
||||
self.position += 1;
|
||||
}
|
||||
if args.is_empty() {
|
||||
Err("Expected command".to_string())
|
||||
} else {
|
||||
Ok(ASTNode::Command {
|
||||
name: args[0].clone(),
|
||||
args: args[1..].to_vec(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_token(&mut self, expected: Token) -> Result<(), String> {
|
||||
if self.position < self.tokens.len() && self.tokens[self.position] == expected {
|
||||
self.position += 1;
|
||||
@@ -252,7 +284,7 @@ impl Parser {
|
||||
Err(format!(
|
||||
"Expected {:?}, found {:?}",
|
||||
expected,
|
||||
self.tokens.get(self.position)
|
||||
self.tokens.get(self.position).unwrap_or(&Token::NewLine)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@@ -16,13 +16,15 @@
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Token {
|
||||
Word(String),
|
||||
String(String),
|
||||
Assignment,
|
||||
Pipe,
|
||||
Redirect(String),
|
||||
LeftParen,
|
||||
RightParen,
|
||||
Redirect(RedirectType),
|
||||
Semicolon,
|
||||
NewLine,
|
||||
Ampersand,
|
||||
LeftParen,
|
||||
RightParen,
|
||||
If,
|
||||
Then,
|
||||
Else,
|
||||
@@ -33,7 +35,23 @@ pub enum Token {
|
||||
For,
|
||||
In,
|
||||
Function,
|
||||
Ampersand,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum RedirectType {
|
||||
Out,
|
||||
Append,
|
||||
In,
|
||||
}
|
||||
|
||||
impl RedirectType {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
RedirectType::Out => ">",
|
||||
RedirectType::Append => ">>",
|
||||
RedirectType::In => "<",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -49,7 +67,7 @@ pub enum ASTNode {
|
||||
Pipeline(Vec<ASTNode>),
|
||||
Redirect {
|
||||
node: Box<ASTNode>,
|
||||
direction: String,
|
||||
direction: RedirectType,
|
||||
target: String,
|
||||
},
|
||||
Block(Vec<ASTNode>),
|
||||
@@ -73,3 +91,12 @@ pub enum ASTNode {
|
||||
},
|
||||
Background(Box<ASTNode>),
|
||||
}
|
||||
|
||||
impl ASTNode {
|
||||
pub fn is_empty_command(&self) -> bool {
|
||||
match self {
|
||||
ASTNode::Command { name, args } => name.is_empty() && args.is_empty(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user