From 2a5d914630d54d7aa454a8f6b18d3901bae61bf2 Mon Sep 17 00:00:00 2001 From: Hunter Goldstein <hunter.d.goldstein@gmail.com> Date: Sat, 17 Jun 2017 18:21:41 -0400 Subject: [PATCH] Move `export` into the grammar (#306) * Move `export` from builtin to grammar * Refactor common parts of `let` and `export` logic * `export` now prints `env` --- src/builtins/mod.rs | 11 +-- src/builtins/variables.rs | 53 ----------- src/parser/grammar.rustpeg | 7 ++ src/shell/assignments.rs | 189 +++++++++++++++++-------------------- src/shell/flow.rs | 11 ++- src/shell/flow_control.rs | 1 + 6 files changed, 104 insertions(+), 168 deletions(-) diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 3ad6814d..903f5ab3 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -8,7 +8,7 @@ mod echo; mod calc; mod set; -use self::variables::{alias, drop_alias, drop_variable, export_variable}; +use self::variables::{alias, drop_alias, drop_variable}; use self::functions::fn_; use self::source::source; use self::echo::echo; @@ -129,15 +129,6 @@ impl Builtin { }); /* Variables */ - commands.insert("export", - Builtin { - name: "export", - help: "Set an environment variable", - main: Box::new(|args: &[&str], shell: &mut Shell| -> i32 { - export_variable(&mut shell.variables, args) - }) - }); - commands.insert("fn", Builtin { name: "fn", diff --git a/src/builtins/variables.rs b/src/builtins/variables.rs index 5f502b27..9a7e9309 100644 --- a/src/builtins/variables.rs +++ b/src/builtins/variables.rs @@ -1,6 +1,5 @@ // TODO: Move into grammar -use std::env; use std::io::{self, Write}; use types::*; @@ -183,58 +182,6 @@ pub fn drop_variable<I: IntoIterator>(vars: &mut Variables, args: I) -> i32 } -/// Exporting a variable sets that variable as a global variable in the system. -/// Global variables can be accessed by other programs running on the system. -pub fn export_variable<'a, S: AsRef<str> + 'a>(vars: &mut Variables, args: &[S]) -> i32 -{ - match parse_assignment(args) { - Binding::InvalidKey(key) => { - let stderr = io::stderr(); - let _ = writeln!(&mut stderr.lock(), "ion: variable name, '{}', is invalid", key); - return FAILURE - }, - Binding::KeyValue(key, value) => env::set_var(key, value), - Binding::KeyOnly(key) => { - if let Some(local_value) = vars.get_var(&key) { - env::set_var(key, local_value); - } else { - let stderr = io::stderr(); - let _ = writeln!(&mut stderr.lock(), "ion: unknown variable, '{}'", key); - return FAILURE; - } - }, - Binding::Math(key, operator, increment) => { - let value = vars.get_var(&key).unwrap_or_else(|| "".into()); - match value.parse::<f32>() { - Ok(old_value) => match operator { - Operator::Plus => env::set_var(key, (old_value + increment).to_string()), - Operator::Minus => env::set_var(key, (old_value - increment).to_string()), - Operator::Multiply => env::set_var(key, (old_value * increment).to_string()), - Operator::Divide => env::set_var(key, (old_value / increment).to_string()), - }, - Err(_) => { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = writeln!(stderr, "ion: original value, {}, is not a number", value); - return FAILURE; - } - } - }, - Binding::MathInvalid(value) => { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = writeln!(stderr, "ion: supplied value, {}, is not a number", value); - return FAILURE; - }, - _ => { - let stderr = io::stderr(); - let _ = writeln!(&mut stderr.lock(), "ion usage: export KEY=VALUE"); - return FAILURE; - } - } - SUCCESS -} - #[cfg(test)] mod test { use super::*; diff --git a/src/parser/grammar.rustpeg b/src/parser/grammar.rustpeg index 89f2a0cc..b80da8f1 100644 --- a/src/parser/grammar.rustpeg +++ b/src/parser/grammar.rustpeg @@ -6,6 +6,7 @@ use shell::flow_control::{ElseIf, Statement}; #[pub] parse_ -> Statement = let_ + / export_ / if_ / else_if_ / else_ @@ -23,6 +24,12 @@ let_ -> Statement Statement::Let { expression: parse_assignment(value) } } +#[pub] +export_ -> Statement + = whitespace* "export" whitespace? value:$(.*) { + Statement::Export(parse_assignment(value)) + } + #[pub] break_ -> Statement = whitespace* "break" { Statement::Break } diff --git a/src/shell/assignments.rs b/src/shell/assignments.rs index 038a55a6..c3ce46e3 100644 --- a/src/shell/assignments.rs +++ b/src/shell/assignments.rs @@ -1,4 +1,5 @@ use std::io::{self, Write}; +use std::env; use super::variables::Variables; use super::directory_stack::DirectoryStack; @@ -23,7 +24,7 @@ use super::status::*; enum Action { UpdateString(Identifier, VString), UpdateArray(Identifier, VArray), - NoOp + List } fn print_vars(list: &VariableContext) { @@ -61,111 +62,91 @@ fn print_arrays(list: &ArrayVariableContext) { } } -pub fn let_assignment(binding: Binding, vars: &mut Variables, dir_stack: &DirectoryStack) -> i32 { - let action = { - let expanders = ExpanderFunctions { - tilde: &|tilde: &str| vars.tilde_expansion(tilde, dir_stack), - array: &|array: &str, selection: Select| { - match vars.get_array(array) { - Some(array) => match selection { - Select::None => None, - Select::All => Some(array.clone()), - Select::Index(id) => { - id.resolve(array.len()) - .and_then(|n| array.get(n)) - .map(|x| Some(x.to_owned()).into_iter().collect()) - }, - Select::Range(range) => { - if let Some((start, length)) = range.bounds(array.len()) { - let array: VArray = array.iter() - .skip(start) - .take(length) - .map(|x| x.to_owned()) - .collect(); - if array.is_empty() { - None - } else { - Some(array) - } - } else { - None - } - } - }, - None => None - } - }, - variable: &|variable: &str, quoted: bool| { - use ascii_helpers::AsciiReplace; - - if quoted { - vars.get_var(variable) - } else { - vars.get_var(variable).map(|x| x.ascii_replace('\n', ' ').into()) - } - }, - command: &|command: &str, quoted: bool| vars.command_expansion(command, quoted), - }; - - - - match binding { - Binding::InvalidKey(key) => { - let stderr = io::stderr(); - let _ = writeln!(&mut stderr.lock(), "ion: variable name, '{}', is invalid", key); - return FAILURE; - }, - Binding::KeyValue(key, value) => match parse_expression(&value, &expanders) { - Value::String(value) => Action::UpdateString(key, value), - Value::Array(array) => Action::UpdateArray(key, array) - }, - Binding::KeyOnly(key) => { - let stderr = io::stderr(); - let _ = writeln!(&mut stderr.lock(), "ion: please provide value for variable '{}'", key); - return FAILURE; - }, - Binding::ListEntries => { - print_vars(&vars.variables); - print_arrays(&vars.arrays); - Action::NoOp - }, - Binding::Math(key, operator, value) => { - match parse_expression(&value, &expanders) { - Value::String(ref value) => { - let left = match vars.get_var(&key).and_then(|x| x.parse::<f32>().ok()) { - Some(left) => left, - None => return FAILURE, - }; - - let right = match value.parse::<f32>().ok() { - Some(right) => right, - None => return FAILURE - }; - - let result = match operator { - Operator::Add => left + right, - Operator::Subtract => left - right, - Operator::Divide => left / right, - Operator::Multiply => left * right, - Operator::Exponent => f32::powf(left, right) - }; - - Action::UpdateString(key, result.to_string()) - }, - Value::Array(_) => { - let stderr = io::stderr(); - let _ = writeln!(stderr.lock(), "ion: array math not supported yet"); - return FAILURE - } +fn parse_assignment(binding: Binding, + vars: &mut Variables, + dir_stack: &DirectoryStack) -> Result<Action, i32> +{ + let expanders = get_expanders!(vars, dir_stack); + match binding { + Binding::InvalidKey(key) => { + let stderr = io::stderr(); + let _ = writeln!(&mut stderr.lock(), "ion: variable name, '{}', is invalid", key); + Err(FAILURE) + }, + Binding::KeyValue(key, value) => match parse_expression(&value, &expanders) { + Value::String(value) => Ok(Action::UpdateString(key, value)), + Value::Array(array) => Ok(Action::UpdateArray(key, array)) + }, + Binding::KeyOnly(key) => { + let stderr = io::stderr(); + let _ = writeln!(&mut stderr.lock(), "ion: please provide value for variable '{}'", key); + Err(FAILURE) + }, + Binding::ListEntries => Ok(Action::List), + Binding::Math(key, operator, value) => { + match parse_expression(&value, &expanders) { + Value::String(ref value) => { + let left = match vars.get_var(&key).and_then(|x| x.parse::<f32>().ok()) { + Some(left) => left, + None => return Err(FAILURE), + }; + + let right = match value.parse::<f32>().ok() { + Some(right) => right, + None => return Err(FAILURE) + }; + + let result = match operator { + Operator::Add => left + right, + Operator::Subtract => left - right, + Operator::Divide => left / right, + Operator::Multiply => left * right, + Operator::Exponent => f32::powf(left, right) + }; + + Ok(Action::UpdateString(key, result.to_string())) + }, + Value::Array(_) => { + let stderr = io::stderr(); + let _ = writeln!(stderr.lock(), "ion: array math not supported yet"); + Err(FAILURE) } - }, + } + }, + } +} + +pub fn let_assignment(binding: Binding, vars: &mut Variables, dir_stack: &DirectoryStack) -> i32 { + match parse_assignment(binding, vars, dir_stack) { + Ok(Action::UpdateArray(key, array)) => vars.set_array(&key, array), + Ok(Action::UpdateString(key, string)) => vars.set_var(&key, &string), + Ok(Action::List) => { + print_vars(&vars.variables); + print_arrays(&vars.arrays); } + Err(code) => return code, }; - match action { - Action::UpdateArray(key, array) => vars.set_array(&key, array), - Action::UpdateString(key, string) => vars.set_var(&key, &string), - Action::NoOp => () + SUCCESS +} + +/// Exporting a variable sets that variable as a global variable in the system. +/// Global variables can be accessed by other programs running on the system. +pub fn export_variable(binding : Binding, vars: &mut Variables, dir_stack : &DirectoryStack) -> i32 { + match parse_assignment(binding, vars, dir_stack) { + Ok(Action::UpdateArray(key, array)) => env::set_var(&key, array.join(" ")), + Ok(Action::UpdateString(key, string)) => env::set_var(&key, string), + Ok(Action::List) => { + let stdout = io::stdout(); + let stdout = &mut stdout.lock(); + for (key, value) in env::vars() { + let _ = stdout.write(key.as_bytes()) + .and_then(|_| stdout.write_all(b"=")) + .and_then(|_| stdout.write_all(value.as_bytes())) + .and_then(|_| stdout.write_all(b"\n")); + } + } + Err(code) => return code }; SUCCESS @@ -189,8 +170,8 @@ fn parse_expression(expression: &str, shell_funcs: &ExpanderFunctions) -> Value // If multiple arguments have been passed, they will be collapsed into a single string. // IE: `[ one two three ] four` is equivalent to `one two three four` let arguments: Vec<String> = arguments.iter() - .flat_map(|expression| expand_string(expression, shell_funcs, false)) - .collect(); + .flat_map(|expression| expand_string(expression, shell_funcs, false)) + .collect(); Value::String(arguments.join(" ")) } diff --git a/src/shell/flow.rs b/src/shell/flow.rs index aca918ba..effe96af 100644 --- a/src/shell/flow.rs +++ b/src/shell/flow.rs @@ -7,7 +7,7 @@ use super::flags::*; use super::flow_control::{ElseIf, Function, Statement, collect_loops, collect_if}; use parser::{ForExpression, StatementSplitter, check_statement}; use parser::peg::Pipeline; -use super::assignments::let_assignment; +use super::assignments::{let_assignment, export_variable}; //use glob::glob; @@ -105,6 +105,9 @@ impl<'a> FlowLogic for Shell<'a> { Statement::Let { expression } => { self.previous_status = let_assignment(expression, &mut self.variables, &self.directory_stack); }, + Statement::Export(expression) => { + self.previous_status = export_variable(expression, &mut self.variables, &self.directory_stack); + } Statement::While { expression, statements } => { self.execute_while(expression, statements); }, @@ -148,6 +151,9 @@ impl<'a> FlowLogic for Shell<'a> { Statement::Let { expression } => { self.previous_status = let_assignment(expression, &mut self.variables, &self.directory_stack); }, + Statement::Export(expression) => { + self.previous_status = export_variable(expression, &mut self.variables, &self.directory_stack); + } Statement::While { expression, mut statements } => { self.flow_control.level += 1; collect_loops(&mut iterator, &mut statements, &mut self.flow_control.level); @@ -288,6 +294,9 @@ impl<'a> FlowLogic for Shell<'a> { Statement::Let { expression } => { self.previous_status = let_assignment(expression, &mut self.variables, &self.directory_stack); }, + Statement::Export(expression) => { + self.previous_status = export_variable(expression, &mut self.variables, &self.directory_stack); + } // 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 } => { diff --git a/src/shell/flow_control.rs b/src/shell/flow_control.rs index d6198513..76b99f4e 100644 --- a/src/shell/flow_control.rs +++ b/src/shell/flow_control.rs @@ -20,6 +20,7 @@ pub enum Statement { Let { expression: Binding, }, + Export(Binding), If { expression: Pipeline, success: Vec<Statement>, -- GitLab