diff --git a/examples/basic_condition.ion b/examples/basic_condition.ion index 2c0c6c4d2afd12e8e152df8a364a64c6c1e0cdb4..10bd1f4513a40fc56154b03c8a78ee18f2ea8d4a 100644 --- a/examples/basic_condition.ion +++ b/examples/basic_condition.ion @@ -1,5 +1,5 @@ -if 3 == 2 - echo fail -else +if test 3 -eq 3 echo pass +else + echo fail end diff --git a/examples/else_if.ion b/examples/else_if.ion new file mode 100644 index 0000000000000000000000000000000000000000..4e75c01bfc866769db3872fa0fa4fe62320e4623 --- /dev/null +++ b/examples/else_if.ion @@ -0,0 +1,7 @@ +if test 1 -gt 5 + echo one +else if test 1 -eq 1 + echo two +else + echo three +end diff --git a/examples/else_if.out b/examples/else_if.out new file mode 100644 index 0000000000000000000000000000000000000000..f719efd430d52bcfc8566a43b2eb655688d38871 --- /dev/null +++ b/examples/else_if.out @@ -0,0 +1 @@ +two diff --git a/examples/fail.ion b/examples/fail.ion index 5dd0e614a08152f65ff1984d50805b9f62c29903..aafe277b20adb6ed148c216c829c39197e7d0348 100644 --- a/examples/fail.ion +++ b/examples/fail.ion @@ -1,4 +1,4 @@ -if 3 == 2 +if test 3 -eq 2 echo pass else echo fail diff --git a/examples/fn.ion b/examples/fn.ion index f5097892927d33b0dd4b681e701ccd98aa1ecdb8..faac03ac1c87118598358acf5b4351f23efc65fc 100644 --- a/examples/fn.ion +++ b/examples/fn.ion @@ -5,3 +5,11 @@ fn test a b c end test hello world goodbye + +fn another_test + for i in 1..10 + echo $i + end +end + +another_test diff --git a/examples/fn.out b/examples/fn.out index 206b65b89fdf5d702ee8ecf3f4e3c35bc3defd56..c77d34931dc6e380bd98c54271da98081f003be4 100644 --- a/examples/fn.out +++ b/examples/fn.out @@ -1,3 +1,12 @@ hello world goodbye +1 +2 +3 +4 +5 +6 +7 +8 +9 diff --git a/examples/for.ion b/examples/for.ion index aedf731b17f4ea1f89a6fa66591922662e5182eb..591235886f5d428692c437456411cd8d9c7783a4 100644 --- a/examples/for.ion +++ b/examples/for.ion @@ -1,3 +1,11 @@ +for i in 1..6 + echo $i +end + +for i in 1...5 + echo $i +end + for i in 1 2 3 4 5 echo $i end diff --git a/examples/for.out b/examples/for.out index 8a1218a1024a212bb3db30becd860315f9f3ac52..90048fced299df43e1088f8e3093c53583a87a94 100644 --- a/examples/for.out +++ b/examples/for.out @@ -3,3 +3,13 @@ 3 4 5 +1 +2 +3 +4 +5 +1 +2 +3 +4 +5 diff --git a/examples/advanced_conditions.ion b/examples/nested_conditions.ion similarity index 76% rename from examples/advanced_conditions.ion rename to examples/nested_conditions.ion index 60230b9882c2e1062b3b2ed86d601c2b495e1dd3..ffcb304ca3e1a898e45f2d32b3136e28a6f9a6a1 100644 --- a/examples/advanced_conditions.ion +++ b/examples/nested_conditions.ion @@ -1,10 +1,10 @@ -if a == a +if test a = a echo true a == a - if b != b + if test b != b echo true b != b else echo false b != b - if 3 > 2 + if test 3 -gt 2 echo "true 3 > 2" else echo "false 3 > 2" diff --git a/examples/advanced_conditions.out b/examples/nested_conditions.out similarity index 100% rename from examples/advanced_conditions.out rename to examples/nested_conditions.out diff --git a/examples/nested_for.ion b/examples/nested_for.ion new file mode 100644 index 0000000000000000000000000000000000000000..2b83bd0bf69cc2da86af98629189d813c3992f8c --- /dev/null +++ b/examples/nested_for.ion @@ -0,0 +1,5 @@ +for a in 0..10 + for b in 1..10 + echo $a$b + end +end diff --git a/examples/nested_for.out b/examples/nested_for.out new file mode 100644 index 0000000000000000000000000000000000000000..12a2653075cb7aa31f53024475b3b059bec5984f --- /dev/null +++ b/examples/nested_for.out @@ -0,0 +1,90 @@ +01 +02 +03 +04 +05 +06 +07 +08 +09 +11 +12 +13 +14 +15 +16 +17 +18 +19 +21 +22 +23 +24 +25 +26 +27 +28 +29 +31 +32 +33 +34 +35 +36 +37 +38 +39 +41 +42 +43 +44 +45 +46 +47 +48 +49 +51 +52 +53 +54 +55 +56 +57 +58 +59 +61 +62 +63 +64 +65 +66 +67 +68 +69 +71 +72 +73 +74 +75 +76 +77 +78 +79 +81 +82 +83 +84 +85 +86 +87 +88 +89 +91 +92 +93 +94 +95 +96 +97 +98 +99 diff --git a/examples/while.ion b/examples/while.ion new file mode 100644 index 0000000000000000000000000000000000000000..8bd808f312bb11236683ebc98c4104bd285a3145 --- /dev/null +++ b/examples/while.ion @@ -0,0 +1,5 @@ +let a = 1 +while test $a -lt 10 + echo $a + let a += 1 +end diff --git a/examples/while.out b/examples/while.out new file mode 100644 index 0000000000000000000000000000000000000000..07193989308c972f8a2d0f1b3a15c29ea4ac565b --- /dev/null +++ b/examples/while.out @@ -0,0 +1,9 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 diff --git a/src/flow_control.rs b/src/flow_control.rs index ad5d2de964f97369924999d019986bca5e0c4e79..929045fcb6b4ec30ecb5cb452820449c883a5d0d 100644 --- a/src/flow_control.rs +++ b/src/flow_control.rs @@ -1,21 +1,34 @@ use parser::peg::Pipeline; +#[derive(Debug, PartialEq, Clone)] +pub struct ElseIf { + pub expression: Pipeline, + pub success: Vec<Statement> +} + #[derive(Debug, PartialEq, Clone)] pub enum Statement { If { - left: String, - comparitor: Comparitor, - right: String + expression: Pipeline, + success: Vec<Statement>, + else_if: Vec<ElseIf>, + failure: Vec<Statement> }, + ElseIf(ElseIf), Function { name: String, - args: Vec<String> + args: Vec<String>, + statements: Vec<Statement> }, - For{ + For { variable: String, values: String, + statements: Vec<Statement> + }, + While { + expression: Pipeline, + statements: Vec<Statement> }, - While { expression: Pipeline }, Else, End, // TODO: Vec is unnecessary here because there will always be one pipeline parsed @@ -23,55 +36,86 @@ pub enum Statement { Default } -impl Statement { - pub fn is_flow_control(&self) -> bool { - match *self { - Statement::If{..} | - Statement::Else | - Statement::For{..} | - Statement::While{..} | - Statement::Function{..} => true, - - Statement::End | - Statement::Pipelines(..) | - Statement::Default => false +pub struct FlowControl { + pub level: usize, + pub current_statement: Statement, + pub current_if_mode: u8 // { 0 = SUCCESS; 1 = FAILURE } +} +impl Default for FlowControl { + fn default() -> FlowControl { + FlowControl { + level: 0, + current_statement: Statement::Default, + current_if_mode: 0, } } } -#[derive(Debug, PartialEq, Clone)] -pub enum Comparitor { - Equal, - NotEqual, - GreaterThan, - LessThan, - GreaterThanOrEqual, - LessThanOrEqual -} - -pub struct CodeBlock { - pub pipelines: Vec<Pipeline>, +#[derive(Clone)] +pub struct Function { + pub name: String, + pub args: Vec<String>, + pub statements: Vec<Statement> } -pub struct Mode { - pub value: bool, +pub fn collect_loops<I>(iterator: &mut I, statements: &mut Vec<Statement>, level: &mut usize) + where I: Iterator<Item = Statement> +{ + #[allow(while_let_on_iterator)] + while let Some(statement) = iterator.next() { + match statement { + Statement::While{..} | Statement::For{..} | Statement::If{..} | + Statement::Function{..} => *level += 1, + Statement::End if *level == 1 => { *level = 0; break }, + Statement::End => *level -= 1, + _ => (), + } + statements.push(statement); + } } -pub struct FlowControl { - pub modes: Vec<Mode>, - pub collecting_block: bool, - pub current_block: CodeBlock, - pub current_statement: Statement, /* pub prompt: &'static str, // Custom prompt while collecting code block */ -} +pub fn collect_if<I>(iterator: &mut I, success: &mut Vec<Statement>, else_if: &mut Vec<ElseIf>, + failure: &mut Vec<Statement>, level: &mut usize, mut current_block: u8) + -> Result<u8, &'static str> + where I: Iterator<Item = Statement> +{ + #[allow(while_let_on_iterator)] + while let Some(statement) = iterator.next() { + match statement { + Statement::While{..} | Statement::For{..} | Statement::If{..} | + Statement::Function{..} => *level += 1, + Statement::ElseIf(ref elseif) if *level == 1 => { + if current_block == 1 { + return Err("ion: syntax error: else block already given"); + } else { + current_block = 2; + else_if.push(elseif.clone()); + continue + } + } + Statement::Else if *level == 1 => { + current_block = 1; + continue + }, + Statement::Else if *level == 1 && current_block == 1 => { + return Err("ion: syntax error: else block already given"); + } + Statement::End if *level == 1 => { *level = 0; break }, + Statement::End => *level -= 1, + _ => (), + } -impl Default for FlowControl { - fn default() -> FlowControl { - FlowControl { - modes: vec![], - collecting_block: false, - current_block: CodeBlock { pipelines: vec![] }, - current_statement: Statement::Default, + match current_block { + 0 => success.push(statement), + 1 => failure.push(statement), + 2 => { + let mut last = else_if.last_mut().unwrap(); // This is a bug if there isn't a value + last.success.push(statement); + } + _ => unreachable!() } } + + Ok(current_block) } diff --git a/src/function.rs b/src/function.rs deleted file mode 100644 index d6edd6d45d534db1cfcaa578f596a21c9c3c784e..0000000000000000000000000000000000000000 --- a/src/function.rs +++ /dev/null @@ -1,8 +0,0 @@ -use parser::peg::Pipeline; - -#[derive(Clone)] -pub struct Function { - pub name: String, - pub pipelines: Vec<Pipeline>, - pub args: Vec<String> -} diff --git a/src/main.rs b/src/main.rs index 409dd4834e205a592fcacc148875bc796241ea1a..7c8df788f13de8bcb750afeff93408549e41e95c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ -#![deny(warnings)] +// #![deny(warnings)] +#![allow(unknown_lints)] #![feature(box_syntax)] #![feature(plugin)] #![plugin(peg_syntax_ext)] @@ -21,12 +22,11 @@ use completer::MultiCompleter; use directory_stack::DirectoryStack; use variables::Variables; use status::*; -use function::Function; use pipe::execute_pipeline; use parser::shell_expand::ExpandErr; use parser::{expand_string, ForExpression, StatementSplitter}; use parser::peg::{parse, Pipeline}; -use flow_control::{FlowControl, Statement, Comparitor}; +use flow_control::{ElseIf, FlowControl, Function, Statement, collect_loops, collect_if}; pub mod completer; pub mod pipe; @@ -34,7 +34,6 @@ pub mod directory_stack; pub mod to_num; pub mod variables; pub mod status; -pub mod function; pub mod flow_control; mod builtins; mod parser; @@ -254,170 +253,326 @@ impl Shell { } pub fn prompt(&self) -> String { - let mut prompt = self.flow_control.modes.iter().rev().fold(String::new(), |acc, mode| { - acc + - if mode.value { - "+ " - } else { - "- " + if self.flow_control.level == 0 { + let prompt_var = self.variables.get_var_or_empty("PROMPT"); + match expand_string(&prompt_var, &self.variables, &self.directory_stack) { + Ok(expanded_string) => expanded_string, + Err(ExpandErr::UnmatchedBraces(position)) => { + let stderr = io::stderr(); + let mut stderr = stderr.lock(); + let _ = writeln!(stderr, "ion: expand error: unmatched braces\n{}\n{}^", + prompt_var, iter::repeat("-").take(position).collect::<String>()); + String::from("ERROR: ") + }, + Err(ExpandErr::InnerBracesNotImplemented) => { + let stderr = io::stderr(); + let mut stderr = stderr.lock(); + let _ = stderr.write_all(b"ion: expand error: inner braces not yet implemented\n"); + String::from("ERROR: ") + } } - }).to_string(); + } else { + " ".repeat(self.flow_control.level as usize) + } + } - match self.flow_control.current_statement { - Statement::For { .. } => { - prompt.push_str("for> "); - }, - Statement::While { .. } => { - prompt.push_str("while> "); - }, - Statement::Function { .. } => { - prompt.push_str("fn> "); - }, - _ => { - let prompt_var = self.variables.get_var_or_empty("PROMPT"); - match expand_string(&prompt_var, &self.variables, &self.directory_stack) { - Ok(ref expanded_string) => prompt.push_str(expanded_string), - Err(ExpandErr::UnmatchedBraces(position)) => { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = writeln!(stderr, "ion: expand error: unmatched braces\n{}\n{}^", - prompt_var, iter::repeat("-").take(position).collect::<String>()); - prompt.push_str("ERROR: "); - }, - Err(ExpandErr::InnerBracesNotImplemented) => { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = stderr.write_all(b"ion: expand error: inner braces not yet implemented\n"); - prompt.push_str("ERROR: "); + fn execute_statements(&mut self, mut statements: Vec<Statement>) { + let mut iterator = statements.drain(..); + while let Some(statement) = iterator.next() { + match statement { + Statement::While { expression, mut statements } => { + self.flow_control.level += 1; + collect_loops(&mut iterator, &mut statements, &mut self.flow_control.level); + self.execute_while(expression, statements); + }, + Statement::For { variable, values, mut statements } => { + self.flow_control.level += 1; + collect_loops(&mut iterator, &mut statements, &mut self.flow_control.level); + self.execute_for(&variable, &values, statements); + }, + Statement::If { expression, mut success, mut else_if, mut failure } => { + self.flow_control.level += 1; + if let Err(why) = collect_if(&mut iterator, &mut success, &mut else_if, + &mut failure, &mut self.flow_control.level, 0) { + let stderr = io::stderr(); + let mut stderr = stderr.lock(); + let _ = writeln!(stderr, "{}", why); + self.flow_control.level = 0; + self.flow_control.current_if_mode = 0; + println!("ABC"); + return + } + self.execute_if(expression, success, else_if, failure); + }, + Statement::Function { name, args, mut statements } => { + self.flow_control.level += 1; + collect_loops(&mut iterator, &mut statements, &mut self.flow_control.level); + self.functions.insert(name.clone(), Function { + name: name, + args: args, + statements: statements + }); + } + Statement::Pipelines(mut pipelines) => { + for pipeline in pipelines.drain(..) { + self.run_pipeline(&pipeline, false); } } + _ => {} } } - - prompt } - fn on_command(&mut self, command_string: &str) { - for statement in StatementSplitter::new(command_string).map(parse) { - if statement.is_flow_control() { - self.flow_control.current_statement = statement.clone(); - self.flow_control.collecting_block = true; - } + fn execute_while(&mut self, expression: Pipeline, statements: Vec<Statement>) { + while self.run_pipeline(&expression, false) == Some(SUCCESS) { + // Cloning is needed so the statement can be re-iterated again if needed. + self.execute_statements(statements.clone()) + } + } - match statement { - Statement::End => self.handle_end(), - Statement::If{left, right, comparitor} => self.handle_if(left, comparitor, right), - Statement::Pipelines(pipelines) => { let _ = self.handle_pipelines(pipelines, false); }, - _ => {} + fn execute_for(&mut self, variable: &str, values: &str, statements: Vec<Statement>) { + match ForExpression::new(values, &self.directory_stack, &self.variables) { + ForExpression::Normal(expression) => { + for value in expression.split_whitespace() { + self.variables.set_var(variable, value); + self.execute_statements(statements.clone()) + } + }, + ForExpression::Range(start, end) => { + for value in (start..end).map(|x| x.to_string()) { + self.variables.set_var(variable, &value); + self.execute_statements(statements.clone()) + } } } } - fn handle_if(&mut self, left: String, comparitor: Comparitor, right: String) { - let left = expand_string(&left, &self.variables, &self.directory_stack).unwrap_or_else(|_| "".to_string()); - let right = expand_string(&right, &self.variables, &self.directory_stack).unwrap_or_else(|_| "".to_string()); - - let value = match comparitor { - Comparitor::GreaterThan => { left > right }, - Comparitor::GreaterThanOrEqual => { left >= right }, - Comparitor::LessThan => { left < right }, - Comparitor::LessThanOrEqual => { left <= right }, - Comparitor::Equal => { left == right }, - Comparitor::NotEqual => { left != right }, - }; - - self.flow_control.modes.push(flow_control::Mode{value: value}) + fn execute_if(&mut self, expression: Pipeline, success: Vec<Statement>, + mut else_if: Vec<ElseIf>, failure: Vec<Statement>) + { + match self.run_pipeline(&expression, false) { + Some(SUCCESS) => self.execute_statements(success), + _ => { + for elseif in else_if.drain(..) { + if self.run_pipeline(&elseif.expression, false) == Some(SUCCESS) { + self.execute_statements(elseif.success); + return + } + } + self.execute_statements(failure); + } + } } - fn handle_end(&mut self){ - self.flow_control.collecting_block = false; - match self.flow_control.current_statement.clone() { - Statement::While{ref expression} => { - let block_jobs: Vec<Pipeline> = self.flow_control.current_block - .pipelines.drain(..).collect(); - while self.run_pipeline(expression, false) == Some(SUCCESS) { - for pipeline in &block_jobs { - self.run_pipeline(pipeline, false); + fn execute_toplevel<I>(&mut self, iterator: &mut I, statement: Statement) -> Result<(), &'static str> + where I: Iterator<Item = Statement> + { + match statement { + // Collect the statements for the while loop, and if the loop is complete, + // execute the while loop with the provided expression. + Statement::While { expression, mut statements } => { + self.flow_control.level += 1; + + // Collect all of the statements contained within the while block. + collect_loops(iterator, &mut statements, &mut self.flow_control.level); + + if self.flow_control.level == 0 { + // All blocks were read, thus we can immediately execute now + self.execute_while(expression, statements); + } else { + // Store the partial `Statement::While` to memory + self.flow_control.current_statement = Statement::While { + expression: expression, + statements: statements, } } }, - Statement::For{variable: ref var, values: ref vals} => { - let block_jobs: Vec<Pipeline> = self.flow_control - .current_block - .pipelines - .drain(..) - .collect(); - - match ForExpression::new(vals.as_str(), &self.directory_stack, &self.variables) { - ForExpression::Normal(expression) => { - for value in expression.split_whitespace() { - self.variables.set_var(var, value); - for pipeline in &block_jobs { - self.run_pipeline(pipeline, false); - } - } - }, - ForExpression::Range(start, end) => { - for value in (start..end).map(|x| x.to_string()) { - self.variables.set_var(var, &value); - for pipeline in &block_jobs { - self.run_pipeline(pipeline, false); - } - } + // Collect the statements for the for loop, and if the loop is complete, + // execute the for loop with the provided expression. + Statement::For { variable, values, mut statements } => { + self.flow_control.level += 1; + + // Collect all of the statements contained within the while block. + collect_loops(iterator, &mut statements, &mut self.flow_control.level); + + if self.flow_control.level == 0 { + // All blocks were read, thus we can immediately execute now + self.execute_for(&variable, &values, statements); + } else { + // Store the partial `Statement::For` to memory + self.flow_control.current_statement = Statement::For { + variable: variable, + values: values, + statements: statements, } } }, - Statement::Function{ref name, ref args} => { - let block_jobs: Vec<Pipeline> = self.flow_control - .current_block - .pipelines - .drain(..) - .collect(); - self.functions.insert(name.clone(), Function { name: name.clone(), pipelines: block_jobs.clone(), args: args.clone() }); + // Collect the statements needed for the `success`, `else_if`, and `failure` + // conditions; then execute the if statement if it is complete. + Statement::If { expression, mut success, mut else_if, mut failure } => { + self.flow_control.level += 1; + + // Collect all of the success and failure statements within the if condition. + // The `mode` value will let us know whether the collector ended while + // collecting the success block or the failure block. + let mode = collect_if(iterator, &mut success, &mut else_if, + &mut failure, &mut self.flow_control.level, 0)?; + + if self.flow_control.level == 0 { + // All blocks were read, thus we can immediately execute now + self.execute_if(expression, success, else_if, failure); + } else { + // Set the mode and partial if statement in memory. + self.flow_control.current_if_mode = mode; + self.flow_control.current_statement = Statement::If { + expression: expression, + success: success, + else_if: else_if, + failure: failure + }; + } }, - Statement::If{..} | Statement::Else => { - self.flow_control.modes.pop(); - if self.flow_control.modes.is_empty() { - let block_jobs: Vec<Pipeline> = self.flow_control - .current_block - .pipelines - .drain(..) - .collect(); - for pipeline in &block_jobs { - self.run_pipeline(pipeline, false); + // Collect the statements needed by the function and add the function to the + // list of functions if it is complete. + Statement::Function { name, args, mut statements } => { + self.flow_control.level += 1; + + // The same logic that applies to loops, also applies here. + collect_loops(iterator, &mut statements, &mut self.flow_control.level); + + if self.flow_control.level == 0 { + // All blocks were read, thus we can add it to the list + self.functions.insert(name.clone(), Function { + name: name, + args: args, + statements: statements + }); + } else { + // Store the partial function declaration in memory. + self.flow_control.current_statement = Statement::Function { + name: name, + args: args, + statements: statements } } }, - _ => { - let block_jobs: Vec<Pipeline> = self.flow_control - .current_block - .pipelines - .drain(..) - .collect(); - for pipeline in &block_jobs { - self.run_pipeline(pipeline, false); + // Simply executes a provide pipeline, immediately. + Statement::Pipelines(mut pipelines) => { + // Immediately execute the command as it has no dependents. + for pipeline in pipelines.drain(..) { + let _ = self.run_pipeline(&pipeline, false); } - } + }, + // At this level, else and else if keywords are forbidden. + Statement::ElseIf{..} | Statement::Else => { + let stderr = io::stderr(); + let mut stderr = stderr.lock(); + let _ = writeln!(stderr, "ion: syntax error: not an if statement"); + }, + // Likewise to else and else if, the end keyword does nothing here. + Statement::End => { + let stderr = io::stderr(); + let mut stderr = stderr.lock(); + let _ = writeln!(stderr, "ion: syntax error: no block to end"); + }, + _ => {} } - self.flow_control.current_statement = Statement::Default; + Ok(()) } - fn handle_pipelines(&mut self, mut pipelines: Vec<Pipeline>, noalias: bool) -> Option<i32> { - let mut return_value = None; - for pipeline in pipelines.drain(..) { - if self.flow_control.collecting_block { - let mode = self.flow_control.modes.last().unwrap_or(&flow_control::Mode{value: false}).value; - match (mode, self.flow_control.current_statement.clone()) { - (true, Statement::If{..}) | (false, Statement::Else) | (_, Statement::While{..}) | - (_, Statement::For{..}) |(_, Statement::Function{..}) => self.flow_control.current_block.pipelines.push(pipeline), - _ => {} + fn on_command(&mut self, command_string: &str) { + let mut iterator = StatementSplitter::new(command_string).map(parse); + + // If the value is set to `0`, this means that we don't need to append to an existing + // partial statement block in memory, but can read and execute new statements. + if self.flow_control.level == 0 { + while let Some(statement) = iterator.next() { + // Executes all statements that it can, and stores the last remaining partial + // statement in memory if needed. We can tell if there is a partial statement + // later if the value of `level` is not set to `0`. + if let Err(why) = self.execute_toplevel(&mut iterator, statement) { + let stderr = io::stderr(); + let mut stderr = stderr.lock(); + let _ = writeln!(stderr, "{}", why); + self.flow_control.level = 0; + self.flow_control.current_if_mode = 0; + return + } + } + } else { + // Appends the newly parsed statements onto the existing statement stored in memory. + match self.flow_control.current_statement { + Statement::While{ ref mut statements, .. } + | Statement::For { ref mut statements, .. } + | Statement::Function { ref mut statements, .. } => + { + collect_loops(&mut iterator, statements, &mut self.flow_control.level); + }, + Statement::If { ref mut success, ref mut else_if, ref mut failure, .. } => { + self.flow_control.current_if_mode = match collect_if(&mut iterator, success, + else_if, failure, &mut self.flow_control.level, + self.flow_control.current_if_mode) { + Ok(mode) => mode, + Err(why) => { + let stderr = io::stderr(); + let mut stderr = stderr.lock(); + let _ = writeln!(stderr, "{}", why); + 4 + } + }; + } + _ => () + } + + // If this is true, an error occurred during the if statement + if self.flow_control.current_if_mode == 4 { + self.flow_control.level = 0; + self.flow_control.current_if_mode = 0; + self.flow_control.current_statement = Statement::Default; + return + } + + // If the level is set to 0, it means that the statement in memory is finished + // and thus is ready for execution. + if self.flow_control.level == 0 { + // Replaces the `current_statement` with a `Default` value to avoid the + // need to clone the value, and clearing it at the same time. + let mut replacement = Statement::Default; + mem::swap(&mut self.flow_control.current_statement, &mut replacement); + + match replacement { + Statement::While { expression, statements } => { + self.execute_while(expression, statements); + }, + Statement::For { variable, values, statements } => { + self.execute_for(&variable, &values, statements); + }, + Statement::Function { name, args, statements } => { + self.functions.insert(name.clone(), Function { + name: name, + args: args, + statements: statements + }); + }, + Statement::If { expression, success, else_if, failure } => { + self.execute_if(expression, success, else_if, failure); + } + _ => () + } + + // Capture any leftover statements. + while let Some(statement) = iterator.next() { + if let Err(why) = self.execute_toplevel(&mut iterator, statement) { + let stderr = io::stderr(); + let mut stderr = stderr.lock(); + let _ = writeln!(stderr, "{}", why); + self.flow_control.level = 0; + self.flow_control.current_if_mode = 0; + return + } } - return_value = None; - } else { - return_value = self.run_pipeline(&pipeline, noalias); } } - return_value } /// Sets the history size for the shell context equal to the HISTORY_SIZE shell variable if it @@ -473,20 +628,17 @@ impl Shell { alias += argument; } - // Execute each statement within the alias and return the last return value. for statement in StatementSplitter::new(&alias).map(parse) { - if statement.is_flow_control() { - self.flow_control.current_statement = statement.clone(); - self.flow_control.collecting_block = true; - } - match statement { - Statement::End => self.handle_end(), - Statement::If{left, right, comparitor} => self.handle_if(left, comparitor, right), - Statement::Pipelines(pipelines) => { - exit_status = self.handle_pipelines(pipelines, true); + Statement::Pipelines(mut pipelines) => for pipeline in pipelines.drain(..) { + exit_status = self.run_pipeline(&pipeline, true); }, - _ => {} + _ => { + exit_status = Some(FAILURE); + let stderr = io::stderr(); + let mut stderr = stderr.lock(); + let _ = writeln!(stderr, "ion: syntax error: alias only supports pipeline arguments"); + } } } } @@ -498,24 +650,23 @@ impl Shell { // Run the 'main' of the command and set exit_status Some((*command.main)(pipeline.jobs[0].args.as_slice(), self)) // Branch else if -> input == shell function and set the exit_status - } else if let Some(function) = self.functions.get(pipeline.jobs[0].command.as_str()).cloned() { + } else if let Some(function) = self.functions.get(pipeline.jobs[0].command.as_str()).cloned() { if pipeline.jobs[0].args.len() - 1 == function.args.len() { let mut variables_backup: HashMap<&str, Option<String>> = HashMap::new(); for (name, value) in function.args.iter().zip(pipeline.jobs[0].args.iter().skip(1)) { variables_backup.insert(name, self.variables.get_var(name)); self.variables.set_var(name, value); } - let mut return_value = None; - for function_pipeline in &function.pipelines { - return_value = self.run_pipeline(function_pipeline, false) - } + + self.execute_statements(function.statements); + for (name, value_option) in &variables_backup { match *value_option { Some(ref value) => self.variables.set_var(name, value), None => {self.variables.unset_var(name);}, } } - return_value + None } else { let stderr = io::stderr(); let mut stderr = stderr.lock(); @@ -830,9 +981,10 @@ impl Command { fn main() { let mut shell = Shell::default(); shell.evaluate_init_file(); - // Clear the history just added by the init file being evaluated. - shell.context.history.buffers.clear(); - shell.set_context_history_from_vars(); + // NOTE: Remove + // // Clear the history just added by the init file being evaluated. + // shell.context.history.buffers.clear(); + // shell.set_context_history_from_vars(); if "1" == shell.variables.get_var_or_empty("HISTORY_FILE_ENABLED") { match shell.context.history.load_history() { diff --git a/src/parser/README.md b/src/parser/README.md new file mode 100644 index 0000000000000000000000000000000000000000..45f89fec733d7e0623a2372968076d891fcec660 --- /dev/null +++ b/src/parser/README.md @@ -0,0 +1,66 @@ +# Ion Shell Parser Logic + +This module handles all of the parsing logic within the Ion shell. The following is the strategy currently in use: + +1. Parse supplied commands into individual statements using the `StatementSplitter`. +2. Map each individual statement to their equivalent `Statement` enum using the peg parser. +3. Later expand shell expressions where required using the `expand_string()` function. + +## Parsing Statements + +First, inputs received by the shell should be parsed with the `StatementSplitter` in the `statements` module. A statement is any command that is separated by a `;`. + +Given the following command: + +```ion +let a = 1; while test $a -lt 100; echo $a; let a += 1; end +``` + +The `StatementSplitter` will parse the string and split it into individual statements. This makes the parsing that comes after much easier to manage. Example below, with one statement per line: + +```ion +let a = 1 +while test $a -lt 100 + echo $a + let a += 1 +end +``` + +### PEG Parser + +Currently, PEG is being used to perform some basic parsing of syntax, but it has a limitation in that it cannot return string references, so at some point it may be replaced for a better solution that can avoid the needless copies. + +The PEG parser will read a supplied statement and determine what kind of statement the Statement is -- collecting the required information for that statement and serving it back up as a `Statement` enum. This will later be pattern matched in the actual shell code to determine which code to execute. + +#### Pipelines Module + +The `pipelines` module is closely related to our `peg` module, in that for a handful of scenarios, such as when parsing `while`, `if`, and regular statements, the `pipelines` module provides a parser that parses pipelines, redirections, and conditional operators in commands, such as the following: + +##### Pipelines Example + +```ion +git remote show local | egrep 'tracked|new' | grep -v master | awk '{print $1}' +``` + +##### Conditionals Example + +```ion +test -e .git && echo $PWD contains .git directory || echo $PWD does not contain a .git directory +``` + +##### Redirection Example + +```ion +cargo build > build.log +``` + +#### Loops Module + +For loops within Ion work uniquely compared to other shells, in that not only does the `ForExpression` parser parse/expand the supplied expression, but it checks if the expanded expression is either an inclusive or exclusive range, then returns the appropriate `ForExpression`. + +### Shell Expansion + +This is one of the most important pieces of the parsing puzzle outside of the basic grammar. The purpose of the `shell_expand` module is to supply a generic expansion library that performs all shell expansions throughout the shell. + +- The `ForExpression` parser uses the `shell_expand` module to expand the supplied expression before evaluating it. +- Pipelines are also expanded before diff --git a/src/parser/grammar.rustpeg b/src/parser/grammar.rustpeg index 26b75576746f10373299e33982af316917107c1d..381ed31ef9ffb0ddc4cf36b9f2b0e75ab148316f 100644 --- a/src/parser/grammar.rustpeg +++ b/src/parser/grammar.rustpeg @@ -1,11 +1,12 @@ use parser::peg::Pipeline; use parser::pipelines; use flow_control::Statement; -use flow_control::Comparitor; +use flow_control::ElseIf; #[pub] parse_ -> Statement = if_ + / else_if_ / else_ / for_ / while_ @@ -15,7 +16,45 @@ parse_ -> Statement #[pub] if_ -> Statement - = whitespace* "if " l:_not_comparitor whitespace c:comparitor whitespace r:_not_comparitor whitespace* { Statement::If{ left: l, comparitor: c, right: r} } + = whitespace* "if" whitespace? command:$(.*) {? + let mut possible_error = None; + let mut pipelines: Vec<Pipeline> = Vec::new(); + + pipelines::collect(&mut pipelines, &mut possible_error, command); + + match possible_error { + Some(error) => Err(error), + None => match pipelines.drain(..).next() { + Some(pipeline) => Ok(Statement::If { + expression: pipeline, + success: Vec::new(), + else_if: Vec::new(), + failure: Vec::new() + }), + None => unreachable!() + } + } + } + +#[pub] +else_if_ -> Statement + = whitespace* "else" whitespace? "if" whitespace? command:$(.*) {? + let mut possible_error = None; + let mut pipelines: Vec<Pipeline> = Vec::new(); + + pipelines::collect(&mut pipelines, &mut possible_error, command); + + match possible_error { + Some(error) => Err(error), + None => match pipelines.drain(..).next() { + Some(pipeline) => Ok(Statement::ElseIf(ElseIf { + expression: pipeline, + success: Vec::new(), + })), + None => unreachable!() + } + } + } #[pub] else_ -> Statement @@ -27,7 +66,13 @@ end_ -> Statement #[pub] fn_ -> Statement - = whitespace* "fn " n:_name whitespace* args:_args whitespace* { Statement::Function{name: n.to_string(), args: args} } + = whitespace* "fn " n:_name whitespace* args:_args whitespace* { + Statement::Function { + name: n.to_string(), + args: args, + statements: Vec::new(), + } + } _name -> String = n:$([A-z]+) { n.to_string() } @@ -41,7 +86,11 @@ _arg -> String #[pub] for_ -> Statement = whitespace* "for" whitespace? n:_name whitespace? "in" whitespace? expr:$(.*) { - Statement::For { variable: n.to_string(), values: expr.to_string() } + Statement::For { + variable: n.to_string(), + values: expr.to_string(), + statements: Vec::new(), + } } #[pub] @@ -55,23 +104,15 @@ while_ -> Statement match possible_error { Some(error) => Err(error), None => match pipelines.drain(..).next() { - Some(pipeline) => Ok(Statement::While { expression: pipeline }), + Some(pipeline) => Ok(Statement::While { + expression: pipeline, + statements: Vec::new() + }), None => unreachable!() } } } -comparitor -> Comparitor - = "==" { Comparitor::Equal } - / "!=" { Comparitor::NotEqual } - / "<=" { Comparitor::LessThanOrEqual } - / ">=" { Comparitor::GreaterThanOrEqual } - / "<" { Comparitor::LessThan } - / ">" { Comparitor::GreaterThan } - -_not_comparitor -> String - = !comparitor n:$([^ ]+) { n.to_string() } - #[pub] pipelines -> Statement = (unused* newline)* [#] .* { Statement::Pipelines(vec![]) } diff --git a/src/parser/peg.rs b/src/parser/peg.rs index 2b87569885dedd7e7998866ee748fcb63e936830..f500cfdfb73efd0a6c5ada3494e3cc2107f9a517 100644 --- a/src/parser/peg.rs +++ b/src/parser/peg.rs @@ -103,7 +103,8 @@ peg_file! grammar("grammar.rustpeg"); #[cfg(test)] mod tests { use super::grammar::*; - use flow_control::{Statement, Comparitor}; + use super::*; + use flow_control::Statement; #[test] fn full_script() { @@ -161,24 +162,20 @@ else #[test] fn parsing_ifs() { // Default case where spaced normally - let parsed_if = if_("if 1 == 2").unwrap(); - let correct_parse = Statement::If{left: "1".to_string(), - comparitor: Comparitor::Equal, - right: "2".to_string()}; + let parsed_if = if_("if test 1 -eq 2").unwrap(); + let correct_parse = Statement::If { + expression: Pipeline::new( + vec!(Job::new( + vec!("test".to_owned(), "1".to_owned(), "-eq".to_owned(), "2".to_owned()), JobKind::Last) + ), None, None), + success: vec!(), + else_if: vec!(), + failure: vec!() + }; assert_eq!(correct_parse, parsed_if); // Trailing spaces after final value - let parsed_if = if_("if 1 == 2 ").unwrap(); - let correct_parse = Statement::If{left: "1".to_string(), - comparitor: Comparitor::Equal, - right: "2".to_string()}; - assert_eq!(correct_parse, parsed_if); - - // Default case where spaced normally - let parsed_if = if_("if 1 <= 2").unwrap(); - let correct_parse = Statement::If{left: "1".to_string(), - comparitor: Comparitor::LessThanOrEqual, - right: "2".to_string()}; + let parsed_if = if_("if test 1 -eq 2 ").unwrap(); assert_eq!(correct_parse, parsed_if); } @@ -222,7 +219,11 @@ else fn parsing_functions() { // Default case where spaced normally let parsed_if = fn_("fn bob").unwrap(); - let correct_parse = Statement::Function{name: "bob".to_string(), args: vec!()}; + let correct_parse = Statement::Function{ + name: "bob".to_string(), + args: vec!(), + statements: vec!() + }; assert_eq!(correct_parse, parsed_if); // Trailing spaces after final value @@ -235,7 +236,11 @@ else // Default case where spaced normally let parsed_if = fn_("fn bob a b").unwrap(); - let correct_parse = Statement::Function{name: "bob".to_string(), args: vec!("a".to_string(), "b".to_string())}; + let correct_parse = Statement::Function{ + name: "bob".to_owned(), + args: vec!("a".to_owned(), "b".to_owned()), + statements: vec!() + }; assert_eq!(correct_parse, parsed_if); // Trailing spaces after final value