latest pushes
This commit is contained in:
parent
fd88fe816b
commit
9124ef7fed
164
README.md
164
README.md
@ -1,16 +1,15 @@
|
|||||||
# bellos
|
# bellos
|
||||||
|
|
||||||
## Bellande Operating System Scripting Language written in Rust
|
## Bellande Operating System Scripting Language Features
|
||||||
- Variable Assignment
|
- **Command Execution**: Run both built-in and external commands.
|
||||||
- Command Execution
|
- **Variable Assignment and Expansion**: Assign and use variables within scripts or interactive mode.
|
||||||
- I/O Redirection
|
- **Control Structures**: Implement logic flow using if-else statements, while loops, and for loops.
|
||||||
- Interactive Mode and File Execution
|
- **Functions**: Define and call custom functions.
|
||||||
- Error handling
|
- **File Operations**: Perform basic file I/O operations.
|
||||||
- Control structures
|
- **Pipelines**: Chain commands together using pipes.
|
||||||
- Functions
|
- **Input/Output Redirection**: Redirect command input and output to and from files.
|
||||||
- Built-in commands
|
- **Background Jobs**: Run commands in the background.
|
||||||
- Environment variables
|
- **Environment Variable Handling**: Access and modify environment variables.
|
||||||
- redirection support
|
|
||||||
|
|
||||||
# Usage of Bellande Rust Executable Builder
|
# Usage of Bellande Rust Executable Builder
|
||||||
- https://github.com/Architecture-Mechanism/bellande_rust_executable
|
- https://github.com/Architecture-Mechanism/bellande_rust_executable
|
||||||
@ -24,6 +23,20 @@
|
|||||||
./bellos hello_world.bellos
|
./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
|
## BELLOS Usage
|
||||||
```
|
```
|
||||||
#!/usr/bin/env bellos
|
#!/usr/bin/env bellos
|
||||||
@ -32,135 +45,6 @@
|
|||||||
# Simple Hello World script
|
# Simple Hello World script
|
||||||
echo "Hello, World!"
|
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
|
## Website Crates
|
||||||
- https://crates.io/crates/bellos
|
- https://crates.io/crates/bellos
|
||||||
|
|
||||||
|
@ -1,22 +1,40 @@
|
|||||||
#!/usr/bin/env bellos
|
#!/usr/bin/env bellos
|
||||||
|
# File: basic_math.bellos
|
||||||
|
|
||||||
# File: file_operations.bellos
|
# Demonstrating arithmetic operations
|
||||||
# Demonstrating file operations
|
|
||||||
|
|
||||||
# Writing to a file
|
echo Basic Math Operations
|
||||||
write test.txt "This is a test file"
|
|
||||||
append test.txt "Adding another line"
|
|
||||||
|
|
||||||
# Reading from a file
|
# Simple echo statements for arithmetic
|
||||||
echo "Contents of test.txt:"
|
echo Addition:
|
||||||
read test.txt
|
echo 5 + 3 = 8
|
||||||
|
|
||||||
# Using a loop to read file line by line
|
echo Subtraction:
|
||||||
echo "Reading file line by line:"
|
echo 10 - 4 = 6
|
||||||
for line in $(read_lines test.txt)
|
|
||||||
do
|
|
||||||
echo "Line: ${line}"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Cleaning up
|
echo Multiplication:
|
||||||
delete test.txt
|
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.
|
||||||
|
@ -3,29 +3,62 @@
|
|||||||
|
|
||||||
# Demonstrating if statements and loops
|
# Demonstrating if statements and loops
|
||||||
|
|
||||||
# If statement
|
# If-else statement
|
||||||
if [ $# -eq 0 ]
|
echo "If-else statement:"
|
||||||
then
|
x=10
|
||||||
echo "No arguments provided"
|
if [ $x -gt 5 ]; then
|
||||||
elif [ $# -eq 1 ]
|
echo "x is greater than 5"
|
||||||
then
|
|
||||||
echo "One argument provided: $1"
|
|
||||||
else
|
else
|
||||||
echo "Multiple arguments provided"
|
echo "x is not greater than 5"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# For loop
|
# Nested if-else
|
||||||
echo "Counting from 1 to 5:"
|
echo "\nNested if-else:"
|
||||||
for i in 1 2 3 4 5
|
y=20
|
||||||
do
|
if [ $x -gt 5 ]; then
|
||||||
echo $i
|
if [ $y -gt 15 ]; then
|
||||||
done
|
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
|
# While loop
|
||||||
echo "Countdown:"
|
echo "\nWhile loop:"
|
||||||
count=5
|
counter=0
|
||||||
while [ $count -gt 0 ]
|
while [ $counter -lt 5 ]; do
|
||||||
do
|
echo "Counter: $counter"
|
||||||
echo $count
|
counter=$((counter + 1))
|
||||||
count=$((count - 1))
|
|
||||||
done
|
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
|
||||||
|
@ -3,21 +3,62 @@
|
|||||||
|
|
||||||
# Demonstrating file operations
|
# Demonstrating file operations
|
||||||
|
|
||||||
# Writing to a file
|
# Create a test file
|
||||||
echo "This is a test file" > test.txt
|
echo "Creating test file..."
|
||||||
echo "Adding another line" >> test.txt
|
echo "Hello, World!" > test.txt
|
||||||
|
|
||||||
# Reading from a file
|
# Read the contents of the file
|
||||||
echo "Contents of test.txt:"
|
echo "\nReading test file:"
|
||||||
cat test.txt
|
cat test.txt
|
||||||
|
|
||||||
# Using a while loop to read file line by line
|
# Append to the file
|
||||||
echo "Reading file line by line:"
|
echo "\nAppending to test file..."
|
||||||
while read -r line
|
echo "This is a new line" >> test.txt
|
||||||
do
|
|
||||||
echo "Line: $line"
|
|
||||||
done < test.txt
|
|
||||||
|
|
||||||
# Cleaning up
|
# Read the updated contents
|
||||||
rm test.txt
|
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
|
||||||
|
@ -3,15 +3,70 @@
|
|||||||
|
|
||||||
# Defining and using functions
|
# Defining and using functions
|
||||||
|
|
||||||
|
# Simple function
|
||||||
function greet() {
|
function greet() {
|
||||||
echo "Hello, $1!"
|
echo "Hello, $1!"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
echo "Testing simple function:"
|
||||||
|
greet "World"
|
||||||
|
greet "Bellos"
|
||||||
|
|
||||||
|
# Function with return value
|
||||||
function add() {
|
function add() {
|
||||||
echo $(($1 + $2))
|
local result=$(($1 + $2))
|
||||||
|
echo $result
|
||||||
}
|
}
|
||||||
|
|
||||||
# Calling functions
|
echo "\nTesting function with return value:"
|
||||||
greet "User"
|
sum=$(add 5 3)
|
||||||
result=$(add 3 4)
|
echo "5 + 3 = $sum"
|
||||||
echo "3 + 4 = $result"
|
|
||||||
|
# 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"
|
||||||
|
@ -1,9 +1,50 @@
|
|||||||
#!/usr/bin/env bellos
|
#!/usr/bin/env bellos
|
||||||
# File: hello_world.bellos
|
# File: hello_world.bellos
|
||||||
|
|
||||||
# Simple Hello World script
|
# Demonstrating hello world
|
||||||
echo "Hello, World!"
|
|
||||||
|
|
||||||
# Using variables
|
# Simple echo
|
||||||
name="Bellos"
|
echo Hello, World!
|
||||||
echo "Welcome to $name programming!"
|
|
||||||
|
# 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.
|
||||||
|
@ -3,21 +3,67 @@
|
|||||||
|
|
||||||
# Demonstrating string manipulation
|
# Demonstrating string manipulation
|
||||||
|
|
||||||
string="Hello, Bellos!"
|
# String concatenation
|
||||||
|
str1="Hello"
|
||||||
|
str2="World"
|
||||||
|
concat="$str1 $str2"
|
||||||
|
echo "Concatenated string: $concat"
|
||||||
|
|
||||||
# String length
|
# String length
|
||||||
echo "Length of string: ${#string}"
|
echo "Length of '$concat': ${#concat}"
|
||||||
|
|
||||||
# Substring
|
# Substring extraction
|
||||||
echo "First 5 characters: ${string:0:5}"
|
echo "Substring (index 0-4): ${concat:0:5}"
|
||||||
|
echo "Substring (from index 6): ${concat:6}"
|
||||||
|
|
||||||
# String replacement
|
# String replacement
|
||||||
new_string=${string/Bellos/World}
|
sentence="The quick brown fox jumps over the lazy dog"
|
||||||
echo "Replaced string: $new_string"
|
echo "Original sentence: $sentence"
|
||||||
|
replaced=${sentence/fox/cat}
|
||||||
|
echo "After replacing 'fox' with 'cat': $replaced"
|
||||||
|
|
||||||
# Converting to uppercase
|
# Replace all occurrences
|
||||||
echo "Uppercase: ${string^^}"
|
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
|
# String to uppercase
|
||||||
echo "Lowercase: ${string,,}"
|
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
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
||||||
tempfile = "3.2"
|
tempfile = "3.2"
|
||||||
|
shellexpand = "3.1.0"
|
||||||
|
Binary file not shown.
@ -21,9 +21,14 @@ mod utilities;
|
|||||||
|
|
||||||
use crate::executor::executor::Executor;
|
use crate::executor::executor::Executor;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::process;
|
||||||
|
|
||||||
fn main() -> Result<(), String> {
|
fn main() {
|
||||||
let mut executor = Executor::new();
|
|
||||||
let args: Vec<String> = env::args().collect();
|
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
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// 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 glob::glob;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::File;
|
use std::fs::{File, OpenOptions};
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
use std::process::{Child, Command, Stdio};
|
use std::process::{Child, Command, Stdio};
|
||||||
@ -289,25 +288,36 @@ impl Interpreter {
|
|||||||
fn execute_redirect(
|
fn execute_redirect(
|
||||||
&mut self,
|
&mut self,
|
||||||
node: ASTNode,
|
node: ASTNode,
|
||||||
direction: String,
|
direction: RedirectType,
|
||||||
target: String,
|
target: String,
|
||||||
) -> Result<Option<i32>, String> {
|
) -> Result<Option<i32>, String> {
|
||||||
let target = self.expand_variables(&target);
|
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 mut file = File::create(&target).map_err(|e| e.to_string())?;
|
||||||
let result = self.capture_output(Box::new(node))?;
|
let result = self.capture_output(Box::new(node))?;
|
||||||
file.write_all(result.as_bytes())
|
file.write_all(result.as_bytes())
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
Ok(Some(0))
|
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 file = File::open(&target).map_err(|e| e.to_string())?;
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
file.read_to_string(&mut input).map_err(|e| e.to_string())?;
|
file.read_to_string(&mut input).map_err(|e| e.to_string())?;
|
||||||
self.execute_with_input(Box::new(node), input)
|
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
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// 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 {
|
pub struct Lexer {
|
||||||
input: Vec<char>,
|
input: Vec<char>,
|
||||||
@ -27,6 +27,15 @@ impl Lexer {
|
|||||||
position: 0,
|
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> {
|
fn next_token(&mut self) -> Option<Token> {
|
||||||
self.skip_whitespace();
|
self.skip_whitespace();
|
||||||
|
|
||||||
@ -34,65 +43,93 @@ impl Lexer {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.input[self.position] {
|
Some(match self.current_char() {
|
||||||
'=' => {
|
' ' | '\t' => {
|
||||||
self.position += 1;
|
self.advance();
|
||||||
Some(Token::Assignment)
|
return self.next_token();
|
||||||
}
|
|
||||||
'|' => {
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
'\n' => {
|
'\n' => {
|
||||||
self.position += 1;
|
self.advance();
|
||||||
Some(Token::NewLine)
|
Token::NewLine
|
||||||
|
}
|
||||||
|
';' => {
|
||||||
|
self.advance();
|
||||||
|
Token::Semicolon
|
||||||
|
}
|
||||||
|
'|' => {
|
||||||
|
self.advance();
|
||||||
|
Token::Pipe
|
||||||
}
|
}
|
||||||
'&' => {
|
'&' => {
|
||||||
self.position += 1;
|
self.advance();
|
||||||
Some(Token::Ampersand)
|
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) {
|
fn skip_whitespace(&mut self) {
|
||||||
while self.position < self.input.len() && self.input[self.position].is_whitespace() {
|
while self.position < self.input.len() && matches!(self.input[self.position], ' ' | '\t') {
|
||||||
self.position += 1;
|
self.advance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_word(&mut self) -> Token {
|
fn read_word(&mut self) -> Token {
|
||||||
let start = self.position;
|
let start = self.position;
|
||||||
while self.position < self.input.len()
|
while self.position < self.input.len()
|
||||||
&& !self.input[self.position].is_whitespace()
|
|
||||||
&& !matches!(
|
&& !matches!(
|
||||||
self.input[self.position],
|
self.current_char(),
|
||||||
'=' | '|' | '>' | '<' | '(' | ')' | ';' | '&' | '\n'
|
' ' | '\t' | '\n' | ';' | '|' | '&' | '=' | '(' | ')' | '>' | '<' | '"'
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
self.position += 1;
|
self.advance();
|
||||||
}
|
}
|
||||||
|
|
||||||
let word: String = self.input[start..self.position].iter().collect();
|
let word: String = self.input[start..self.position].iter().collect();
|
||||||
match word.as_str() {
|
match word.as_str() {
|
||||||
"if" => Token::If,
|
"if" => Token::If,
|
||||||
@ -100,28 +137,53 @@ impl Lexer {
|
|||||||
"else" => Token::Else,
|
"else" => Token::Else,
|
||||||
"fi" => Token::Fi,
|
"fi" => Token::Fi,
|
||||||
"while" => Token::While,
|
"while" => Token::While,
|
||||||
"for" => Token::For,
|
|
||||||
"do" => Token::Do,
|
"do" => Token::Do,
|
||||||
"done" => Token::Done,
|
"done" => Token::Done,
|
||||||
|
"for" => Token::For,
|
||||||
"in" => Token::In,
|
"in" => Token::In,
|
||||||
|
"function" => Token::Function,
|
||||||
_ => Token::Word(word),
|
_ => Token::Word(word),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_string(&mut self) -> Token {
|
fn read_string(&mut self) -> Token {
|
||||||
self.position += 1; // Skip opening quote
|
self.advance(); // Skip opening quote
|
||||||
let start = self.position;
|
let start = self.position;
|
||||||
while self.position < self.input.len() && self.input[self.position] != '"' {
|
while self.position < self.input.len() && self.current_char() != '"' {
|
||||||
self.position += 1;
|
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());
|
let string: String = self.input[start..self.position].iter().collect();
|
||||||
self.position += 1; // Skip closing quote
|
if self.position < self.input.len() {
|
||||||
result
|
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 {
|
impl Iterator for Lexer {
|
||||||
type Item = Token;
|
type Item = Token;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
self.next_token()
|
self.next_token()
|
||||||
}
|
}
|
||||||
|
@ -13,11 +13,13 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// 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 {
|
pub struct Parser {
|
||||||
tokens: Vec<Token>,
|
tokens: Vec<Token>,
|
||||||
position: usize,
|
position: usize,
|
||||||
|
recursion_depth: usize,
|
||||||
|
max_recursion_depth: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parser {
|
impl Parser {
|
||||||
@ -25,9 +27,24 @@ impl Parser {
|
|||||||
Parser {
|
Parser {
|
||||||
tokens,
|
tokens,
|
||||||
position: 0,
|
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> {
|
pub fn parse(&mut self) -> Result<Vec<ASTNode>, String> {
|
||||||
let mut nodes = Vec::new();
|
let mut nodes = Vec::new();
|
||||||
while self.position < self.tokens.len() {
|
while self.position < self.tokens.len() {
|
||||||
@ -39,18 +56,25 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_statement(&mut self) -> Result<ASTNode, String> {
|
fn parse_statement(&mut self) -> Result<ASTNode, String> {
|
||||||
match &self.tokens[self.position] {
|
self.increment_recursion()?;
|
||||||
Token::Word(_) => self.parse_command_or_assignment(),
|
let result = if self.position >= self.tokens.len() {
|
||||||
Token::LeftParen => self.parse_block(),
|
Err("Unexpected end of input".to_string())
|
||||||
Token::If => self.parse_if(),
|
} else {
|
||||||
Token::While => self.parse_while(),
|
match &self.tokens[self.position] {
|
||||||
Token::For => self.parse_for(),
|
Token::Word(_) => self.parse_command_or_assignment(),
|
||||||
Token::Function => self.parse_function(),
|
Token::LeftParen => self.parse_block(),
|
||||||
_ => Err(format!(
|
Token::If => self.parse_if(),
|
||||||
"Unexpected token: {:?}",
|
Token::While => self.parse_while(),
|
||||||
self.tokens[self.position]
|
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> {
|
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 {
|
if self.position < self.tokens.len() && self.tokens[self.position] == Token::Assignment {
|
||||||
self.position += 1;
|
self.position += 1;
|
||||||
let value = match &self.tokens[self.position] {
|
let value = if self.position < self.tokens.len() {
|
||||||
Token::Word(w) => w.clone(),
|
match &self.tokens[self.position] {
|
||||||
_ => String::new(), // Allow empty assignments
|
Token::Word(w) => w.clone(),
|
||||||
|
Token::String(s) => s.clone(),
|
||||||
|
_ => String::new(), // Allow empty assignments
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
};
|
};
|
||||||
if self.position < self.tokens.len() {
|
if self.position < self.tokens.len() {
|
||||||
self.position += 1;
|
self.position += 1;
|
||||||
@ -88,14 +117,15 @@ impl Parser {
|
|||||||
| Token::Assignment
|
| Token::Assignment
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if let Token::Word(w) = &self.tokens[self.position] {
|
match &self.tokens[self.position] {
|
||||||
args.push(w.clone());
|
Token::Word(w) => args.push(w.clone()),
|
||||||
self.position += 1;
|
Token::String(s) => args.push(s.clone()),
|
||||||
} else {
|
_ => break,
|
||||||
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) => {
|
Token::Redirect(direction) => {
|
||||||
self.position += 1;
|
self.position += 1;
|
||||||
let target = match &self.tokens[self.position] {
|
let target = if self.position < self.tokens.len() {
|
||||||
Token::Word(w) => w.clone(),
|
match &self.tokens[self.position] {
|
||||||
_ => {
|
Token::Word(w) => w.clone(),
|
||||||
return Err(format!(
|
Token::String(s) => s.clone(),
|
||||||
"Expected word after redirect, found {:?}",
|
_ => {
|
||||||
self.tokens[self.position]
|
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;
|
self.position += 1;
|
||||||
let redirect = ASTNode::Redirect {
|
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> {
|
fn parse_if(&mut self) -> Result<ASTNode, String> {
|
||||||
self.position += 1; // Consume 'if'
|
self.position += 1; // Consume 'if'
|
||||||
let condition = Box::new(self.parse_command()?);
|
let condition = Box::new(self.parse_command()?);
|
||||||
@ -175,12 +222,12 @@ impl Parser {
|
|||||||
self.expect_token(Token::In)?;
|
self.expect_token(Token::In)?;
|
||||||
let mut list = Vec::new();
|
let mut list = Vec::new();
|
||||||
while self.position < self.tokens.len() && self.tokens[self.position] != Token::Do {
|
while self.position < self.tokens.len() && self.tokens[self.position] != Token::Do {
|
||||||
if let Token::Word(w) = &self.tokens[self.position] {
|
match &self.tokens[self.position] {
|
||||||
list.push(w.clone());
|
Token::Word(w) => list.push(w.clone()),
|
||||||
self.position += 1;
|
Token::String(s) => list.push(s.clone()),
|
||||||
} else {
|
_ => break,
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
self.position += 1;
|
||||||
}
|
}
|
||||||
self.expect_token(Token::Do)?;
|
self.expect_token(Token::Do)?;
|
||||||
let block = Box::new(self.parse_block()?);
|
let block = Box::new(self.parse_block()?);
|
||||||
@ -188,46 +235,6 @@ impl Parser {
|
|||||||
Ok(ASTNode::For { var, list, block })
|
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> {
|
fn parse_function(&mut self) -> Result<ASTNode, String> {
|
||||||
self.position += 1; // Consume 'function'
|
self.position += 1; // Consume 'function'
|
||||||
let name = match &self.tokens[self.position] {
|
let name = match &self.tokens[self.position] {
|
||||||
@ -244,6 +251,31 @@ impl Parser {
|
|||||||
Ok(ASTNode::Function { name, body })
|
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> {
|
fn expect_token(&mut self, expected: Token) -> Result<(), String> {
|
||||||
if self.position < self.tokens.len() && self.tokens[self.position] == expected {
|
if self.position < self.tokens.len() && self.tokens[self.position] == expected {
|
||||||
self.position += 1;
|
self.position += 1;
|
||||||
@ -252,7 +284,7 @@ impl Parser {
|
|||||||
Err(format!(
|
Err(format!(
|
||||||
"Expected {:?}, found {:?}",
|
"Expected {:?}, found {:?}",
|
||||||
expected,
|
expected,
|
||||||
self.tokens.get(self.position)
|
self.tokens.get(self.position).unwrap_or(&Token::NewLine)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,13 +16,15 @@
|
|||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Token {
|
pub enum Token {
|
||||||
Word(String),
|
Word(String),
|
||||||
|
String(String),
|
||||||
Assignment,
|
Assignment,
|
||||||
Pipe,
|
Pipe,
|
||||||
Redirect(String),
|
Redirect(RedirectType),
|
||||||
LeftParen,
|
|
||||||
RightParen,
|
|
||||||
Semicolon,
|
Semicolon,
|
||||||
NewLine,
|
NewLine,
|
||||||
|
Ampersand,
|
||||||
|
LeftParen,
|
||||||
|
RightParen,
|
||||||
If,
|
If,
|
||||||
Then,
|
Then,
|
||||||
Else,
|
Else,
|
||||||
@ -33,7 +35,23 @@ pub enum Token {
|
|||||||
For,
|
For,
|
||||||
In,
|
In,
|
||||||
Function,
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
@ -49,7 +67,7 @@ pub enum ASTNode {
|
|||||||
Pipeline(Vec<ASTNode>),
|
Pipeline(Vec<ASTNode>),
|
||||||
Redirect {
|
Redirect {
|
||||||
node: Box<ASTNode>,
|
node: Box<ASTNode>,
|
||||||
direction: String,
|
direction: RedirectType,
|
||||||
target: String,
|
target: String,
|
||||||
},
|
},
|
||||||
Block(Vec<ASTNode>),
|
Block(Vec<ASTNode>),
|
||||||
@ -73,3 +91,12 @@ pub enum ASTNode {
|
|||||||
},
|
},
|
||||||
Background(Box<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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user