latest pushes

This commit is contained in:
2024-10-03 00:14:47 -04:00
parent 257dcbb92e
commit d6828d5ca6
15 changed files with 925 additions and 369 deletions

View File

@@ -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);
}
}

View File

@@ -16,18 +16,27 @@
use crate::interpreter::interpreter::Interpreter;
use crate::lexer::lexer::Lexer;
use crate::parser::parser::Parser;
use crate::utilities::utilities::Token;
use std::fs::File;
use std::io::{self, BufRead, Write};
use crate::utilities::utilities::{ASTNode, RedirectType, Token};
use shellexpand;
use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use std::io::{self, BufRead, Read, Write};
use std::process::{Command, Stdio};
pub struct Executor {
interpreter: Interpreter,
variables: HashMap<String, String>,
functions: HashMap<String, ASTNode>,
last_exit_status: i32,
}
impl Executor {
pub fn new() -> Self {
Self {
Executor {
interpreter: Interpreter::new(),
variables: HashMap::new(),
functions: HashMap::new(),
last_exit_status: 0,
}
}
@@ -45,20 +54,10 @@ impl Executor {
let file =
File::open(filename).map_err(|e| format!("Error opening file {}: {}", filename, e))?;
let reader = io::BufReader::new(file);
let mut lines = reader.lines();
// Check for shebang
if let Some(Ok(first_line)) = lines.next() {
if !first_line.starts_with("#!") {
// If no shebang, process this line
self.process_line(&first_line, 1)?;
}
}
// Process remaining lines
for (line_num, line) in lines.enumerate() {
for (line_num, line) in reader.lines().enumerate() {
let line = line.map_err(|e| format!("Error reading line: {}", e))?;
self.process_line(&line, line_num + 2)?;
self.process_line(&line, line_num + 1)?;
}
Ok(())
}
@@ -72,10 +71,9 @@ impl Executor {
let lexer = Lexer::new(line.to_string());
let tokens: Vec<Token> = lexer.into_iter().collect();
let mut parser = Parser::new(tokens);
match parser.parse() {
Ok(ast) => {
if let Err(e) = self.interpreter.interpret(ast) {
if let Err(e) = self.execute(ast) {
eprintln!("Error on line {}: {}", line_num, e);
}
}
@@ -98,10 +96,9 @@ impl Executor {
let lexer = Lexer::new(input);
let tokens: Vec<Token> = lexer.into_iter().collect();
let mut parser = Parser::new(tokens);
match parser.parse() {
Ok(ast) => {
if let Err(e) = self.interpreter.interpret(ast) {
if let Err(e) = self.execute(ast) {
eprintln!("Error: {}", e);
}
}
@@ -109,4 +106,308 @@ impl Executor {
}
}
}
pub fn execute(&mut self, nodes: Vec<ASTNode>) -> Result<(), String> {
for node in nodes {
self.execute_node(node)?;
}
Ok(())
}
fn execute_node(&mut self, node: ASTNode) -> Result<String, String> {
match node {
ASTNode::Command { name, args } => self.execute_command(name, args),
ASTNode::Assignment { name, value } => {
let expanded_value = self.expand_variables(&value);
self.variables.insert(name, expanded_value);
Ok(String::new())
}
ASTNode::Pipeline(commands) => self.execute_pipeline(commands),
ASTNode::Redirect {
node,
direction,
target,
} => self.execute_redirect(*node, direction, target),
ASTNode::Block(nodes) => {
let mut last_output = String::new();
for node in nodes {
last_output = self.execute_node(node)?;
}
Ok(last_output)
}
ASTNode::If {
condition,
then_block,
else_block,
} => self.execute_if(*condition, *then_block, else_block.map(|b| *b)),
ASTNode::While { condition, block } => self.execute_while(*condition, *block),
ASTNode::For { var, list, block } => self.execute_for(var, list, *block),
ASTNode::Function { name, body } => {
self.functions.insert(name, *body);
Ok(String::new())
}
ASTNode::Background(node) => self.execute_background(*node),
}
}
fn execute_command(&mut self, name: String, args: Vec<String>) -> Result<String, String> {
let expanded_args: Vec<String> =
args.iter().map(|arg| self.expand_variables(arg)).collect();
let result = match name.as_str() {
"cd" => self.change_directory(&expanded_args),
"echo" => {
let output = expanded_args.join(" ");
println!("{}", output);
Ok(output)
}
"exit" => std::process::exit(0),
"write" => self.handle_write(&expanded_args),
"append" => self.handle_append(&expanded_args),
"read" => self.handle_read(&expanded_args),
"read_lines" => self.handle_read_lines(&expanded_args),
"delete" => self.handle_delete(&expanded_args),
_ => {
if let Some(function) = self.functions.get(&name) {
self.execute_node(function.clone())
} else {
// Execute external command
let output = Command::new(&name)
.args(&expanded_args)
.output()
.map_err(|e| format!("Failed to execute command: {}", e))?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).to_string())
} else {
Err(String::from_utf8_lossy(&output.stderr).to_string())
}
}
}
};
self.last_exit_status = if result.is_ok() { 0 } else { 1 };
result
}
fn change_directory(&self, args: &[String]) -> Result<String, String> {
let new_dir = args.get(0).map(|s| s.as_str()).unwrap_or("~");
let path = shellexpand::tilde(new_dir);
std::env::set_current_dir(path.as_ref())
.map_err(|e| format!("Failed to change directory: {}", e))?;
Ok(String::new())
}
fn execute_pipeline(&mut self, commands: Vec<ASTNode>) -> Result<String, String> {
let mut last_output = Vec::new();
for (i, command) in commands.iter().enumerate() {
let mut child = match command {
ASTNode::Command { name, args } => {
let mut cmd = Command::new(name);
cmd.args(args);
if i > 0 {
cmd.stdin(Stdio::piped());
}
if i < commands.len() - 1 {
cmd.stdout(Stdio::piped());
}
cmd.spawn()
.map_err(|e| format!("Failed to spawn command: {}", e))?
}
_ => return Err("Invalid command in pipeline".to_string()),
};
if i > 0 {
if let Some(mut stdin) = child.stdin.take() {
stdin
.write_all(&last_output)
.map_err(|e| format!("Failed to write to stdin: {}", e))?;
}
}
let output = child
.wait_with_output()
.map_err(|e| format!("Failed to wait for command: {}", e))?;
last_output = output.stdout;
}
Ok(String::from_utf8_lossy(&last_output).to_string())
}
fn execute_redirect(
&mut self,
node: ASTNode,
direction: RedirectType,
target: String,
) -> Result<String, String> {
let output = self.execute_node(node)?;
match direction {
RedirectType::Out => {
let mut file =
File::create(&target).map_err(|e| format!("Failed to create file: {}", e))?;
file.write_all(output.as_bytes())
.map_err(|e| format!("Failed to write to file: {}", e))?;
}
RedirectType::Append => {
let mut file = OpenOptions::new()
.append(true)
.create(true)
.open(&target)
.map_err(|e| format!("Failed to open file: {}", e))?;
file.write_all(output.as_bytes())
.map_err(|e| format!("Failed to append to file: {}", e))?;
}
RedirectType::In => {
let mut file =
File::open(&target).map_err(|e| format!("Failed to open file: {}", e))?;
let mut content = String::new();
file.read_to_string(&mut content)
.map_err(|e| format!("Failed to read file: {}", e))?;
return Ok(content);
}
}
Ok(String::new())
}
fn execute_if(
&mut self,
condition: ASTNode,
then_block: ASTNode,
else_block: Option<ASTNode>,
) -> Result<String, String> {
let condition_result = self.execute_node(condition)?;
if !condition_result.trim().is_empty() && condition_result.trim() != "0" {
self.execute_node(then_block)
} else if let Some(else_block) = else_block {
self.execute_node(else_block)
} else {
Ok(String::new())
}
}
fn execute_while(&mut self, condition: ASTNode, block: ASTNode) -> Result<String, String> {
let mut last_output = String::new();
while {
let condition_result = self.execute_node(condition.clone())?;
!condition_result.trim().is_empty() && condition_result.trim() != "0"
} {
last_output = self.execute_node(block.clone())?;
}
Ok(last_output)
}
fn execute_for(
&mut self,
var: String,
list: Vec<String>,
block: ASTNode,
) -> Result<String, String> {
let mut last_output = String::new();
for item in list {
self.variables.insert(var.clone(), item);
last_output = self.execute_node(block.clone())?;
}
Ok(last_output)
}
fn execute_background(&mut self, node: ASTNode) -> Result<String, String> {
std::thread::spawn(move || {
let mut executor = Executor::new();
if let Err(e) = executor.execute_node(node) {
eprintln!("Background job error: {}", e);
}
});
Ok(String::new())
}
fn expand_variables(&self, input: &str) -> String {
let mut result = String::new();
let mut chars = input.chars().peekable();
while let Some(c) = chars.next() {
if c == '$' {
let var_name: String = chars
.by_ref()
.take_while(|&c| c.is_alphanumeric() || c == '_')
.collect();
if var_name == "?" {
result.push_str(&self.last_exit_status.to_string());
} else if var_name == "#" {
// Assuming we don't have access to script arguments in this context
result.push_str("0");
} else if let Some(value) = self.variables.get(&var_name) {
result.push_str(value);
} else if let Ok(value) = std::env::var(&var_name) {
result.push_str(&value);
}
} else {
result.push(c);
}
}
result
}
// File handling methods
fn handle_write(&self, args: &[String]) -> Result<String, String> {
if args.len() != 2 {
return Err("Usage: write <filename> <content>".to_string());
}
let filename = &args[0];
let content = &args[1];
std::fs::write(filename, content).map_err(|e| format!("Failed to write to file: {}", e))?;
Ok(format!("Successfully wrote to {}", filename))
}
fn handle_append(&self, args: &[String]) -> Result<String, String> {
if args.len() != 2 {
return Err("Usage: append <filename> <content>".to_string());
}
let filename = &args[0];
let content = &args[1];
use std::fs::OpenOptions;
use std::io::Write;
let mut file = OpenOptions::new()
.append(true)
.create(true)
.open(filename)
.map_err(|e| format!("Failed to open file for appending: {}", e))?;
writeln!(file, "{}", content).map_err(|e| format!("Failed to append to file: {}", e))?;
Ok(format!("Successfully appended to {}", filename))
}
fn handle_read(&self, args: &[String]) -> Result<String, String> {
if args.len() != 1 {
return Err("Usage: read <filename>".to_string());
}
let filename = &args[0];
let content =
std::fs::read_to_string(filename).map_err(|e| format!("Failed to read file: {}", e))?;
Ok(content)
}
fn handle_read_lines(&self, args: &[String]) -> Result<String, String> {
if args.len() != 1 {
return Err("Usage: read_lines <filename>".to_string());
}
self.handle_read(args)
}
fn handle_delete(&self, args: &[String]) -> Result<String, String> {
if args.len() != 1 {
return Err("Usage: delete <filename>".to_string());
}
let filename = &args[0];
std::fs::remove_file(filename).map_err(|e| format!("Failed to delete file: {}", e))?;
Ok(format!("Successfully deleted {}", filename))
}
}

View File

@@ -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)),
}
}

View File

@@ -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()
}

View File

@@ -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)
))
}
}

View File

@@ -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,
}
}
}