diff --git a/examples/fn-root-vars.ion b/examples/fn-root-vars.ion new file mode 100644 index 0000000000000000000000000000000000000000..ee7dc005bb9538db58f0c4a78e6e2eb9a7d50e31 --- /dev/null +++ b/examples/fn-root-vars.ion @@ -0,0 +1,9 @@ +fn env + echo $HOME + echo $PROMPT + echo $UID + echo $CDPATH +end + +env + diff --git a/examples/fn-root-vars.out b/examples/fn-root-vars.out new file mode 100644 index 0000000000000000000000000000000000000000..bae0b7bbc27cb2df6fdab3c851feef354b35e99f --- /dev/null +++ b/examples/fn-root-vars.out @@ -0,0 +1,4 @@ +/home/adminxvii +${x::1B}]0;${USER}: ${PWD}${x::07}${c::0x55,bold}${USER}${c::default}:${c::0x4B}${SWD}${c::default}# ${c::reset} +1000 + diff --git a/examples/glob.out b/examples/glob.out index 5f5f7ec03fb2ddc7d404c2397a5e59649e900cc7..209f4f579dae55ce46f9d8147bdee1ce073547d7 100644 --- a/examples/glob.out +++ b/examples/glob.out @@ -5,7 +5,7 @@ Cargo.toml Cargo.lock Cargo.toml Cargo.toml Cargo.toml -examples/else_if.ion examples/empty_loop_test.ion examples/exists.ion examples/fail.ion examples/fibonacci.ion examples/fn.ion examples/for.ion examples/function_piping.ion +examples/else_if.ion examples/empty_loop_test.ion examples/exists.ion examples/fail.ion examples/fibonacci.ion examples/fn-root-vars.ion examples/fn.ion examples/for.ion examples/function_piping.ion [] [] [] one three two diff --git a/examples/run_examples.sh b/examples/run_examples.sh index 627cb60c7ceed2f6196fb51cfc7c6f0f356b7f02..c7ca529393082af7a11cd45a0f7f383d23aa43a7 100755 --- a/examples/run_examples.sh +++ b/examples/run_examples.sh @@ -21,6 +21,12 @@ EXIT_VAL=0 # and it never hurts to force consistency regardless cd $PROJECT_DIR +# Create expected output for fn-root-vars +echo $HOME > examples/fn-root-vars.out # Overwrite previous file +echo '${x::1B}]0;${USER}: ${PWD}${x::07}${c::0x55,bold}${USER}${c::default}:${c::0x4B}${SWD}${c::default}# ${c::reset}' >> examples/fn-root-vars.out +echo $UID >> examples/fn-root-vars.out +echo >> examples/fn-root-vars.out + function test { # Replace .ion with .out in file name EXPECTED_OUTPUT_FILE=$(echo $1 | sed 's/\..\+/\.out/') diff --git a/examples/scopes.ion b/examples/scopes.ion index dcb6a4eabd072f13b258873e301a69eee486c784..cfc53af6c00632de6cb82e4257e5a9f731b557da 100644 --- a/examples/scopes.ion +++ b/examples/scopes.ion @@ -26,3 +26,16 @@ end echo $x echo $y +fn demo + echo ${super::foo} + drop foo + + fn bar + super::demo + end + bar +end + +let foo = bar +echo $foo +demo diff --git a/examples/scopes.out b/examples/scopes.out index 15020d142b9d1f487c1adbd7382b5d219879b334..cc6de1ce253ed648155b426c60212953817662d9 100644 --- a/examples/scopes.out +++ b/examples/scopes.out @@ -4,3 +4,7 @@ +bar +bar +ion: undefined variable: foo +ion: command not found: super::demo diff --git a/src/lib/shell/binary/prompt.rs b/src/lib/shell/binary/prompt.rs index ea05dcf75322a0bd5fe205c8d37b2d7040c4bc92..0ce6a6a3a366e7ad1d8bb13241e156a7c415dc3c 100644 --- a/src/lib/shell/binary/prompt.rs +++ b/src/lib/shell/binary/prompt.rs @@ -1,6 +1,6 @@ use crate::{ parser::shell_expand::expand_string, - shell::{flags::UNTERMINATED, Capture, Function, Shell}, + shell::{flags::UNTERMINATED, variables::Value, Capture, Shell}, sys, }; use std::{io::Read, process}; @@ -18,29 +18,30 @@ pub(crate) fn prompt(shell: &mut Shell) -> String { } pub(crate) fn prompt_fn(shell: &mut Shell) -> Option<String> { - let function = shell.variables.get::<Function>("PROMPT")?; - let function = &function as *const Function; - - let mut output = None; - - match shell.fork(Capture::StdoutThenIgnoreStderr, |child| { - let _ = unsafe { function.read() }.execute(child, &["ion"]); - }) { - Ok(result) => { - let mut string = String::with_capacity(1024); - match result.stdout.unwrap().read_to_string(&mut string) { - Ok(_) => output = Some(string), - Err(why) => { - eprintln!("ion: error reading stdout of child: {}", why); + if let Some(Value::Function(function)) = shell.variables.get_ref("PROMPT") { + let output = match shell.fork(Capture::StdoutThenIgnoreStderr, |child| { + let _ = function.execute(child, &["ion"]); + }) { + Ok(result) => { + let mut string = String::with_capacity(1024); + match result.stdout?.read_to_string(&mut string) { + Ok(_) => Some(string), + Err(why) => { + eprintln!("ion: error reading stdout of child: {}", why); + None + } } } - } - Err(why) => { - eprintln!("ion: fork error: {}", why); - } - } + Err(why) => { + eprintln!("ion: fork error: {}", why); + None + } + }; - // Ensure that the parent retains ownership of the terminal before exiting. - let _ = sys::tcsetpgrp(sys::STDIN_FILENO, process::id()); - output + // Ensure that the parent retains ownership of the terminal before exiting. + let _ = sys::tcsetpgrp(sys::STDIN_FILENO, process::id()); + output + } else { + None + } } diff --git a/src/lib/shell/flow.rs b/src/lib/shell/flow.rs index 962944302b6e2dbf3d3d6e12b6c1c517f4d0f508..eaed0f3b19fcb64a46cd91ceb41d7a3c36cf9200 100644 --- a/src/lib/shell/flow.rs +++ b/src/lib/shell/flow.rs @@ -399,7 +399,7 @@ fn expand_pipeline( shell: &Shell, pipeline: &Pipeline, ) -> Result<(Pipeline, Vec<Statement>), String> { - let mut item_iter = pipeline.items.iter().cloned(); + let mut item_iter = pipeline.items.iter(); let mut items: Vec<PipeItem> = Vec::with_capacity(item_iter.size_hint().0); let mut statements = Vec::new(); @@ -443,7 +443,7 @@ fn expand_pipeline( } // Append rest of the pipeline to the last pipeline in the // alias. - pline.items.extend(item_iter); + pline.items.extend(item_iter.cloned()); } else { // Error in expansion return Err(format!( diff --git a/src/lib/shell/flow_control.rs b/src/lib/shell/flow_control.rs index a1dc7d771e946966ec0256689a59d3c416dbff4d..bd744e97fdd652ffd8658ef9a8308a78c4948e7b 100644 --- a/src/lib/shell/flow_control.rs +++ b/src/lib/shell/flow_control.rs @@ -378,7 +378,7 @@ impl Function { pub fn is_empty(&self) -> bool { self.statements.is_empty() } pub(crate) fn execute<S: AsRef<str>>( - self, + &self, shell: &mut Shell, args: &[S], ) -> Result<(), FunctionError> { diff --git a/src/lib/shell/fork_function.rs b/src/lib/shell/fork_function.rs index cac2488b75e52ae8debcac2a87cf47b40544eee9..62066ba9d2c1fd0e2367508cffb3fea8e0be96bd 100644 --- a/src/lib/shell/fork_function.rs +++ b/src/lib/shell/fork_function.rs @@ -1,5 +1,5 @@ use crate::{ - shell::{Capture, Function, Shell}, + shell::{variables::Value, Capture, Shell}, sys, }; use std::process; @@ -11,23 +11,20 @@ pub(crate) fn command_not_found(shell: &mut Shell, command: &str) -> bool { /// High-level function for executing a function programmatically. /// NOTE: Always add "ion" as a first argument in `args`. pub fn fork_function<S: AsRef<str>>(shell: &mut Shell, fn_name: &str, args: &[S]) -> bool { - let function: Function = match shell.variables.get::<Function>(fn_name) { - Some(func) => func, - None => return false, - }; - let function = &function as *const Function; - - if let Err(err) = shell.fork(Capture::None, |child| { - let result = unsafe { function.read() }.execute(child, args); - if let Err(err) = result { - eprintln!("ion: {} function call: {}", fn_name, err); + if let Some(Value::Function(function)) = shell.variables.get_ref(fn_name) { + if let Err(err) = shell.fork(Capture::None, |child| { + if let Err(err) = function.execute(child, args) { + eprintln!("ion: {} function call: {}", fn_name, err); + } + }) { + eprintln!("ion: fork error: {}", err); + false + } else { + // Ensure that the parent retains ownership of the terminal before exiting. + let _ = sys::tcsetpgrp(sys::STDIN_FILENO, process::id()); + true } - }) { - eprintln!("ion: fork error: {}", err); - return false; + } else { + false } - - // Ensure that the parent retains ownership of the terminal before exiting. - let _ = sys::tcsetpgrp(sys::STDIN_FILENO, process::id()); - true } diff --git a/src/lib/shell/variables/mod.rs b/src/lib/shell/variables/mod.rs index b4714cde85cc85760cf1047193cde684fb59c86e..0809082f52ee183b7be5dac3ef411a5bfce6d15a 100644 --- a/src/lib/shell/variables/mod.rs +++ b/src/lib/shell/variables/mod.rs @@ -273,7 +273,7 @@ impl Variables { self.scopes.extend(scopes); } - pub fn scopes(&self) -> impl Iterator<Item = &Scope> { + pub fn scopes(&self) -> impl DoubleEndedIterator<Item = &Scope> { let amount = self.scopes.len() - self.current - 1; self.scopes.iter().rev().skip(amount) } @@ -301,33 +301,38 @@ impl Variables { const GLOBAL_NS: &str = "global::"; const SUPER_NS: &str = "super::"; - let mut up_namespace: isize = if name.starts_with(GLOBAL_NS) { + if name.starts_with(GLOBAL_NS) { name = &name[GLOBAL_NS.len()..]; // Go up as many namespaces as possible - self.scopes().filter(|scope| scope.namespace).count() as isize - } else { + self.scopes() + .rev() + .take_while(|scope| !scope.namespace) + .filter_map(|scope| scope.get(name)) + .last() + } else if name.starts_with(SUPER_NS) { let mut up = 0; while name.starts_with(SUPER_NS) { name = &name[SUPER_NS.len()..]; up += 1; } - up - }; - - for scope in self.scopes() { - match scope.get(name) { - val @ Some(Value::Function(_)) => return val, - val @ Some(_) if up_namespace == 0 => return val, - _ => (), + for scope in self.scopes() { + if up == 0 { + let val = scope.get(name); + if val.is_some() { + return val; + } else if scope.namespace { + break; + } + } else if scope.namespace { + up -= 1; + } } - if scope.namespace { - up_namespace -= 1; - } + None + } else { + self.scopes().filter_map(|scope| scope.get(name)).next() } - - None } pub fn get_mut(&mut self, name: &str) -> Option<&mut Value> { @@ -348,10 +353,18 @@ impl Variables { } pub fn remove_variable(&mut self, name: &str) -> Option<Value> { + if name.starts_with("super::") || name.starts_with("global::") { + // Cannot mutate outer namespace + return None; + } for scope in self.scopes_mut() { + let exit = scope.namespace; if let val @ Some(_) = scope.remove(name) { return val; } + if exit { + break; + } } None }