diff --git a/README.md b/README.md index 62298af..40eeea7 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ # Usage of Bellande Rust Executable Builder - https://github.com/Architecture-Mechanism/bellande_rust_executable -- ```bellande_rust_executable -d dependencies.txt -s src -m bellos.rs -o executable/bellos``` +- ```bellande_rust_executable -d dependencies.bellande -s src -m bellos.rs -o executable/bellos``` # Usage of Bellande Rust Importer - https://github.com/Architecture-Mechanism/bellande_importer @@ -37,7 +37,6 @@ ``` ## Built-in Commands - ### Basic Commands - **echo [args...]**: Print arguments to standard output. - **cd [directory]**: Change the current working directory. diff --git a/bellos_scripts/basic_math.bellos b/bellos_scripts/basic_math.bellos old mode 100644 new mode 100755 diff --git a/bellos_scripts/control_structures.bellos b/bellos_scripts/control_structures.bellos index 986f9b7..82df9d4 100644 --- a/bellos_scripts/control_structures.bellos +++ b/bellos_scripts/control_structures.bellos @@ -1,54 +1,52 @@ #!/usr/bin/env bellos # File: control_structures.bellos -# Demonstrating if statements and loops - -# If-else statement -echo "If-else statement:" +# Demonstrating if-else statements +echo "Demonstrating if-else statements:" x=10 -if [ $x -gt 5 ]; then +If [ $x -gt 5 ] Then echo "x is greater than 5" -else +Else echo "x is not greater than 5" -fi +Fi -# Nested if-else -echo "\nNested if-else:" +# Demonstrating nested if-else +echo "Demonstrating nested if-else:" y=20 -if [ $x -gt 5 ]; then - if [ $y -gt 15 ]; then +If [ $x -gt 5 ] Then + If [ $y -gt 15 ] Then echo "x is greater than 5 and y is greater than 15" - else + Else echo "x is greater than 5 but y is not greater than 15" - fi -else + Fi +Else echo "x is not greater than 5" -fi +Fi -# While loop -echo "\nWhile loop:" +# Demonstrating while loop +echo "Demonstrating while loop:" counter=0 -while [ $counter -lt 5 ]; do +While [ $counter -lt 5 ] Do echo "Counter: $counter" counter=$((counter + 1)) -done +Done -# For loop -echo "\nFor loop:" -for i in 1 2 3 4 5; do +# Demonstrating for loop +echo "Demonstrating for loop:" +For i In 1 2 3 4 5 Do echo "Iteration: $i" -done +Done -# For loop with range -echo "\nFor loop with range:" -for i in $(seq 1 5); do +# Demonstrating for loop with command substitution +echo "Demonstrating for loop with command substitution:" +For i In $(seq 1 5) Do echo "Number: $i" -done +Done -# Case statement -echo "\nCase statement:" +# Demonstrating case statement +echo "Demonstrating case statement:" fruit="apple" -case $fruit in +Case $fruit In "apple") echo "It's an apple" ;; @@ -61,4 +59,14 @@ case $fruit in *) echo "Unknown fruit" ;; -esac +Esac + +# Demonstrating function +echo "Demonstrating function:" +Function greet ( + echo "Hello, $1!" +) +greet "World" +greet "Bellos" + +echo "Control structures demonstration completed." diff --git a/bellos_scripts/file_operations.bellos b/bellos_scripts/file_operations.bellos old mode 100644 new mode 100755 diff --git a/bellos_scripts/string_manipulation.bellos b/bellos_scripts/string_manipulation.bellos old mode 100644 new mode 100755 index 47f2eb0..e0f39d9 --- a/bellos_scripts/string_manipulation.bellos +++ b/bellos_scripts/string_manipulation.bellos @@ -1,37 +1,54 @@ #!/usr/bin/env bellos + # File: string_manipulation.bellos +# Demonstrating basic string manipulation in Bellos -# Demonstrating string manipulation - -# String concatenation +# String assignment +echo "String assignment:" first_name=John last_name=Doe -full_name="$first_name $last_name" -echo Full name: $full_name +echo "first_name: $first_name" +echo "last_name: $last_name" +echo -# String length -string="Hello, World!" -echo The string '$string' has ${#string} characters. +# String concatenation +echo "String concatenation:" +full_name="$first_name $last_name" +echo full_name: $full_name +echo -# Substring extraction -echo The first 5 characters are: ${string:0:5} +# Using strings in commands +echo "Using strings in commands:" +greeting=Hello +echo "$greeting, $full_name!" +echo -# String replacement +# Quoting strings +echo "Quoting strings:" sentence="The quick brown fox jumps over the lazy dog" -echo Original sentence: $sentence -new_sentence=${sentence/fox/cat} -echo Modified sentence: $new_sentence +echo With double quotes: "$sentence" +echo With single quotes: '$sentence' +echo -# Converting to uppercase and lowercase -echo Uppercase: ${string^^} -echo Lowercase: ${string,,} +# Assigning command output to a variable +echo "Assigning command output to a variable:" +current_date=10-17-2025 +echo "The current date is: $current_date" +echo -# Trimming whitespace -padded_string=" trim me " -echo Original string: '$padded_string' -trimmed_string="${padded_string#"${padded_string%%[![:space:]]*}"}" # Trim leading whitespace -trimmed_string="${trimmed_string%"${trimmed_string##*[![:space:]]}"}" # Trim trailing whitespace -echo Trimmed string: '$trimmed_string' +# Escaping special characters +echo "Escaping special characters:" +path=/home/user/documents +echo "Path with slashes: $path" +echo "Dollar sign: $100" +echo + +# Using variables in paths +echo "Using variables in paths:" +username=johndoe +user_home=/home/$username +echo "User's home directory: $user_home" +echo # Completion message -echo String manipulation operations completed. +echo "String manipulation operations completed." diff --git a/executable/bellos b/executable/bellos index 61cd72d..079a1bf 100755 Binary files a/executable/bellos and b/executable/bellos differ diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 36e9d6f..378a288 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -18,8 +18,6 @@ use crate::utilities::utilities::{ASTNode, Token}; pub struct Parser { tokens: Vec, position: usize, - recursion_depth: usize, - max_recursion_depth: usize, } impl Parser { @@ -27,199 +25,48 @@ 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, String> { let mut nodes = Vec::new(); while self.position < self.tokens.len() { + self.skip_newlines(); + if self.position >= self.tokens.len() { + break; + } nodes.push(self.parse_statement()?); - self.consume_if(Token::Semicolon); - self.consume_if(Token::NewLine); } Ok(nodes) } fn parse_statement(&mut self) -> Result { - 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 { - let name = match &self.tokens[self.position] { - Token::Word(w) => w.clone(), - _ => { - return Err(format!( - "Expected word, found {:?}", - self.tokens[self.position] - )) - } - }; - self.position += 1; - - if self.position < self.tokens.len() && self.tokens[self.position] == Token::Assignment { - self.position += 1; - let value = self.parse_expression()?; - Ok(ASTNode::Assignment { name, value }) - } else { - let mut args = Vec::new(); - while self.position < self.tokens.len() - && !matches!( - self.tokens[self.position], - Token::Pipe - | Token::Redirect(_) - | Token::Semicolon - | Token::NewLine - | Token::Ampersand - | Token::Assignment - ) - { - match &self.tokens[self.position] { - Token::Word(w) => args.push(w.clone()), - Token::String(s) => args.push(s.clone()), - _ => break, - } - self.position += 1; - } - let command = ASTNode::Command { name, args }; - self.parse_pipeline_or_redirect(command) - } - } - - fn parse_expression(&mut self) -> Result { - let mut expression = String::new(); - let mut paren_count = 0; - - while self.position < self.tokens.len() { - match &self.tokens[self.position] { - Token::Word(w) => expression.push_str(w), - Token::String(s) => { - expression.push('"'); - expression.push_str(s); - expression.push('"'); - } - Token::LeftParen => { - expression.push('('); - paren_count += 1; - } - Token::RightParen if paren_count > 0 => { - expression.push(')'); - paren_count -= 1; - } - Token::Semicolon | Token::NewLine if paren_count == 0 => break, - Token::Assignment => expression.push('='), - _ if paren_count == 0 => break, - _ => expression.push_str(&format!("{:?}", self.tokens[self.position])), - } - self.position += 1; - } - - if paren_count != 0 { - return Err("Mismatched parentheses in expression".to_string()); - } - - Ok(expression) - } - - fn parse_pipeline_or_redirect(&mut self, left: ASTNode) -> Result { if self.position >= self.tokens.len() { - return Ok(left); + return Err("Unexpected end of input".to_string()); } - match &self.tokens[self.position] { - Token::Pipe => { - self.position += 1; - let right = self.parse_command_or_assignment()?; - let pipeline = ASTNode::Pipeline(vec![left, right]); - self.parse_pipeline_or_redirect(pipeline) - } - Token::Redirect(direction) => { - self.position += 1; - 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 { - node: Box::new(left), - direction: direction.clone(), - target, - }; - self.parse_pipeline_or_redirect(redirect) - } - Token::Ampersand => { - self.position += 1; - Ok(ASTNode::Background(Box::new(left))) - } - _ => Ok(left), + Token::Word(w) if w.eq_ignore_ascii_case("if") => self.parse_if(), + Token::Word(w) if w.eq_ignore_ascii_case("while") => self.parse_while(), + Token::Word(w) if w.eq_ignore_ascii_case("for") => self.parse_for(), + Token::Word(w) if w.eq_ignore_ascii_case("case") => self.parse_case(), + Token::Word(w) if w.eq_ignore_ascii_case("function") => self.parse_function(), + _ => self.parse_command_or_assignment(), } } - fn parse_block(&mut self) -> Result { - 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 { self.position += 1; // Consume 'if' let condition = Box::new(self.parse_command()?); - self.expect_token(Token::Then)?; - let then_block = Box::new(self.parse_block()?); - let else_block = if self.consume_if(Token::Else) { - Some(Box::new(self.parse_block()?)) + self.skip_newlines_and_expect("then")?; + let then_block = Box::new(self.parse_block("else", "fi")?); + let else_block = if self.current_token_is("else") { + self.position += 1; + self.skip_newlines(); + Some(Box::new(self.parse_block("fi", "fi")?)) } else { None }; - self.expect_token(Token::Fi)?; + self.skip_newlines_and_expect("fi")?; Ok(ASTNode::If { condition, then_block, @@ -230,65 +77,70 @@ impl Parser { fn parse_while(&mut self) -> Result { self.position += 1; // Consume 'while' let condition = Box::new(self.parse_command()?); - self.expect_token(Token::Do)?; - let block = Box::new(self.parse_block()?); - self.expect_token(Token::Done)?; + self.skip_newlines_and_expect("do")?; + let block = Box::new(self.parse_block("done", "done")?); + self.skip_newlines_and_expect("done")?; Ok(ASTNode::While { condition, block }) } fn parse_for(&mut self) -> Result { self.position += 1; // Consume 'for' - let var = match &self.tokens[self.position] { - Token::Word(w) => w.clone(), - _ => return Err("Expected variable name after 'for'".to_string()), - }; - self.position += 1; - self.expect_token(Token::In)?; - let mut list = Vec::new(); - while self.position < self.tokens.len() && self.tokens[self.position] != Token::Do { - 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()?); - self.expect_token(Token::Done)?; + let var = self.expect_word()?; + self.skip_newlines_and_expect("in")?; + let list = self.parse_list()?; + self.skip_newlines_and_expect("do")?; + let block = Box::new(self.parse_block("done", "done")?); + self.skip_newlines_and_expect("done")?; Ok(ASTNode::For { var, list, block }) } + fn parse_case(&mut self) -> Result { + self.position += 1; // Consume 'case' + let var = self.parse_expression()?; + self.skip_newlines_and_expect("in")?; + let mut cases = Vec::new(); + while !self.current_token_is("esac") { + self.skip_newlines(); + let pattern = self.parse_expression()?; + self.expect_token(&Token::RightParen)?; + let block = self.parse_block(";;", "esac")?; + cases.push((pattern, block)); + self.skip_newlines(); + if self.current_token_is(";;") { + self.position += 1; + } + } + self.skip_newlines_and_expect("esac")?; + Ok(ASTNode::Case { var, cases }) + } + fn parse_function(&mut self) -> Result { self.position += 1; // Consume 'function' - let name = match &self.tokens[self.position] { - Token::Word(w) => w.clone(), - _ => { - return Err(format!( - "Expected function name, found {:?}", - self.tokens[self.position] - )) - } - }; - self.position += 1; - let body = Box::new(self.parse_block()?); + let name = self.expect_word()?; + self.skip_newlines(); + self.expect_token(&Token::LeftParen)?; + self.skip_newlines(); + let body = Box::new(self.parse_block(")", ")")?); + self.expect_token(&Token::RightParen)?; Ok(ASTNode::Function { name, body }) } + fn parse_block(&mut self, end_token1: &str, end_token2: &str) -> Result { + let mut statements = Vec::new(); + while !self.current_token_is(end_token1) && !self.current_token_is(end_token2) { + self.skip_newlines(); + if self.current_token_is(end_token1) || self.current_token_is(end_token2) { + break; + } + statements.push(self.parse_statement()?); + } + Ok(ASTNode::Block(statements)) + } + fn parse_command(&mut self) -> Result { 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; + while self.position < self.tokens.len() && !self.is_command_end() { + args.push(self.expect_word()?); } if args.is_empty() { Err("Expected command".to_string()) @@ -300,25 +152,109 @@ impl Parser { } } - fn expect_token(&mut self, expected: Token) -> Result<(), String> { - if self.position < self.tokens.len() && self.tokens[self.position] == expected { + fn parse_list(&mut self) -> Result, String> { + let mut list = Vec::new(); + while !self.current_token_is("do") { + list.push(self.expect_word()?); + self.skip_newlines(); + } + Ok(list) + } + + fn parse_expression(&mut self) -> Result { + self.expect_word() + } + + fn expect_word(&mut self) -> Result { + if self.position >= self.tokens.len() { + return Err("Unexpected end of input".to_string()); + } + match &self.tokens[self.position] { + Token::Word(w) | Token::String(w) => { + self.position += 1; + Ok(w.clone()) + } + _ => Err(format!( + "Expected word, found {:?}", + self.tokens[self.position] + )), + } + } + + fn expect_token(&mut self, expected: &Token) -> Result<(), String> { + if self.position >= self.tokens.len() { + return Err(format!("Expected {:?}, found end of input", expected)); + } + if self.tokens[self.position] == *expected { self.position += 1; Ok(()) } else { Err(format!( "Expected {:?}, found {:?}", - expected, - self.tokens.get(self.position).unwrap_or(&Token::NewLine) + expected, self.tokens[self.position] )) } } - fn consume_if(&mut self, token: Token) -> bool { - if self.position < self.tokens.len() && self.tokens[self.position] == token { + fn current_token_is(&self, token: &str) -> bool { + if self.position >= self.tokens.len() { + return false; + } + match &self.tokens[self.position] { + Token::Word(w) => w.eq_ignore_ascii_case(token), + _ => false, + } + } + + fn skip_newlines(&mut self) { + while self.position < self.tokens.len() && self.tokens[self.position] == Token::NewLine { self.position += 1; - true + } + } + + fn skip_newlines_and_expect(&mut self, expected: &str) -> Result<(), String> { + self.skip_newlines(); + if self.position >= self.tokens.len() { + return Err(format!("Expected {}, found end of input", expected)); + } + if self.current_token_is(expected) { + self.position += 1; + Ok(()) } else { - false + Err(format!( + "Expected {}, found {:?}", + expected, self.tokens[self.position] + )) + } + } + + fn is_command_end(&self) -> bool { + self.position >= self.tokens.len() + || matches!( + self.tokens[self.position], + Token::Semicolon | Token::NewLine + ) + || self.current_token_is("then") + || self.current_token_is("do") + || self.current_token_is("done") + || self.current_token_is("fi") + || self.current_token_is("else") + || self.current_token_is("elif") + || self.current_token_is("esac") + } + + fn parse_command_or_assignment(&mut self) -> Result { + let name = self.expect_word()?; + if self.position < self.tokens.len() && self.tokens[self.position] == Token::Assignment { + self.position += 1; + let value = self.parse_expression()?; + Ok(ASTNode::Assignment { name, value }) + } else { + let mut args = Vec::new(); + while self.position < self.tokens.len() && !self.is_command_end() { + args.push(self.expect_word()?); + } + Ok(ASTNode::Command { name, args }) } } } diff --git a/src/utilities/utilities.rs b/src/utilities/utilities.rs index 779d0f4..d2f27ca 100644 --- a/src/utilities/utilities.rs +++ b/src/utilities/utilities.rs @@ -34,6 +34,8 @@ pub enum Token { Done, For, In, + Case, + Esac, Function, } @@ -90,6 +92,10 @@ pub enum ASTNode { body: Box, }, Background(Box), + Case { + var: String, + cases: Vec<(String, ASTNode)>, + }, } impl ASTNode {