From 5ee9dfc5cda7094ae2f0dee31970e18d931670a3 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 20 Oct 2020 18:09:26 +0800 Subject: [PATCH] 1) Change namespaces to iter_namespaces 2) throw can throw any value --- RELEASES.md | 1 + doc/src/engine/custom-syntax.md | 2 +- doc/src/engine/var.md | 2 +- doc/src/language/fn-ptr.md | 18 +++++++----- doc/src/language/throw.md | 12 ++++---- doc/src/rust/register-raw.md | 5 ++-- examples/repl.rs | 9 +----- examples/rhai_runner.rs | 9 +----- src/engine.rs | 20 +++++-------- src/fn_call.rs | 3 +- src/fn_native.rs | 8 ++--- src/parser.rs | 13 ++++----- src/result.rs | 52 +++++++++++++++++++++++++++++---- src/syntax.rs | 6 ++-- tests/throw.rs | 6 ++-- 15 files changed, 97 insertions(+), 69 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index db52a753..bd4bb02d 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -17,6 +17,7 @@ New features ------------ * The plugins system is enhanced to support functions taking a `NativeCallContext` as the first parameter. +* `throw` statement can throw any value instead of just text strings. Enhancements ------------ diff --git a/doc/src/engine/custom-syntax.md b/doc/src/engine/custom-syntax.md index e635fcdc..84463935 100644 --- a/doc/src/engine/custom-syntax.md +++ b/doc/src/engine/custom-syntax.md @@ -121,7 +121,7 @@ where: * `context: &mut EvalContext` - mutable reference to the current evaluation _context_, exposing the following: * `context.scope: &mut Scope` - mutable reference to the current [`Scope`]; variables can be added to/removed from it. * `context.engine(): &Engine` - reference to the current [`Engine`]. - * `context.namespaces(): &[&Module]` - reference to the chain of namespaces (as a slice of [modules]) containing all script-defined functions. + * `context.iter_namespaces(): impl Iterator` - iterator of the namespaces (as [modules]) containing all script-defined functions. * `context.this_ptr(): Option<&Dynamic>` - reference to the current bound [`this`] pointer, if any. * `context.call_level(): usize` - the current nesting level of function calls. diff --git a/doc/src/engine/var.md b/doc/src/engine/var.md index 46468ae5..270f122e 100644 --- a/doc/src/engine/var.md +++ b/doc/src/engine/var.md @@ -77,7 +77,7 @@ where: * `context: &EvalContext` - reference to the current evaluation _context_, which exposes the following fields: * `context.scope: &Scope` - reference to the current [`Scope`] containing all variables up to the current evaluation position. * `context.engine(): &Engine` - reference to the current [`Engine`]. - * `context.namespaces(): &[&Module]` - reference to the chain of namespaces (as a slice of [modules]) containing all script-defined functions. + * `context.iter_namespaces(): impl Iterator` - iterator of the namespaces (as [modules]) containing all script-defined functions. * `context.this_ptr(): Option<&Dynamic>` - reference to the current bound [`this`] pointer, if any. * `context.call_level(): usize` - the current nesting level of function calls. diff --git a/doc/src/language/fn-ptr.md b/doc/src/language/fn-ptr.md index bb7543a3..71d384c4 100644 --- a/doc/src/language/fn-ptr.md +++ b/doc/src/language/fn-ptr.md @@ -199,9 +199,9 @@ fn call_fn_ptr_with_value(context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { // 'args' is guaranteed to contain enough arguments of the correct types - let fp = std::mem::take(args[1]).cast::(); // 2nd argument - function pointer - let value = args[2].clone(); // 3rd argument - function argument - let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer + let fp = std::mem::take(args[1]).cast::(); // 2nd argument - function pointer + let value = args[2].clone(); // 3rd argument - function argument + let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer // Use 'FnPtr::call_dynamic' to call the function pointer. // Beware, private script-defined functions will not be found. @@ -238,7 +238,7 @@ let engine = Engine::new(); let mut ast = engine.compile( r#" let test = "hello"; - |x| test + x // this creates an closure + |x| test + x // this creates an closure "#, )?; @@ -248,9 +248,13 @@ let fn_ptr = engine.eval_ast::(&ast)?; // Get rid of the script, retaining only functions ast.retain_functions(|_, _, _| true); -// Create native call context via a tuple containing the Engine and the -// set of script-defined functions (within the AST) in form of a slice. -let context = (&engine, &[ast.as_ref()]).into(); +// Create native call context via a tuple +let context = + ( + &engine, // the 'Engine' + &[ast.as_ref()] // function namespace from the 'AST' + // as a one-element slice + ).into(); // 'f' captures: the engine, the AST, and the closure let f = move |x: i64| fn_ptr.call_dynamic(context, None, [x.into()]); diff --git a/doc/src/language/throw.md b/doc/src/language/throw.md index 0b6c479e..116b7b44 100644 --- a/doc/src/language/throw.md +++ b/doc/src/language/throw.md @@ -10,24 +10,24 @@ To deliberately return an error during an evaluation, use the `throw` keyword. ```rust if some_bad_condition_has_happened { - throw error; // 'throw' takes a string as the exception text + throw error; // 'throw' any value as the exception } -throw; // defaults to empty exception text: "" +throw; // defaults to '()' ``` Exceptions thrown via `throw` in the script can be captured in Rust by matching -`Err(Box)` with the exception text -captured by `reason`. +`Err(Box)` with the exception value +captured by `value`. ```rust let result = engine.eval::(r#" let x = 42; if x > 0 { - throw x + " is too large!"; + throw x; } "#); -println!(result); // prints "Runtime error: 42 is too large! (line 5, position 15)" +println!(result); // prints "Runtime error: 42 (line 5, position 15)" ``` diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md index b35212e1..8e848734 100644 --- a/doc/src/rust/register-raw.md +++ b/doc/src/rust/register-raw.md @@ -70,9 +70,8 @@ where: * `context: NativeCallContext` - the current _native call context_, which exposes the following: * `context.engine(): &Engine` - the current [`Engine`], with all configurations and settings. - This is sometimes useful for calling a script-defined function within the same evaluation context - using [`Engine::call_fn`][`call_fn`], or calling a [function pointer]. - * `context.namespaces(): &[&Module]` - reference to the chain of namespaces (as a slice of [modules]) containing all script-defined functions. + This is sometimes useful for calling a script-defined function within the same evaluation context using [`Engine::call_fn`][`call_fn`], or calling a [function pointer]. + * `context.iter_namespaces(): impl Iterator` - iterator of the namespaces (as [modules]) containing all script-defined functions. * `args: &mut [&mut Dynamic]` - a slice containing `&mut` references to [`Dynamic`] values. The slice is guaranteed to contain enough arguments _of the correct types_. diff --git a/examples/repl.rs b/examples/repl.rs index f97bc0ba..f6999b6a 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -29,18 +29,11 @@ fn print_error(input: &str, err: EvalAltResult) { // Specific position println!("{}{}", line_no, lines[pos.line().unwrap() - 1]); - let err_text = match err { - EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => { - format!("Runtime error: {}", err) - } - err => err.to_string(), - }; - println!( "{0:>1$} {2}", "^", line_no.len() + pos.position().unwrap(), - err_text.replace(&pos_text, "") + err.to_string().replace(&pos_text, "") ); } } diff --git a/examples/rhai_runner.rs b/examples/rhai_runner.rs index 135269ce..8c202396 100644 --- a/examples/rhai_runner.rs +++ b/examples/rhai_runner.rs @@ -32,14 +32,7 @@ fn eprint_error(input: &str, err: EvalAltResult) { eprintln!("{}", err); } else { // Specific position - let err_text = match err { - EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => { - format!("Runtime error: {}", err) - } - err => err.to_string(), - }; - - eprint_line(&lines, pos, &err_text) + eprint_line(&lines, pos, &err.to_string()) } } diff --git a/src/engine.rs b/src/engine.rs index 9d437937..54c4dc7f 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -442,13 +442,13 @@ pub struct Limits { /// Context of a script evaluation process. #[derive(Debug)] pub struct EvalContext<'e, 'x, 'px: 'x, 'a, 's, 'm, 'pm: 'm, 't, 'pt: 't> { - engine: &'e Engine, + pub(crate) engine: &'e Engine, pub scope: &'x mut Scope<'px>, pub(crate) mods: &'a mut Imports, pub(crate) state: &'s mut State, - lib: &'m [&'pm Module], + pub(crate) lib: &'m [&'pm Module], pub(crate) this_ptr: &'t mut Option<&'pt mut Dynamic>, - level: usize, + pub(crate) level: usize, } impl<'e, 'x, 'px, 'a, 's, 'm, 'pm, 't, 'pt> EvalContext<'e, 'x, 'px, 'a, 's, 'm, 'pm, 't, 'pt> { @@ -465,10 +465,10 @@ impl<'e, 'x, 'px, 'a, 's, 'm, 'pm, 't, 'pt> EvalContext<'e, 'x, 'px, 'a, 's, 'm, pub fn imports(&self) -> &'a Imports { self.mods } - /// The chain of namespaces containing definition of all script-defined functions. + /// Get an iterator over the namespaces containing definition of all script-defined functions. #[inline(always)] - pub fn namespaces(&self) -> &'m [&'pm Module] { - self.lib + pub fn iter_namespaces(&self) -> impl Iterator + 'm { + self.lib.iter().cloned() } /// The current bound `this` pointer, if any. #[inline(always)] @@ -1951,16 +1951,12 @@ impl Engine { Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Exception => { let expr = x.1.as_ref().unwrap(); let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - EvalAltResult::ErrorRuntime( - val.take_string().unwrap_or_else(|_| "".into()), - (x.0).1, - ) - .into() + EvalAltResult::ErrorRuntime(val, (x.0).1).into() } // Empty throw Stmt::ReturnWithVal(x) if (x.0).0 == ReturnType::Exception => { - EvalAltResult::ErrorRuntime("".into(), (x.0).1).into() + EvalAltResult::ErrorRuntime(().into(), (x.0).1).into() } Stmt::ReturnWithVal(_) => unreachable!(), diff --git a/src/fn_call.rs b/src/fn_call.rs index f2ed914f..1844bf5d 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -533,7 +533,8 @@ impl Engine { format!( "'{}' should not be called in method style. Try {}(...);", fn_name, fn_name - ), + ) + .into(), Position::none(), ) .into() diff --git a/src/fn_native.rs b/src/fn_native.rs index a41d6623..b3758e88 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -72,10 +72,10 @@ impl<'e, 'm, 'pm> NativeCallContext<'e, 'm, 'pm> { pub fn engine(&self) -> &'e Engine { self.engine } - /// The chain of namespaces containing definition of all script-defined functions. + /// Get an iterator over the namespaces containing definition of all script-defined functions. #[inline(always)] - pub fn namespaces(&self) -> &'m [&'pm Module] { - self.lib + pub fn iter_namespaces(&self) -> impl Iterator + 'm { + self.lib.iter().cloned() } } @@ -186,7 +186,7 @@ impl FnPtr { .engine() .exec_fn_call( &mut Default::default(), - context.namespaces(), + context.lib, fn_name, hash_script, args.as_mut(), diff --git a/src/parser.rs b/src/parser.rs index 0828c532..389bdff6 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3297,25 +3297,24 @@ fn parse_stmt( match input.peek().unwrap() { // `return`/`throw` at (Token::EOF, pos) => Ok(Some(Stmt::ReturnWithVal(Box::new(( - (return_type, *pos), + (return_type, token_pos), None, - token_pos, + *pos, ))))), // `return;` or `throw;` (Token::SemiColon, _) => Ok(Some(Stmt::ReturnWithVal(Box::new(( - (return_type, settings.pos), + (return_type, token_pos), None, - token_pos, + settings.pos, ))))), // `return` or `throw` with expression (_, _) => { let expr = parse_expr(input, state, lib, settings.level_up())?; let pos = expr.position(); - Ok(Some(Stmt::ReturnWithVal(Box::new(( - (return_type, pos), + (return_type, token_pos), Some(expr), - token_pos, + pos, ))))) } } diff --git a/src/result.rs b/src/result.rs index 650b0a0a..e5faeeb8 100644 --- a/src/result.rs +++ b/src/result.rs @@ -4,6 +4,7 @@ use crate::any::Dynamic; use crate::error::ParseErrorType; use crate::parser::INT; use crate::token::Position; +use crate::utils::ImmutableString; #[cfg(not(feature = "no_function"))] use crate::engine::is_anonymous_fn; @@ -82,8 +83,8 @@ pub enum EvalAltResult { ErrorDataTooLarge(String, usize, usize, Position), /// The script is prematurely terminated. ErrorTerminated(Position), - /// Run-time error encountered. Wrapped value is the error message. - ErrorRuntime(String, Position), + /// Run-time error encountered. Wrapped value is the error. + ErrorRuntime(Dynamic, Position), /// Breaking out of loops - not an error if within a loop. /// The wrapped value, if true, means breaking clean out of the loop (i.e. a `break` statement). @@ -186,7 +187,12 @@ impl fmt::Display for EvalAltResult { | Self::ErrorStackOverflow(_) | Self::ErrorTerminated(_) => f.write_str(desc)?, - Self::ErrorRuntime(s, _) => f.write_str(if s.is_empty() { desc } else { s })?, + Self::ErrorRuntime(d, _) if d.is::() => { + let s = d.as_str().unwrap(); + write!(f, "{}: {}", desc, if s.is_empty() { desc } else { s })? + } + Self::ErrorRuntime(d, _) if d.is::<()>() => f.write_str(desc)?, + Self::ErrorRuntime(d, _) => write!(f, "{}: {}", desc, d)?, Self::ErrorAssignmentToConstant(s, _) => write!(f, "{}: '{}'", desc, s)?, Self::ErrorMismatchOutputType(r, s, _) => { @@ -248,7 +254,7 @@ impl fmt::Display for EvalAltResult { impl> From for EvalAltResult { #[inline(always)] fn from(err: T) -> Self { - Self::ErrorRuntime(err.as_ref().to_string(), Position::none()) + Self::ErrorRuntime(err.as_ref().to_string().into(), Position::none()) } } @@ -256,13 +262,49 @@ impl> From for Box { #[inline(always)] fn from(err: T) -> Self { Box::new(EvalAltResult::ErrorRuntime( - err.as_ref().to_string(), + err.as_ref().to_string().into(), Position::none(), )) } } impl EvalAltResult { + /// Can this error be caught? + pub fn catchable(&self) -> bool { + match self { + Self::ErrorSystem(_, _) => false, + Self::ErrorParsing(_, _) => false, + + Self::ErrorFunctionNotFound(_, _) + | Self::ErrorInFunctionCall(_, _, _) + | Self::ErrorInModule(_, _, _) + | Self::ErrorUnboundThis(_) + | Self::ErrorMismatchDataType(_, _, _) + | Self::ErrorArrayBounds(_, _, _) + | Self::ErrorStringBounds(_, _, _) + | Self::ErrorIndexingType(_, _) + | Self::ErrorFor(_) + | Self::ErrorVariableNotFound(_, _) + | Self::ErrorModuleNotFound(_, _) + | Self::ErrorDataRace(_, _) + | Self::ErrorAssignmentToUnknownLHS(_) + | Self::ErrorAssignmentToConstant(_, _) + | Self::ErrorMismatchOutputType(_, _, _) + | Self::ErrorInExpr(_) + | Self::ErrorDotExpr(_, _) + | Self::ErrorArithmetic(_, _) + | Self::ErrorRuntime(_, _) => true, + + Self::ErrorTooManyOperations(_) + | Self::ErrorTooManyModules(_) + | Self::ErrorStackOverflow(_) + | Self::ErrorDataTooLarge(_, _, _, _) + | Self::ErrorTerminated(_) + | Self::LoopBreak(_, _) + | Self::Return(_, _) => false, + } + } + /// Get the `Position` of this error. pub fn position(&self) -> Position { match self { diff --git a/src/syntax.rs b/src/syntax.rs index 5d3b8a02..c5e9d138 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -64,14 +64,14 @@ impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_, '_> { &mut self, expr: &Expression, ) -> Result> { - self.engine().eval_expr( + self.engine.eval_expr( self.scope, self.mods, self.state, - self.namespaces(), + self.lib, self.this_ptr, expr.expr(), - self.call_level(), + self.level, ) } } diff --git a/tests/throw.rs b/tests/throw.rs index 5f5c58a0..2d7ed7e0 100644 --- a/tests/throw.rs +++ b/tests/throw.rs @@ -5,12 +5,12 @@ fn test_throw() { let engine = Engine::new(); assert!(matches!( - *engine.eval::<()>(r#"if true { throw "hello" }"#).expect_err("expects error"), - EvalAltResult::ErrorRuntime(s, _) if s == "hello" + *engine.eval::<()>("if true { throw 42 }").expect_err("expects error"), + EvalAltResult::ErrorRuntime(s, _) if s.as_int().unwrap() == 42 )); assert!(matches!( *engine.eval::<()>(r#"throw"#).expect_err("expects error"), - EvalAltResult::ErrorRuntime(s, _) if s == "" + EvalAltResult::ErrorRuntime(s, _) if s.is::<()>() )); }