Skip to content

Commit

Permalink
Add exit function.
Browse files Browse the repository at this point in the history
  • Loading branch information
schungx committed Sep 1, 2023
1 parent fe240ac commit f86609e
Show file tree
Hide file tree
Showing 12 changed files with 66 additions and 23 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Dependencies
New features
------------

* New `exit` function that terminates script evaluation regardless of where it is called, even inside deeply-nested function calls.
* Added `Engine::max_variables` and `Engine::set_max_variables` to limit the maximum number of variables allowed within a scope at any time. This is to guard against defining a huge number of variables containing large data just beyond individual data size limits. When `max_variables` is exceeded a new error, `ErrorTooManyVariables`, is returned.
* Added `zip` function for arrays.
* Doc-comments are now included in custom type definitions within plugin modules. They can be accessed via `Module::get_custom_type_comments`. These doc-comments for custom types are also exported in JSON via `Engine::gen_fn_metadata_to_json`.
Expand Down
13 changes: 9 additions & 4 deletions src/api/call_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,17 +226,18 @@ impl Engine {

let rewind_scope = options.rewind_scope;

let result = if options.eval_ast && !statements.is_empty() {
let global_result = if options.eval_ast && !statements.is_empty() {
defer! {
scope if rewind_scope => rewind;
let orig_scope_len = scope.len();
}

self.eval_global_statements(global, caches, scope, statements)
self.eval_global_statements(global, caches, scope, statements, true)
} else {
Ok(Dynamic::UNIT)
}
.and_then(|_| {
};

let result = global_result.and_then(|_| {
let args = &mut arg_values.iter_mut().collect::<StaticVec<_>>();

// Check for data race.
Expand All @@ -261,6 +262,10 @@ impl Engine {
)
},
)
.or_else(|err| match *err {
ERR::Exit(out, ..) => Ok(out),
_ => Err(err),
})
});

#[cfg(feature = "debugging")]
Expand Down
2 changes: 1 addition & 1 deletion src/api/deprecated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ impl Engine {
scope: &mut Scope,
statements: &[crate::ast::Stmt],
) -> RhaiResult {
self.eval_global_statements(global, caches, scope, statements)
self.eval_global_statements(global, caches, scope, statements, true)
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/api/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ impl Engine {
g.source = orig_source;
}}

let r = self.eval_global_statements(global, caches, scope, ast.statements())?;
let r = self.eval_global_statements(global, caches, scope, ast.statements(), true)?;

#[cfg(feature = "debugging")]
if self.is_debugger_registered() {
Expand Down
2 changes: 1 addition & 1 deletion src/api/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ impl Engine {
global.embedded_module_resolver = ast.resolver().cloned();
}

let _ = self.eval_global_statements(global, caches, scope, ast.statements())?;
let _ = self.eval_global_statements(global, caches, scope, ast.statements(), true)?;

#[cfg(feature = "debugging")]
if self.is_debugger_registered() {
Expand Down
2 changes: 2 additions & 0 deletions src/eval/stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1011,10 +1011,12 @@ impl Engine {
caches: &mut Caches,
scope: &mut Scope,
statements: &[Stmt],
map_exit_to_return_value: bool,
) -> RhaiResult {
self.eval_stmt_block(global, caches, scope, None, statements, false)
.or_else(|err| match *err {
ERR::Return(out, ..) => Ok(out),
ERR::Exit(out, ..) if map_exit_to_return_value => Ok(out),
ERR::LoopBreak(..) => {
unreachable!("no outer loop scope to break out of")
}
Expand Down
9 changes: 5 additions & 4 deletions src/func/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1252,14 +1252,15 @@ impl Engine {
}
global.level = orig_level;

return result.map_err(|err| {
ERR::ErrorInFunctionCall(
return result.map_err(|err| match *err {
ERR::Exit(..) => err,
_ => ERR::ErrorInFunctionCall(
KEYWORD_EVAL.to_string(),
global.source().unwrap_or("").to_string(),
err,
pos,
)
.into()
.into(),
});
}

Expand Down Expand Up @@ -1612,7 +1613,7 @@ impl Engine {
}

// Evaluate the AST
self.eval_global_statements(global, caches, scope, statements)
self.eval_global_statements(global, caches, scope, statements, false)
}

/// # Main Entry-Point (`FnCallExpr`)
Expand Down
5 changes: 5 additions & 0 deletions src/func/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ impl Engine {
.or_else(|err| match *err {
// Convert return statement to return value
ERR::Return(x, ..) => Ok(x),
// Exit value is passed straight-through
mut err @ ERR::Exit(..) => {
err.set_position(pos);
Err(err.into())
}
// System errors are passed straight-through
mut err if err.is_system_exception() => {
err.set_position(pos);
Expand Down
24 changes: 22 additions & 2 deletions src/packages/lang_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::def_package;
use crate::module::ModuleFlags;
use crate::plugin::*;
use crate::types::dynamic::Tag;
use crate::{Dynamic, RhaiResultOf, ERR, INT};
use crate::{Dynamic, RhaiResult, RhaiResultOf, ERR, INT};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;

Expand All @@ -26,6 +26,26 @@ def_package! {

#[export_module]
mod core_functions {
/// Exit the script evaluation immediately with a value.
///
/// # Example
/// ```rhai
/// exit(42);
/// ```
#[rhai_fn(name = "exit", return_raw)]
pub fn exit_with_value(value: Dynamic) -> RhaiResult {
Err(ERR::Exit(value, Position::NONE).into())
}
/// Exit the script evaluation immediately with `()` as exit value.
///
/// # Example
/// ```rhai
/// exit();
/// ```
#[rhai_fn(return_raw)]
pub fn exit() -> RhaiResult {
Err(ERR::Exit(Dynamic::UNIT, Position::NONE).into())
}
/// Take ownership of the data in a `Dynamic` value and return it.
/// The data is _NOT_ cloned.
///
Expand All @@ -41,7 +61,7 @@ mod core_functions {
/// print(x); // prints ()
/// ```
#[rhai_fn(return_raw)]
pub fn take(value: &mut Dynamic) -> RhaiResultOf<Dynamic> {
pub fn take(value: &mut Dynamic) -> RhaiResult {
if value.is_read_only() {
return Err(
ERR::ErrorNonPureMethodCallOnConstant("take".to_string(), Position::NONE).into(),
Expand Down
6 changes: 3 additions & 3 deletions src/tokenizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ static RESERVED_LIST: [(&str, bool, bool, bool); 150] = [
("import", cfg!(feature = "no_module"), false, false),
("--", true, false, false),
("nil", true, false, false),
("exit", true, false, false),
("exit", false, false, false),
("", false, false, false),
("export", cfg!(feature = "no_module"), false, false),
("<|", true, false, false),
Expand Down Expand Up @@ -1458,7 +1458,7 @@ fn scan_block_comment(

match c {
'/' => {
if let Some(c2) = stream.peek_next().filter(|&c2| c2 == '*') {
if let Some(c2) = stream.peek_next().filter(|&ch| ch == '*') {
eat_next_and_advance(stream, pos);
if let Some(comment) = comment.as_mut() {
comment.push(c2);
Expand All @@ -1467,7 +1467,7 @@ fn scan_block_comment(
}
}
'*' => {
if let Some(c2) = stream.peek_next().filter(|&c2| c2 == '/') {
if let Some(c2) = stream.peek_next().filter(|&ch| ch == '/') {
eat_next_and_advance(stream, pos);
if let Some(comment) = comment.as_mut() {
comment.push(c2);
Expand Down
21 changes: 15 additions & 6 deletions src/types/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ pub enum EvalAltResult {
/// Not an error: Value returned from a script via the `return` keyword.
/// Wrapped value is the result value.
Return(Dynamic, Position),
/// Not an error: Value returned from a script via the `exit` function.
/// Wrapped value is the exit value.
Exit(Dynamic, Position),
}

impl Error for EvalAltResult {}
Expand Down Expand Up @@ -227,6 +230,7 @@ impl fmt::Display for EvalAltResult {
Self::LoopBreak(false, ..) => f.write_str("'continue' must be within a loop")?,

Self::Return(..) => f.write_str("NOT AN ERROR - function returns value")?,
Self::Exit(..) => f.write_str("NOT AN ERROR - exit value")?,

Self::ErrorArrayBounds(max, index, ..) => match max {
0 => write!(f, "Array index {index} out of bounds: array is empty"),
Expand Down Expand Up @@ -287,12 +291,15 @@ impl<T: AsRef<str>> From<T> for Box<EvalAltResult> {
impl EvalAltResult {
/// Is this a pseudo error? A pseudo error is one that does not occur naturally.
///
/// [`LoopBreak`][EvalAltResult::LoopBreak] and [`Return`][EvalAltResult::Return] are pseudo errors.
/// [`LoopBreak`][EvalAltResult::LoopBreak], [`Return`][EvalAltResult::Return] and [`Exit`][EvalAltResult::Exit] are pseudo errors.
#[cold]
#[inline(never)]
#[must_use]
pub const fn is_pseudo_error(&self) -> bool {
matches!(self, Self::LoopBreak(..) | Self::Return(..))
matches!(
self,
Self::LoopBreak(..) | Self::Return(..) | Self::Exit(..)
)
}
/// Can this error be caught?
#[cold]
Expand Down Expand Up @@ -340,7 +347,7 @@ impl EvalAltResult {
| Self::ErrorDataTooLarge(..)
| Self::ErrorTerminated(..) => false,

Self::LoopBreak(..) | Self::Return(..) => false,
Self::LoopBreak(..) | Self::Return(..) | Self::Exit(..) => false,
}
}
/// Is this error a system exception?
Expand Down Expand Up @@ -376,7 +383,7 @@ impl EvalAltResult {
);

match self {
Self::LoopBreak(..) | Self::Return(..) => (),
Self::LoopBreak(..) | Self::Return(..) | Self::Exit(..) => (),

Self::ErrorSystem(..)
| Self::ErrorParsing(..)
Expand Down Expand Up @@ -497,7 +504,8 @@ impl EvalAltResult {
| Self::ErrorCustomSyntax(.., pos)
| Self::ErrorRuntime(.., pos)
| Self::LoopBreak(.., pos)
| Self::Return(.., pos) => *pos,
| Self::Return(.., pos)
| Self::Exit(.., pos) => *pos,
}
}
/// Remove the [position][Position] information from this error.
Expand Down Expand Up @@ -558,7 +566,8 @@ impl EvalAltResult {
| Self::ErrorCustomSyntax(.., pos)
| Self::ErrorRuntime(.., pos)
| Self::LoopBreak(.., pos)
| Self::Return(.., pos) => *pos = new_position,
| Self::Return(.., pos)
| Self::Exit(.., pos) => *pos = new_position,
}
self
}
Expand Down
2 changes: 1 addition & 1 deletion tools/reserved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ shared, true, false, false
with, true, false, false
is, true, false, false
goto, true, false, false
exit, true, false, false
exit, false, false, false
match, true, false, false
case, true, false, false
default, true, false, false
Expand Down

0 comments on commit f86609e

Please sign in to comment.