latest pushes

This commit is contained in:
Ronaldson Bellande 2024-10-03 00:14:47 -04:00
parent fd88fe816b
commit 9124ef7fed
14 changed files with 604 additions and 349 deletions

164
README.md
View File

@ -1,16 +1,15 @@
# bellos
## Bellande Operating System Scripting Language written in Rust
- Variable Assignment
- Command Execution
- I/O Redirection
- Interactive Mode and File Execution
- Error handling
- Control structures
- Functions
- Built-in commands
- Environment variables
- redirection support
## Bellande Operating System Scripting Language Features
- **Command Execution**: Run both built-in and external commands.
- **Variable Assignment and Expansion**: Assign and use variables within scripts or interactive mode.
- **Control Structures**: Implement logic flow using if-else statements, while loops, and for loops.
- **Functions**: Define and call custom functions.
- **File Operations**: Perform basic file I/O operations.
- **Pipelines**: Chain commands together using pipes.
- **Input/Output Redirection**: Redirect command input and output to and from files.
- **Background Jobs**: Run commands in the background.
- **Environment Variable Handling**: Access and modify environment variables.
# Usage of Bellande Rust Executable Builder
- https://github.com/Architecture-Mechanism/bellande_rust_executable
@ -24,6 +23,20 @@
./bellos hello_world.bellos
```
## Built-in Commands
### Basic Commands
- **echo [args...]**: Print arguments to standard output.
- **cd [directory]**: Change the current working directory.
- **exit**: Exit the shell.
### File Operations
- **write <filename> <content>**: Write content to a file.
- **append <filename> <content>**: Append content to a file.
- **read <filename>**: Read and display the contents of a file.
- **read_lines <filename>**: Read and display the contents of a file line by line.
- **delete <filename>**: Delete a file.
## BELLOS Usage
```
#!/usr/bin/env bellos
@ -32,135 +45,6 @@
# Simple Hello World script
echo "Hello, World!"
# Using variables
name="Bellos"
echo "Welcome to $name programming!"
```
```
#!/usr/bin/env bellos
# File: basic_math.bellos
# Demonstrating arithmetic operations
a=5
b=3
sum=$((a + b))
difference=$((a - b))
product=$((a * b))
quotient=$((a / b))
echo "Sum: $sum"
echo "Difference: $difference"
echo "Product: $product"
echo "Quotient: $quotient"
```
```
#!/usr/bin/env bellos
# File: control_structures.bellos
# Demonstrating if statements and loops
# If statement
if [ $# -eq 0 ]
then
echo "No arguments provided"
elif [ $# -eq 1 ]
then
echo "One argument provided: $1"
else
echo "Multiple arguments provided"
fi
# For loop
echo "Counting from 1 to 5:"
for i in 1 2 3 4 5
do
echo $i
done
# While loop
echo "Countdown:"
count=5
while [ $count -gt 0 ]
do
echo $count
count=$((count - 1))
done
```
```
#!/usr/bin/env bellos
# File: functions.bellos
# Defining and using functions
function greet() {
echo "Hello, $1!"
}
function add() {
echo $(($1 + $2))
}
# Calling functions
greet "User"
result=$(add 3 4)
echo "3 + 4 = $result"
```
```
#!/usr/bin/env bellos
# File: file_operations.bellos
# Demonstrating file operations
# Writing to a file
echo "This is a test file" > test.txt
echo "Adding another line" >> test.txt
# Reading from a file
echo "Contents of test.txt:"
cat test.txt
# Using a while loop to read file line by line
echo "Reading file line by line:"
while read -r line
do
echo "Line: $line"
done < test.txt
# Cleaning up
rm test.txt
```
```
#!/usr/bin/env bellos
# File: string_manipulation.bellos
# Demonstrating string manipulation
string="Hello, Bellos!"
# String length
echo "Length of string: ${#string}"
# Substring
echo "First 5 characters: ${string:0:5}"
# String replacement
new_string=${string/Bellos/World}
echo "Replaced string: $new_string"
# Converting to uppercase
echo "Uppercase: ${string^^}"
# Converting to lowercase
echo "Lowercase: ${string,,}"
```
## Website Crates
- https://crates.io/crates/bellos

View File

@ -1,22 +1,40 @@
#!/usr/bin/env bellos
# File: basic_math.bellos
# File: file_operations.bellos
# Demonstrating file operations
# Demonstrating arithmetic operations
# Writing to a file
write test.txt "This is a test file"
append test.txt "Adding another line"
echo Basic Math Operations
# Reading from a file
echo "Contents of test.txt:"
read test.txt
# Simple echo statements for arithmetic
echo Addition:
echo 5 + 3 = 8
# Using a loop to read file line by line
echo "Reading file line by line:"
for line in $(read_lines test.txt)
do
echo "Line: ${line}"
done
echo Subtraction:
echo 10 - 4 = 6
# Cleaning up
delete test.txt
echo Multiplication:
echo 6 * 7 = 42
echo Division:
echo 20 / 4 = 5
echo Modulus:
echo 17 % 5 = 2
echo Compound operation:
echo (10 + 5) * 2 = 30
# Using variables (without arithmetic)
echo Using variables:
a=7
b=3
echo a = $a
echo b = $b
# Simple increments and decrements
echo Increment and Decrement:
echo count = 0
echo count after increment: 1
echo count after decrement: 0
echo Basic math operations completed.

View File

@ -3,29 +3,62 @@
# Demonstrating if statements and loops
# If statement
if [ $# -eq 0 ]
then
echo "No arguments provided"
elif [ $# -eq 1 ]
then
echo "One argument provided: $1"
# If-else statement
echo "If-else statement:"
x=10
if [ $x -gt 5 ]; then
echo "x is greater than 5"
else
echo "Multiple arguments provided"
echo "x is not greater than 5"
fi
# For loop
echo "Counting from 1 to 5:"
for i in 1 2 3 4 5
do
echo $i
done
# Nested if-else
echo "\nNested if-else:"
y=20
if [ $x -gt 5 ]; then
if [ $y -gt 15 ]; then
echo "x is greater than 5 and y is greater than 15"
else
echo "x is greater than 5 but y is not greater than 15"
fi
else
echo "x is not greater than 5"
fi
# While loop
echo "Countdown:"
count=5
while [ $count -gt 0 ]
do
echo $count
count=$((count - 1))
echo "\nWhile loop:"
counter=0
while [ $counter -lt 5 ]; do
echo "Counter: $counter"
counter=$((counter + 1))
done
# For loop
echo "\nFor loop:"
for i in 1 2 3 4 5; do
echo "Iteration: $i"
done
# For loop with range
echo "\nFor loop with range:"
for i in $(seq 1 5); do
echo "Number: $i"
done
# Case statement
echo "\nCase statement:"
fruit="apple"
case $fruit in
"apple")
echo "It's an apple"
;;
"banana")
echo "It's a banana"
;;
"orange")
echo "It's an orange"
;;
*)
echo "Unknown fruit"
;;
esac

View File

@ -3,21 +3,62 @@
# Demonstrating file operations
# Writing to a file
echo "This is a test file" > test.txt
echo "Adding another line" >> test.txt
# Create a test file
echo "Creating test file..."
echo "Hello, World!" > test.txt
# Reading from a file
echo "Contents of test.txt:"
# Read the contents of the file
echo "\nReading test file:"
cat test.txt
# Using a while loop to read file line by line
echo "Reading file line by line:"
while read -r line
do
echo "Line: $line"
done < test.txt
# Append to the file
echo "\nAppending to test file..."
echo "This is a new line" >> test.txt
# Cleaning up
rm test.txt
# Read the updated contents
echo "\nReading updated test file:"
cat test.txt
# Write to a new file
echo "\nWriting to a new file..."
echo "This is a new file" > new_file.txt
# Read the new file
echo "\nReading new file:"
cat new_file.txt
# List files in the current directory
echo "\nListing files in the current directory:"
ls -l
# Rename a file
echo "\nRenaming file..."
mv new_file.txt renamed_file.txt
# Check if file exists
echo "\nChecking if files exist:"
if [ -f "test.txt" ]; then
echo "test.txt exists"
else
echo "test.txt does not exist"
fi
if [ -f "new_file.txt" ]; then
echo "new_file.txt exists"
else
echo "new_file.txt does not exist"
fi
if [ -f "renamed_file.txt" ]; then
echo "renamed_file.txt exists"
else
echo "renamed_file.txt does not exist"
fi
# Delete files
echo "\nDeleting files..."
rm test.txt renamed_file.txt
# List files again to confirm deletion
echo "\nListing files after deletion:"
ls -l

View File

@ -3,15 +3,70 @@
# Defining and using functions
# Simple function
function greet() {
echo "Hello, $1!"
}
echo "Testing simple function:"
greet "World"
greet "Bellos"
# Function with return value
function add() {
echo $(($1 + $2))
local result=$(($1 + $2))
echo $result
}
# Calling functions
greet "User"
result=$(add 3 4)
echo "3 + 4 = $result"
echo "\nTesting function with return value:"
sum=$(add 5 3)
echo "5 + 3 = $sum"
# Function with local variables
function calculate_rectangle_area() {
local length=$1
local width=$2
local area=$((length * width))
echo "The area of a rectangle with length $length and width $width is $area"
}
echo "\nTesting function with local variables:"
calculate_rectangle_area 4 5
# Recursive function (factorial)
function factorial() {
if [ $1 -le 1 ]; then
echo 1
else
local sub_fact=$(factorial $(($1 - 1)))
echo $(($1 * sub_fact))
}
}
echo "\nTesting recursive function (factorial):"
for i in 0 1 2 3 4 5; do
result=$(factorial $i)
echo "Factorial of $i is $result"
done
# Function with default parameter
function greet_with_default() {
local name=${1:-"Guest"}
echo "Hello, $name!"
}
echo "\nTesting function with default parameter:"
greet_with_default
greet_with_default "Alice"
# Function that modifies a global variable
global_var=10
function modify_global() {
global_var=$((global_var + 5))
}
echo "\nTesting function that modifies a global variable:"
echo "Before: global_var = $global_var"
modify_global
echo "After: global_var = $global_var"

View File

@ -1,9 +1,50 @@
#!/usr/bin/env bellos
# File: hello_world.bellos
# Simple Hello World script
echo "Hello, World!"
# Demonstrating hello world
# Using variables
name="Bellos"
echo "Welcome to $name programming!"
# Simple echo
echo Hello, World!
# Variable assignment and usage
name=Bellos
echo Hello, $name!
# Simple echoes (replacing control structures)
echo Checking the name:
echo The name is indeed Bellos
echo Counting from 1 to 5:
echo Number: 1
echo Number: 2
echo Number: 3
echo Number: 4
echo Number: 5
echo Demonstrating simple counting:
echo Count is: 0
echo Count is: 1
echo Count is: 2
# File operations
echo Writing to a file...
write test.txt "This is a test file."
echo Reading from the file:
read test.txt
echo Appending to the file...
append test.txt "This is an appended line."
echo Reading the updated file:
read test.txt
echo Deleting the file...
delete test.txt
# Simple echo
echo The current date is:
echo Current date placeholder
echo Demonstrating echo:
echo hello world
echo Script execution completed.

View File

@ -3,21 +3,67 @@
# Demonstrating string manipulation
string="Hello, Bellos!"
# String concatenation
str1="Hello"
str2="World"
concat="$str1 $str2"
echo "Concatenated string: $concat"
# String length
echo "Length of string: ${#string}"
echo "Length of '$concat': ${#concat}"
# Substring
echo "First 5 characters: ${string:0:5}"
# Substring extraction
echo "Substring (index 0-4): ${concat:0:5}"
echo "Substring (from index 6): ${concat:6}"
# String replacement
new_string=${string/Bellos/World}
echo "Replaced string: $new_string"
sentence="The quick brown fox jumps over the lazy dog"
echo "Original sentence: $sentence"
replaced=${sentence/fox/cat}
echo "After replacing 'fox' with 'cat': $replaced"
# Converting to uppercase
echo "Uppercase: ${string^^}"
# Replace all occurrences
many_the="the the the dog the cat the mouse"
replaced_all=${many_the//the/a}
echo "Replace all 'the' with 'a': $replaced_all"
# Converting to lowercase
echo "Lowercase: ${string,,}"
# String to uppercase
uppercase=${sentence^^}
echo "Uppercase: $uppercase"
# String to lowercase
lowercase=${sentence,,}
echo "Lowercase: $lowercase"
# Check if string contains substring
if [[ $sentence == *"fox"* ]]; then
echo "The sentence contains 'fox'"
else
echo "The sentence does not contain 'fox'"
fi
# Split string into array
IFS=' ' read -ra words <<< "$sentence"
echo "Words in the sentence:"
for word in "${words[@]}"; do
echo " $word"
done
# Join array elements into string
joined=$(IFS=", "; echo "${words[*]}")
echo "Joined words: $joined"
# Trim whitespace
whitespace_string=" trim me "
trimmed_string=$(echo $whitespace_string | xargs)
echo "Original string: '$whitespace_string'"
echo "Trimmed string: '$trimmed_string'"
# String comparison
str_a="apple"
str_b="banana"
if [[ $str_a < $str_b ]]; then
echo "$str_a comes before $str_b alphabetically"
else
echo "$str_b comes before $str_a alphabetically"
fi

View File

@ -1,2 +1,3 @@
glob = "0.3.0"
tempfile = "3.2"
shellexpand = "3.1.0"

Binary file not shown.

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

@ -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.advance();
Token::Ampersand
}
'=' => {
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;
Some(Token::Ampersand)
}
'"' => Some(self.read_string()),
_ => Some(self.read_word()),
}
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
}
let result = Token::Word(self.input[start..self.position].iter().collect());
self.position += 1; // Skip closing quote
result
self.advance();
}
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,6 +56,10 @@ impl Parser {
}
fn parse_statement(&mut self) -> Result<ASTNode, String> {
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(),
@ -51,6 +72,9 @@ impl Parser {
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] {
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());
match &self.tokens[self.position] {
Token::Word(w) => args.push(w.clone()),
Token::String(s) => args.push(s.clone()),
_ => break,
}
self.position += 1;
} else {
break;
}
}
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] {
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,
}
}
}