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