diff --git a/src/arguments.rs b/src/arguments.rs index 671793f..0e645d5 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -71,8 +71,9 @@ impl Arguments { let filename = filename.to_string_lossy().to_string(); - let mut evaluator = - Evaluator::from(Environment::new(Into::::into(self))); + let mut evaluator = Evaluator::from(Environment::new_with_process_control( + Into::::into(self), + )); match parse(&content) { Ok(ast) => match evaluator.evaluate(&ast) { @@ -98,8 +99,9 @@ impl Arguments { } fn evaluate_expression(&self, value: String) -> Result { - let mut evaluator = - Evaluator::from(Environment::new(Into::::into(self))); + let mut evaluator = Evaluator::from(Environment::new_with_process_control( + Into::::into(self), + )); match parse(&value) { Ok(ast) => match evaluator.evaluate(&ast) { @@ -150,8 +152,9 @@ impl Arguments { editor.set_helper(Some(Prompt::new())); editor.load_history(&history).ok(); - let mut evaluator = - Evaluator::from(Environment::new(Into::::into(self))); + let mut evaluator = Evaluator::from(Environment::new_with_process_control( + Into::::into(self), + )); if let Some(filenames) = &self.load { for filename in filenames { diff --git a/src/builtin.rs b/src/builtin.rs index b6598e2..4355ae9 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -14,6 +14,10 @@ pub enum Builtin { } impl Builtin { + pub(crate) fn controls_process(&self) -> bool { + matches!(self.name(), "exit" | "quit") + } + #[must_use] pub fn kind(&self) -> &'static str { match self { diff --git a/src/environment.rs b/src/environment.rs index 247b059..958e747 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -53,6 +53,38 @@ impl<'src> Environment<'src> { } } + fn from_builtins( + config: Config, + builtins: impl IntoIterator, + ) -> Self { + let environment = Self { + config, + frame: Rc::new(RefCell::new(Frame::default())), + }; + + for builtin in builtins { + match builtin { + Builtin::Constant { value, .. } => { + environment.add_symbol(builtin.name(), Value::Number(value(config))); + } + Builtin::Function { + arity, function, .. + } => { + environment.add_function( + builtin.name(), + Function::Builtin { + arity: *arity, + function: *function, + name: builtin.name(), + }, + ); + } + } + } + + environment + } + pub(crate) fn function( &self, name: &str, @@ -94,32 +126,17 @@ impl<'src> Environment<'src> { #[must_use] pub fn new(config: Config) -> Self { - let environment = Self { + Self::from_builtins( config, - frame: Rc::new(RefCell::new(Frame::default())), - }; - - for builtin in BUILTINS { - match builtin { - Builtin::Constant { value, .. } => { - environment.add_symbol(builtin.name(), Value::Number(value(config))); - } - Builtin::Function { - arity, function, .. - } => { - environment.add_function( - builtin.name(), - Function::Builtin { - arity: *arity, - function: *function, - name: builtin.name(), - }, - ); - } - } - } + BUILTINS + .iter() + .filter(|builtin| !builtin.controls_process()), + ) + } - environment + #[must_use] + pub fn new_with_process_control(config: Config) -> Self { + Self::from_builtins(config, BUILTINS) } fn resolve_function(&self, name: &str) -> Option> { @@ -152,3 +169,20 @@ impl fmt::Debug for Environment<'_> { .finish_non_exhaustive() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn process_control_is_opt_in() { + let safe = Environment::new(Config::default()); + let process_control = + Environment::new_with_process_control(Config::default()); + + for name in ["exit", "quit"] { + assert!(safe.resolve_function(name).is_none()); + assert!(process_control.resolve_function(name).is_some()); + } + } +}