diff --git a/Cargo.toml b/Cargo.toml index 58ca03db..11dc351b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = [ [package] name = "rhai" -version = "0.19.2" +version = "0.19.3" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] description = "Embedded scripting for Rust" diff --git a/RELEASES.md b/RELEASES.md index db52a753..f06c6a74 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -5,6 +5,9 @@ Rhai Release Notes Version 0.19.3 ============== +This version streamlines some of the advanced API's, and adds the `try` ... `catch` statement +to catch exceptions. + Breaking changes ---------------- @@ -17,6 +20,8 @@ New features ------------ * The plugins system is enhanced to support functions taking a `NativeCallContext` as the first parameter. +* `throw` statement can now throw any value instead of just text strings. +* New `try` ... `catch` statement to catch exceptions. Enhancements ------------ diff --git a/codegen/src/attrs.rs b/codegen/src/attrs.rs index eb8d999a..a7986b6b 100644 --- a/codegen/src/attrs.rs +++ b/codegen/src/attrs.rs @@ -1,4 +1,7 @@ -use syn::{parse::ParseStream, parse::Parser, spanned::Spanned}; +use syn::{ + parse::{ParseStream, Parser}, + spanned::Spanned, +}; #[derive(Debug)] pub enum ExportScope { diff --git a/codegen/src/function.rs b/codegen/src/function.rs index d0f56f0b..c0d50588 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -13,7 +13,10 @@ use std::format; use std::borrow::Cow; use quote::{quote, quote_spanned}; -use syn::{parse::Parse, parse::ParseStream, parse::Parser, spanned::Spanned}; +use syn::{ + parse::{Parse, ParseStream, Parser}, + spanned::Spanned, +}; use crate::attrs::{ExportInfo, ExportScope, ExportedParams}; @@ -360,7 +363,7 @@ impl Parse for ExportedFn { pass_context, return_dynamic, mut_receiver, - params: ExportedFnParams::default(), + params: Default::default(), }) } } @@ -466,13 +469,10 @@ impl ExportedFn { // // 1. Do not allow non-returning raw functions. // - if params.return_raw - && mem::discriminant(&self.signature.output) - == mem::discriminant(&syn::ReturnType::Default) - { + if params.return_raw && self.return_type().is_none() { return Err(syn::Error::new( self.signature.span(), - "return_raw functions must return Result>", + "functions marked with 'return_raw' must return Result>", )); } @@ -481,7 +481,7 @@ impl ExportedFn { FnSpecialAccess::Property(Property::Get(_)) if self.arg_count() != 1 => { return Err(syn::Error::new( self.signature.span(), - "property getter requires exactly 1 argument", + "property getter requires exactly 1 parameter", )) } // 2b. Property getters must return a value. @@ -495,7 +495,7 @@ impl ExportedFn { FnSpecialAccess::Property(Property::Set(_)) if self.arg_count() != 2 => { return Err(syn::Error::new( self.signature.span(), - "property setter requires exactly 2 arguments", + "property setter requires exactly 2 parameters", )) } // 3b. Property setters must return nothing. @@ -509,7 +509,7 @@ impl ExportedFn { FnSpecialAccess::Index(Index::Get) if self.arg_count() != 2 => { return Err(syn::Error::new( self.signature.span(), - "index getter requires exactly 2 arguments", + "index getter requires exactly 2 parameters", )) } // 4b. Index getters must return a value. @@ -523,7 +523,7 @@ impl ExportedFn { FnSpecialAccess::Index(Index::Set) if self.arg_count() != 3 => { return Err(syn::Error::new( self.signature.span(), - "index setter requires exactly 3 arguments", + "index setter requires exactly 3 parameters", )) } // 5b. Index setters must return nothing. @@ -593,19 +593,19 @@ impl ExportedFn { if self.params.return_raw { quote_spanned! { return_span=> pub #dynamic_signature { - super::#name(#(#arguments),*) + #name(#(#arguments),*) } } } else if self.return_dynamic { quote_spanned! { return_span=> pub #dynamic_signature { - Ok(super::#name(#(#arguments),*)) + Ok(#name(#(#arguments),*)) } } } else { quote_spanned! { return_span=> pub #dynamic_signature { - Ok(Dynamic::from(super::#name(#(#arguments),*))) + Ok(Dynamic::from(#name(#(#arguments),*))) } } } diff --git a/codegen/src/test/function.rs b/codegen/src/test/function.rs index ee5616e7..3cfdbf7b 100644 --- a/codegen/src/test/function.rs +++ b/codegen/src/test/function.rs @@ -297,7 +297,7 @@ mod generate_tests { Token().input_types() } pub fn dynamic_result_fn() -> Result > { - Ok(Dynamic::from(super::do_nothing())) + Ok(Dynamic::from(do_nothing())) } } }; @@ -339,7 +339,7 @@ mod generate_tests { Token().input_types() } pub fn dynamic_result_fn(x: usize) -> Result > { - Ok(Dynamic::from(super::do_something(x))) + Ok(Dynamic::from(do_something(x))) } } }; @@ -381,7 +381,7 @@ mod generate_tests { Token().input_types() } pub fn dynamic_result_fn(context: NativeCallContext, x: usize) -> Result > { - Ok(Dynamic::from(super::do_something(context, x))) + Ok(Dynamic::from(do_something(context, x))) } } }; @@ -425,7 +425,7 @@ mod generate_tests { Token().input_types() } pub fn dynamic_result_fn() -> Result > { - Ok(super::return_dynamic()) + Ok(return_dynamic()) } } }; @@ -497,7 +497,7 @@ mod generate_tests { Token().input_types() } pub fn dynamic_result_fn(x: usize, y: usize) -> Result > { - Ok(Dynamic::from(super::add_together(x, y))) + Ok(Dynamic::from(add_together(x, y))) } } }; @@ -541,7 +541,7 @@ mod generate_tests { Token().input_types() } pub fn dynamic_result_fn(x: &mut usize, y: usize) -> Result > { - Ok(Dynamic::from(super::increment(x, y))) + Ok(Dynamic::from(increment(x, y))) } } }; @@ -584,7 +584,7 @@ mod generate_tests { Token().input_types() } pub fn dynamic_result_fn(message: &str) -> Result > { - Ok(Dynamic::from(super::special_print(message))) + Ok(Dynamic::from(special_print(message))) } } }; diff --git a/codegen/ui_tests/export_fn_raw_noreturn.stderr b/codegen/ui_tests/export_fn_raw_noreturn.stderr index db719f9b..b2574e9c 100644 --- a/codegen/ui_tests/export_fn_raw_noreturn.stderr +++ b/codegen/ui_tests/export_fn_raw_noreturn.stderr @@ -1,4 +1,4 @@ -error: return_raw functions must return Result> +error: functions marked with 'return_raw' must return Result> --> $DIR/export_fn_raw_noreturn.rs:10:5 | 10 | pub fn test_fn(input: &mut Point) { diff --git a/codegen/ui_tests/export_fn_raw_return.stderr b/codegen/ui_tests/export_fn_raw_return.stderr index f570fda9..7fbb58dc 100644 --- a/codegen/ui_tests/export_fn_raw_return.stderr +++ b/codegen/ui_tests/export_fn_raw_return.stderr @@ -8,14 +8,3 @@ error[E0308]: mismatched types | = note: expected enum `std::result::Result>` found type `bool` - -error[E0308]: mismatched types - --> $DIR/export_fn_raw_return.rs:10:33 - | -9 | #[export_fn(return_raw)] - | ------------------------ expected `std::result::Result>` because of return type -10 | pub fn test_fn(input: Point) -> bool { - | ^^^^ expected enum `std::result::Result`, found `bool` - | - = note: expected enum `std::result::Result>` - found type `bool` diff --git a/codegen/ui_tests/export_mod_raw_noreturn.stderr b/codegen/ui_tests/export_mod_raw_noreturn.stderr index dc7bf10f..d840295f 100644 --- a/codegen/ui_tests/export_mod_raw_noreturn.stderr +++ b/codegen/ui_tests/export_mod_raw_noreturn.stderr @@ -1,4 +1,4 @@ -error: return_raw functions must return Result> +error: functions marked with 'return_raw' must return Result> --> $DIR/export_mod_raw_noreturn.rs:12:5 | 12 | pub fn test_fn(input: &mut Point) { diff --git a/codegen/ui_tests/rhai_fn_getter_signature.stderr b/codegen/ui_tests/rhai_fn_getter_signature.stderr index 0689c83f..70b42738 100644 --- a/codegen/ui_tests/rhai_fn_getter_signature.stderr +++ b/codegen/ui_tests/rhai_fn_getter_signature.stderr @@ -1,4 +1,4 @@ -error: property getter requires exactly 1 argument +error: property getter requires exactly 1 parameter --> $DIR/rhai_fn_getter_signature.rs:13:9 | 13 | pub fn test_fn(input: Point, value: bool) -> bool { diff --git a/codegen/ui_tests/rhai_fn_index_getter_signature.stderr b/codegen/ui_tests/rhai_fn_index_getter_signature.stderr index 7e86413e..3bdffb28 100644 --- a/codegen/ui_tests/rhai_fn_index_getter_signature.stderr +++ b/codegen/ui_tests/rhai_fn_index_getter_signature.stderr @@ -1,4 +1,4 @@ -error: index getter requires exactly 2 arguments +error: index getter requires exactly 2 parameters --> $DIR/rhai_fn_index_getter_signature.rs:13:9 | 13 | pub fn test_fn(input: Point) -> bool { diff --git a/codegen/ui_tests/rhai_fn_setter_index_signature.stderr b/codegen/ui_tests/rhai_fn_setter_index_signature.stderr index 80cfc5fb..b7ae7dad 100644 --- a/codegen/ui_tests/rhai_fn_setter_index_signature.stderr +++ b/codegen/ui_tests/rhai_fn_setter_index_signature.stderr @@ -1,4 +1,4 @@ -error: index setter requires exactly 3 arguments +error: index setter requires exactly 3 parameters --> $DIR/rhai_fn_setter_index_signature.rs:13:9 | 13 | pub fn test_fn(input: Point) -> bool { diff --git a/codegen/ui_tests/rhai_fn_setter_signature.stderr b/codegen/ui_tests/rhai_fn_setter_signature.stderr index 55127a25..0e788c9a 100644 --- a/codegen/ui_tests/rhai_fn_setter_signature.stderr +++ b/codegen/ui_tests/rhai_fn_setter_signature.stderr @@ -1,4 +1,4 @@ -error: property setter requires exactly 2 arguments +error: property setter requires exactly 2 parameters --> $DIR/rhai_fn_setter_signature.rs:13:9 | 13 | pub fn test_fn(input: Point) -> bool { diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 962bdca1..3d7a3ca9 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -82,7 +82,8 @@ The Rhai Scripting Language 12. [For Loop](language/for.md) 13. [Return Values](language/return.md) 14. [Throw Exception on Error](language/throw.md) - 15. [Functions](language/functions.md) + 15. [Catch Exceptions](language/try-catch.md) + 16. [Functions](language/functions.md) 1. [Call Method as Function](language/method.md) 2. [Overloading](language/overload.md) 3. [Namespaces](language/fn-namespaces.md) @@ -90,11 +91,11 @@ The Rhai Scripting Language 5. [Currying](language/fn-curry.md) 6. [Anonymous Functions](language/fn-anon.md) 7. [Closures](language/fn-closure.md) - 16. [Print and Debug](language/print-debug.md) - 17. [Modules](language/modules/index.md) + 17. [Print and Debug](language/print-debug.md) + 18. [Modules](language/modules/index.md) 1. [Export Variables, Functions and Sub-Modules](language/modules/export.md) 2. [Import Modules](language/modules/import.md) - 18. [Eval Statement](language/eval.md) + 19. [Eval Statement](language/eval.md) 6. [Safety and Protection](safety/index.md) 1. [Checked Arithmetic](safety/checked.md) 2. [Sand-Boxing](safety/sandbox.md) diff --git a/doc/src/appendix/keywords.md b/doc/src/appendix/keywords.md index b403ac56..94419067 100644 --- a/doc/src/appendix/keywords.md +++ b/doc/src/appendix/keywords.md @@ -21,6 +21,8 @@ Keywords List | `break` | break out of loop iteration | | no | | | `return` | return value | | no | | | `throw` | throw exception | | no | | +| `try` | trap exception | | no | | +| `catch` | catch exception | | no | | | `import` | import module | [`no_module`] | no | | | `export` | export variable | [`no_module`] | no | | | `as` | alias for variable export | [`no_module`] | no | | @@ -55,8 +57,6 @@ Reserved Keywords | `case` | matching | | `public` | function/field access | | `new` | constructor | -| `try` | trap exception | -| `catch` | catch exception | | `use` | import namespace | | `with` | scope | | `module` | module | diff --git a/doc/src/context.json b/doc/src/context.json index 945d1b1c..7fb6c03e 100644 --- a/doc/src/context.json +++ b/doc/src/context.json @@ -1,5 +1,5 @@ { - "version": "0.19.2", + "version": "0.19.3", "repoHome": "https://github.com/jonathandturner/rhai/blob/master", "repoTree": "https://github.com/jonathandturner/rhai/tree/master", "rootUrl": "", diff --git a/doc/src/engine/custom-syntax.md b/doc/src/engine/custom-syntax.md index 6bbe1157..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.namespace(): &Module` - reference to the current _global namespace_ (as a [module]) 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 00b6a4fd..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.namespace(): &Module` - reference to the current _global namespace_ (as a [module]) 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 00e9edac..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) -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..a11a8074 100644 --- a/doc/src/language/throw.md +++ b/doc/src/language/throw.md @@ -10,24 +10,43 @@ 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)" +``` + + +Catch a Thrown Exception +------------------------ + +It is possible to _catch_ an exception instead of having it abort the evaluation +of the entire script via the [`try` ... `catch`]({{rootUrl}}/language/try-catch.md) +statement common to many C-like languages. + +```rust +try +{ + throw 42; +} +catch (err) // 'err' captures the thrown exception value +{ + print(err); // prints 42 +} ``` diff --git a/doc/src/language/try-catch.md b/doc/src/language/try-catch.md new file mode 100644 index 00000000..3ea934fc --- /dev/null +++ b/doc/src/language/try-catch.md @@ -0,0 +1,104 @@ +Catch Exceptions +================ + +{{#include ../links.md}} + + +When an [exception] is thrown via a `throw` statement, evaluation of the script halts +and the [`Engine`] returns with `Err(Box)` containing the +exception value that has been thrown. + +It is possible, via the `try` ... `catch` statement, to _catch_ exceptions. + +```rust +// Catch an exception and capturing its value +try +{ + throw 42; +} +catch (err) // 'err' captures the thrown exception value +{ + print(err); // prints 42 +} + +// Catch an exception without capturing its value +try +{ + print(42/0); // deliberate divide-by-zero exception +} +catch // no catch variable - exception value is discarded +{ + print("Ouch!"); +} + +// Exception in the 'catch' block +try +{ + print(42/0); // throw divide-by-zero exception +} +catch +{ + print("You seem to be dividing by zero here..."); + + throw "die"; // a 'throw' statement inside a 'catch' block + // throws a new exception +} +``` + + +Re-Throw Exception +------------------ + +Like the `try` ... `catch` syntax in most languages, it is possible to _re-throw_ +an exception within the `catch` block simply by another `throw` statement without +a value. + + +```rust +try +{ + // Call something that will throw an exception... + do_something_bad_that_throws(); +} +catch +{ + print("Oooh! You've done something real bad!"); + + throw; // 'throw' without a value within a 'catch' block + // re-throws the original exception +} + +``` + + +Catchable Exceptions +-------------------- + +Many script-oriented exceptions can be caught via `try` ... `catch`: + +* Runtime error thrown by a `throw` statement +* Arithmetic error +* Variable not found +* [Function] not found +* [Module] not found +* Unbound [`this`] +* Data type mismatch +* [Array]/[string] indexing out-of-bounds +* Indexing with an inappropriate type +* `for` statement without an iterator +* Error in an `in` expression +* Data race detected +* Assignment to a calculated value/constant value +* Dot expression error + + +Non-Catchable Exceptions +------------------------ + +Some exceptions _cannot_ be caught: + +* Syntax error during parsing +* System error - e.g. script file not found +* Script evaluation over [limits]({{rootUrl}}/safety/index.md) +* [Stack overflow][maximum call stack depth] +* Script evaluation manually terminated diff --git a/doc/src/links.md b/doc/src/links.md index 82bc8739..3ef7d328 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -89,6 +89,8 @@ [function overloading]: {{rootUrl}}/rust/functions.md#function-overloading [fallible function]: {{rootUrl}}/rust/fallible.md [fallible functions]: {{rootUrl}}/rust/fallible.md +[exception]: {{rootUrl}}/language/throw.md +[exceptions]: {{rootUrl}}/language/throw.md [function pointer]: {{rootUrl}}/language/fn-ptr.md [function pointers]: {{rootUrl}}/language/fn-ptr.md [currying]: {{rootUrl}}/language/fn-curry.md diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md index 87854acc..8e848734 100644 --- a/doc/src/rust/register-raw.md +++ b/doc/src/rust/register-raw.md @@ -70,10 +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.namespace(): &Module` - the global namespace of script-defined functions, as a [`Module`]. + 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/doc/src/safety/max-call-stack.md b/doc/src/safety/max-call-stack.md index 374dc2a6..7efb3c9d 100644 --- a/doc/src/safety/max-call-stack.md +++ b/doc/src/safety/max-call-stack.md @@ -6,7 +6,7 @@ Maximum Call Stack Depth Limit How Stack Usage by Scripts ------------------------------- -Rhai by default limits function calls to a maximum depth of 128 levels (12 levels in debug build). +Rhai by default limits function calls to a maximum depth of 128 levels (8 levels in debug build). This limit may be changed via the `Engine::set_max_call_levels` method. 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/api.rs b/src/api.rs index 2e962b4f..cfb1199a 100644 --- a/src/api.rs +++ b/src/api.rs @@ -804,7 +804,7 @@ impl Engine { /// ``` #[inline(always)] pub fn compile(&self, script: &str) -> Result { - self.compile_with_scope(&Scope::new(), script) + self.compile_with_scope(&Default::default(), script) } /// Compile a string into an `AST` using own scope, which can be used later for evaluation. @@ -965,7 +965,7 @@ impl Engine { #[cfg(not(target_arch = "wasm32"))] #[inline(always)] pub fn compile_file(&self, path: PathBuf) -> Result> { - self.compile_file_with_scope(&Scope::new(), path) + self.compile_file_with_scope(&Default::default(), path) } /// Compile a script file into an `AST` using own scope, which can be used later for evaluation. @@ -1050,7 +1050,7 @@ impl Engine { /// ``` #[cfg(not(feature = "no_object"))] pub fn parse_json(&self, json: &str, has_null: bool) -> Result> { - let mut scope = Scope::new(); + let mut scope = Default::default(); // Trims the JSON string and add a '#' in front let json_text = json.trim_start(); @@ -1112,7 +1112,7 @@ impl Engine { /// ``` #[inline(always)] pub fn compile_expression(&self, script: &str) -> Result { - self.compile_expression_with_scope(&Scope::new(), script) + self.compile_expression_with_scope(&Default::default(), script) } /// Compile a string containing an expression into an `AST` using own scope, @@ -1236,7 +1236,7 @@ impl Engine { /// ``` #[inline(always)] pub fn eval(&self, script: &str) -> Result> { - self.eval_with_scope(&mut Scope::new(), script) + self.eval_with_scope(&mut Default::default(), script) } /// Evaluate a string with own scope. @@ -1294,7 +1294,7 @@ impl Engine { &self, script: &str, ) -> Result> { - self.eval_expression_with_scope(&mut Scope::new(), script) + self.eval_expression_with_scope(&mut Default::default(), script) } /// Evaluate a string containing an expression with own scope. @@ -1350,7 +1350,7 @@ impl Engine { /// ``` #[inline(always)] pub fn eval_ast(&self, ast: &AST) -> Result> { - self.eval_ast_with_scope(&mut Scope::new(), ast) + self.eval_ast_with_scope(&mut Default::default(), ast) } /// Evaluate an `AST` with own scope. @@ -1388,7 +1388,7 @@ impl Engine { scope: &mut Scope, ast: &AST, ) -> Result> { - let mut mods = Imports::new(); + let mut mods = Default::default(); let (result, _) = self.eval_ast_with_scope_raw(scope, &mut mods, ast)?; let typ = self.map_type_name(result.type_name()); @@ -1404,26 +1404,14 @@ impl Engine { } /// Evaluate an `AST` with own scope. - #[inline] + #[inline(always)] pub(crate) fn eval_ast_with_scope_raw<'a>( &self, scope: &mut Scope, mods: &mut Imports, ast: &'a AST, ) -> Result<(Dynamic, u64), Box> { - let mut state = State::new(); - - ast.statements() - .iter() - .try_fold(().into(), |_, stmt| { - self.eval_stmt(scope, mods, &mut state, ast.lib(), &mut None, stmt, 0) - }) - .or_else(|err| match *err { - EvalAltResult::Return(out, _) => Ok(out), - EvalAltResult::LoopBreak(_, _) => unreachable!(), - _ => Err(err), - }) - .map(|v| (v, state.operations)) + self.eval_statements(scope, mods, ast.statements(), &[ast.lib()]) } /// Evaluate a file, but throw away the result and only return error (if any). @@ -1452,7 +1440,7 @@ impl Engine { /// Useful for when you don't need the result, but still need to keep track of possible errors. #[inline(always)] pub fn consume(&self, script: &str) -> Result<(), Box> { - self.consume_with_scope(&mut Scope::new(), script) + self.consume_with_scope(&mut Default::default(), script) } /// Evaluate a string with own scope, but throw away the result and only return error (if any). @@ -1473,33 +1461,20 @@ impl Engine { /// Useful for when you don't need the result, but still need to keep track of possible errors. #[inline(always)] pub fn consume_ast(&self, ast: &AST) -> Result<(), Box> { - self.consume_ast_with_scope(&mut Scope::new(), ast) + self.consume_ast_with_scope(&mut Default::default(), ast) } /// Evaluate an `AST` with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. - #[inline] + #[inline(always)] pub fn consume_ast_with_scope( &self, scope: &mut Scope, ast: &AST, ) -> Result<(), Box> { - let mut state = State::new(); let mut mods = Default::default(); - - ast.statements() - .iter() - .try_fold(().into(), |_, stmt| { - self.eval_stmt(scope, &mut mods, &mut state, ast.lib(), &mut None, stmt, 0) - }) - .map_or_else( - |err| match *err { - EvalAltResult::Return(_, _) => Ok(()), - EvalAltResult::LoopBreak(_, _) => unreachable!(), - _ => Err(err), - }, - |_| Ok(()), - ) + self.eval_statements(scope, &mut mods, ast.statements(), &[ast.lib()]) + .map(|_| ()) } /// Call a script function defined in an `AST` with multiple arguments. @@ -1651,14 +1626,23 @@ impl Engine { .ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::none()))?; let mut state = State::new(); - let mut mods = Imports::new(); + let mut mods = Default::default(); // Check for data race. if cfg!(not(feature = "no_closure")) { ensure_no_data_race(name, args, false)?; } - self.call_script_fn(scope, &mut mods, &mut state, lib, this_ptr, fn_def, args, 0) + self.call_script_fn( + scope, + &mut mods, + &mut state, + &[lib], + this_ptr, + fn_def, + args, + 0, + ) } /// Optimize the `AST` with constants defined in an external Scope. diff --git a/src/engine.rs b/src/engine.rs index 5f1022c5..406d54d1 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -75,7 +75,7 @@ pub type Imports = Vec<(ImmutableString, Module)>; #[cfg(not(feature = "unchecked"))] #[cfg(debug_assertions)] -pub const MAX_CALL_STACK_DEPTH: usize = 12; +pub const MAX_CALL_STACK_DEPTH: usize = 8; #[cfg(not(feature = "unchecked"))] #[cfg(debug_assertions)] pub const MAX_EXPR_DEPTH: usize = 32; @@ -441,17 +441,17 @@ pub struct Limits { /// Context of a script evaluation process. #[derive(Debug)] -pub struct EvalContext<'e, 'x, 'px: 'x, 'a, 's, 'm, 't, 'pt: 't> { - engine: &'e Engine, +pub struct EvalContext<'e, 'x, 'px: 'x, 'a, 's, 'm, 'pm: 'm, 't, 'pt: 't> { + 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 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, 't, 'pt> EvalContext<'e, 'x, 'px, 'a, 's, 'm, 't, 'pt> { +impl<'e, 'x, 'px, 'a, 's, 'm, 'pm, 't, 'pt> EvalContext<'e, 'x, 'px, 'a, 's, 'm, 'pm, 't, 'pt> { /// The current `Engine`. #[inline(always)] pub fn engine(&self) -> &'e Engine { @@ -465,10 +465,10 @@ impl<'e, 'x, 'px, 'a, 's, 'm, 't, 'pt> EvalContext<'e, 'x, 'px, 'a, 's, 'm, 't, pub fn imports(&self) -> &'a Imports { self.mods } - /// The global namespace containing definition of all script-defined functions. + /// Get an iterator over the namespaces containing definition of all script-defined functions. #[inline(always)] - pub fn namespace(&self) -> &'m Module { - self.lib + pub fn iter_namespaces(&self) -> impl Iterator + 'm { + self.lib.iter().cloned() } /// The current bound `this` pointer, if any. #[inline(always)] @@ -757,7 +757,7 @@ impl Engine { scope: &'s mut Scope, mods: &'s mut Imports, state: &mut State, - lib: &Module, + lib: &[&Module], this_ptr: &'s mut Option<&mut Dynamic>, expr: &'a Expr, ) -> Result<(Target<'s>, &'a str, ScopeEntryType, Position), Box> { @@ -792,7 +792,7 @@ impl Engine { scope: &'s mut Scope, mods: &mut Imports, state: &mut State, - lib: &Module, + lib: &[&Module], this_ptr: &'s mut Option<&mut Dynamic>, expr: &'a Expr, ) -> Result<(Target<'s>, &'a str, ScopeEntryType, Position), Box> { @@ -862,7 +862,7 @@ impl Engine { fn eval_dot_index_chain_helper( &self, state: &mut State, - lib: &Module, + lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, target: &mut Target, rhs: &Expr, @@ -1155,7 +1155,7 @@ impl Engine { scope: &mut Scope, mods: &mut Imports, state: &mut State, - lib: &Module, + lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, level: usize, @@ -1234,7 +1234,7 @@ impl Engine { scope: &mut Scope, mods: &mut Imports, state: &mut State, - lib: &Module, + lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, chain_type: ChainType, @@ -1305,7 +1305,7 @@ impl Engine { fn get_indexed_mut<'a>( &self, state: &mut State, - _lib: &Module, + _lib: &[&Module], target: &'a mut Target, idx: Dynamic, idx_pos: Position, @@ -1414,7 +1414,7 @@ impl Engine { scope: &mut Scope, mods: &mut Imports, state: &mut State, - lib: &Module, + lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, lhs: &Expr, rhs: &Expr, @@ -1477,7 +1477,7 @@ impl Engine { scope: &mut Scope, mods: &mut Imports, state: &mut State, - lib: &Module, + lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, level: usize, @@ -1786,7 +1786,7 @@ impl Engine { scope: &mut Scope, mods: &mut Imports, state: &mut State, - lib: &Module, + lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, stmt: &Stmt, level: usize, @@ -1932,6 +1932,63 @@ impl Engine { // Break statement Stmt::Break(pos) => EvalAltResult::LoopBreak(true, *pos).into(), + // Try/Catch statement + Stmt::TryCatch(x) => { + let ((body, _), var_def, (catch_body, _)) = x.as_ref(); + + let result = self + .eval_stmt(scope, mods, state, lib, this_ptr, body, level) + .map(|_| ().into()); + + if let Err(err) = result { + match *err { + mut err @ EvalAltResult::ErrorRuntime(_, _) | mut err + if err.catchable() => + { + let value = match err { + EvalAltResult::ErrorRuntime(ref x, _) => x.clone(), + _ => { + err.set_position(Position::none()); + err.to_string().into() + } + }; + let has_var = if let Some((var_name, _)) = var_def { + let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); + scope.push(var_name, value); + state.scope_level += 1; + true + } else { + false + }; + + let mut result = self + .eval_stmt(scope, mods, state, lib, this_ptr, catch_body, level) + .map(|_| ().into()); + + if let Some(result_err) = result.as_ref().err() { + match result_err.as_ref() { + EvalAltResult::ErrorRuntime(x, pos) if x.is::<()>() => { + err.set_position(*pos); + result = Err(Box::new(err)); + } + _ => (), + } + } + + if has_var { + scope.rewind(scope.len() - 1); + state.scope_level -= 1; + } + + result + } + _ => Err(err), + } + } else { + result + } + } + // Return value Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => { let expr = x.1.as_ref().unwrap(); @@ -1951,16 +2008,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/error.rs b/src/error.rs index 1cdfb7aa..0d680b85 100644 --- a/src/error.rs +++ b/src/error.rs @@ -104,7 +104,7 @@ pub enum ParseErrorType { /// /// Never appears under the `no_object` feature. PropertyExpected, - /// Missing a variable name after the `let`, `const` or `for` keywords. + /// Missing a variable name after the `let`, `const`, `for` or `catch` keywords. VariableExpected, /// An identifier is a reserved keyword. Reserved(String), diff --git a/src/fn_call.rs b/src/fn_call.rs index 8e9c1940..1844bf5d 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -10,7 +10,7 @@ use crate::error::ParseErrorType; use crate::fn_native::{FnCallArgs, FnPtr}; use crate::module::{Module, ModuleRef}; use crate::optimize::OptimizationLevel; -use crate::parser::{Expr, ImmutableString, AST, INT}; +use crate::parser::{Expr, ImmutableString, Stmt, INT}; use crate::result::EvalAltResult; use crate::scope::Scope; use crate::stdlib::ops::Deref; @@ -180,7 +180,7 @@ impl Engine { pub(crate) fn call_native_fn( &self, state: &mut State, - lib: &Module, + lib: &[&Module], fn_name: &str, hash_fn: u64, args: &mut FnCallArgs, @@ -345,7 +345,7 @@ impl Engine { scope: &mut Scope, mods: &mut Imports, state: &mut State, - lib: &Module, + lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, fn_def: &ScriptFnDef, args: &mut FnCallArgs, @@ -382,17 +382,15 @@ impl Engine { ); // Merge in encapsulated environment, if any - let mut lib_merged; + let mut lib_merged: StaticVec<_>; let unified_lib = if let Some(ref env_lib) = fn_def.lib { - if lib.is_empty() { - // In the special case of the main script not defining any function - env_lib - } else { - lib_merged = lib.clone(); - lib_merged.merge(env_lib); - &lib_merged + lib_merged = Default::default(); + lib_merged.push(env_lib.as_ref()); + if !lib.is_empty() { + lib_merged.extend(lib.iter().cloned()); } + lib_merged.as_ref() } else { lib }; @@ -433,7 +431,7 @@ impl Engine { #[inline] pub(crate) fn has_override_by_name_and_arguments( &self, - lib: &Module, + lib: &[&Module], name: &str, arg_types: impl AsRef<[TypeId]>, pub_only: bool, @@ -456,7 +454,7 @@ impl Engine { #[inline(always)] pub(crate) fn has_override( &self, - lib: &Module, + lib: &[&Module], hash_fn: u64, hash_script: u64, pub_only: bool, @@ -464,8 +462,8 @@ impl Engine { // NOTE: We skip script functions for global_module and packages, and native functions for lib // First check script-defined functions - lib.contains_fn(hash_script, pub_only) - //|| lib.contains_fn(hash_fn, pub_only) + lib.iter().any(|&m| m.contains_fn(hash_script, pub_only)) + //|| lib.iter().any(|&m| m.contains_fn(hash_fn, pub_only)) // Then check registered functions //|| self.global_module.contains_fn(hash_script, pub_only) || self.global_module.contains_fn(hash_fn, pub_only) @@ -485,7 +483,7 @@ impl Engine { pub(crate) fn exec_fn_call( &self, state: &mut State, - lib: &Module, + lib: &[&Module], fn_name: &str, hash_script: u64, args: &mut FnCallArgs, @@ -535,7 +533,8 @@ impl Engine { format!( "'{}' should not be called in method style. Try {}(...);", fn_name, fn_name - ), + ) + .into(), Position::none(), ) .into() @@ -543,13 +542,14 @@ impl Engine { // Script-like function found #[cfg(not(feature = "no_function"))] - _ if lib.contains_fn(hash_script, pub_only) + _ if lib.iter().any(|&m| m.contains_fn(hash_script, pub_only)) //|| self.global_module.contains_fn(hash_script, pub_only) || self.packages.contains_fn(hash_script, pub_only) => { // Get function let func = lib - .get_fn(hash_script, pub_only) + .iter() + .find_map(|&m| m.get_fn(hash_script, pub_only)) //.or_else(|| self.global_module.get_fn(hash_script, pub_only)) .or_else(|| self.packages.get_fn(hash_script, pub_only)) .unwrap(); @@ -557,8 +557,8 @@ impl Engine { if func.is_script() { let func = func.get_fn_def(); - let scope = &mut Scope::new(); - let mods = &mut Imports::new(); + let scope: &mut Scope = &mut Default::default(); + let mods = &mut Default::default(); // Move captured variables into scope #[cfg(not(feature = "no_closure"))] @@ -634,6 +634,30 @@ impl Engine { } } + /// Evaluate a list of statements. + #[inline] + pub(crate) fn eval_statements<'a>( + &self, + scope: &mut Scope, + mods: &mut Imports, + statements: impl IntoIterator, + lib: &[&Module], + ) -> Result<(Dynamic, u64), Box> { + let mut state = State::new(); + + statements + .into_iter() + .try_fold(().into(), |_, stmt| { + self.eval_stmt(scope, mods, &mut state, lib, &mut None, stmt, 0) + }) + .or_else(|err| match *err { + EvalAltResult::Return(out, _) => Ok(out), + EvalAltResult::LoopBreak(_, _) => unreachable!(), + _ => Err(err), + }) + .map(|v| (v, state.operations)) + } + /// Evaluate a text string as a script - used primarily for 'eval'. /// Position in `EvalAltResult` is `None` and must be set afterwards. fn eval_script_expr( @@ -641,7 +665,7 @@ impl Engine { scope: &mut Scope, mods: &mut Imports, state: &mut State, - lib: &Module, + lib: &[&Module], script: &str, _level: usize, ) -> Result> { @@ -658,8 +682,8 @@ impl Engine { // Compile the script text // No optimizations because we only run it once - let mut ast = self.compile_with_scope_and_optimization_level( - &Scope::new(), + let ast = self.compile_with_scope_and_optimization_level( + &Default::default(), &[script], OptimizationLevel::None, )?; @@ -669,11 +693,8 @@ impl Engine { return Err(ParseErrorType::WrongFnDefinition.into()); } - let statements = mem::take(ast.statements_mut()); - let ast = AST::new(statements, lib.clone()); - // Evaluate the AST - let (result, operations) = self.eval_ast_with_scope_raw(scope, mods, &ast)?; + let (result, operations) = self.eval_statements(scope, mods, ast.statements(), lib)?; state.operations += operations; self.inc_operations(state)?; @@ -687,7 +708,7 @@ impl Engine { pub(crate) fn make_method_call( &self, state: &mut State, - lib: &Module, + lib: &[&Module], name: &str, hash_script: u64, target: &mut Target, @@ -839,7 +860,7 @@ impl Engine { scope: &mut Scope, mods: &mut Imports, state: &mut State, - lib: &Module, + lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, name: &str, args_expr: impl AsRef<[Expr]>, @@ -984,7 +1005,7 @@ impl Engine { return Ok(false.into()); } else { let hash = calc_fn_hash(empty(), fn_name, num_params as usize, empty()); - return Ok(lib.contains_fn(hash, false).into()); + return Ok(lib.iter().any(|&m| m.contains_fn(hash, false)).into()); } } } @@ -1085,7 +1106,7 @@ impl Engine { scope: &mut Scope, mods: &mut Imports, state: &mut State, - lib: &Module, + lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, modules: &Option>, name: &str, @@ -1186,8 +1207,8 @@ impl Engine { let args = args.as_mut(); let fn_def = f.get_fn_def(); - let new_scope = &mut Scope::new(); - let mods = &mut Imports::new(); + let new_scope = &mut Default::default(); + let mods = &mut Default::default(); self.call_script_fn(new_scope, mods, state, lib, &mut None, fn_def, args, level) } diff --git a/src/fn_native.rs b/src/fn_native.rs index c37c8b28..b3758e88 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -50,30 +50,32 @@ pub type Locked = RwLock; /// Context of native Rust function call. #[derive(Debug, Copy, Clone)] -pub struct NativeCallContext<'e, 'm> { +pub struct NativeCallContext<'e, 'm, 'pm: 'm> { engine: &'e Engine, - lib: &'m Module, + lib: &'m [&'pm Module], } -impl<'e, 'm> From<(&'e Engine, &'m Module)> for NativeCallContext<'e, 'm> { - fn from(value: (&'e Engine, &'m Module)) -> Self { +impl<'e, 'm, 'pm: 'm, M: AsRef<[&'pm Module]> + ?Sized> From<(&'e Engine, &'m M)> + for NativeCallContext<'e, 'm, 'pm> +{ + fn from(value: (&'e Engine, &'m M)) -> Self { Self { engine: value.0, - lib: value.1, + lib: value.1.as_ref(), } } } -impl<'e, 'm> NativeCallContext<'e, 'm> { +impl<'e, 'm, 'pm> NativeCallContext<'e, 'm, 'pm> { /// The current `Engine`. #[inline(always)] pub fn engine(&self) -> &'e Engine { self.engine } - /// The global namespace containing definition of all script-defined functions. + /// Get an iterator over the namespaces containing definition of all script-defined functions. #[inline(always)] - pub fn namespace(&self) -> &'m Module { - self.lib + pub fn iter_namespaces(&self) -> impl Iterator + 'm { + self.lib.iter().cloned() } } @@ -184,7 +186,7 @@ impl FnPtr { .engine() .exec_fn_call( &mut Default::default(), - context.namespace(), + context.lib, fn_name, hash_script, args.as_mut(), diff --git a/src/optimize.rs b/src/optimize.rs index 631f68d6..5c8053e4 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -70,7 +70,7 @@ struct State<'a> { /// An `Engine` instance for eager function evaluation. engine: &'a Engine, /// Library of script-defined functions. - lib: &'a Module, + lib: &'a [&'a Module], /// Optimization level. optimization_level: OptimizationLevel, } @@ -78,7 +78,7 @@ struct State<'a> { impl<'a> State<'a> { /// Create a new State. #[inline(always)] - pub fn new(engine: &'a Engine, lib: &'a Module, level: OptimizationLevel) -> Self { + pub fn new(engine: &'a Engine, lib: &'a [&'a Module], level: OptimizationLevel) -> Self { Self { changed: false, constants: vec![], @@ -268,7 +268,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { ))), // let id; stmt @ Stmt::Let(_) => stmt, - // import expr as id; + // import expr as var; #[cfg(not(feature = "no_module"))] Stmt::Import(x) => Stmt::Import(Box::new((optimize_expr(x.0, state), x.1, x.2))), // { block } @@ -389,6 +389,22 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { _ => Stmt::Block(Box::new((result.into(), pos))), } } + // try { block } catch ( var ) { block } + Stmt::TryCatch(x) if (x.0).0.is_pure() => { + // If try block is pure, there will never be any exceptions + state.set_dirty(); + let pos = (x.0).0.position(); + let mut statements: StaticVec<_> = Default::default(); + statements.push(optimize_stmt((x.0).0, state, preserve_result)); + statements.push(Stmt::Noop(pos)); + Stmt::Block(Box::new((statements, pos))) + } + // try { block } catch ( var ) { block } + Stmt::TryCatch(x) => Stmt::TryCatch(Box::new(( + (optimize_stmt((x.0).0, state, false), (x.0).1), + x.1, + (optimize_stmt((x.2).0, state, false), (x.2).1), + ))), // expr; Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))), // return expr; @@ -615,7 +631,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // First search for script-defined functions (can override built-in) #[cfg(not(feature = "no_function"))] - let has_script_fn = state.lib.get_script_fn(name, args.len(), false).is_some(); + let has_script_fn = state.lib.iter().any(|&m| m.get_script_fn(name, args.len(), false).is_some()); #[cfg(feature = "no_function")] let has_script_fn = false; @@ -686,7 +702,7 @@ fn optimize( statements: Vec, engine: &Engine, scope: &Scope, - lib: &Module, + lib: &[&Module], level: OptimizationLevel, ) -> Vec { // If optimization level is None then skip optimizing @@ -837,7 +853,8 @@ pub fn optimize_into_ast( let pos = fn_def.body.position(); // Optimize the function body - let mut body = optimize(vec![fn_def.body], engine, &Scope::new(), &lib2, level); + let mut body = + optimize(vec![fn_def.body], engine, &Scope::new(), &[&lib2], level); // {} -> Noop fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) { @@ -877,7 +894,7 @@ pub fn optimize_into_ast( match level { OptimizationLevel::None => statements, OptimizationLevel::Simple | OptimizationLevel::Full => { - optimize(statements, engine, &scope, &lib, level) + optimize(statements, engine, &scope, &[&lib], level) } }, lib, diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index 21dc8e86..46ab0119 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -45,21 +45,31 @@ where Ok(StepRange::(from, to, step)) } +macro_rules! reg_range { + ($lib:expr, $x:expr, $( $y:ty ),*) => ( + $( + $lib.set_iterator::>(); + $lib.set_fn_2($x, get_range::<$y>); + )* + ) +} + +macro_rules! reg_step { + ($lib:expr, $x:expr, $( $y:ty ),*) => ( + $( + $lib.set_iterator::>(); + $lib.set_fn_3($x, get_step_range::<$y>); + )* + ) +} + def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { lib.set_iterator::>(); - lib.set_fn_2("range", get_range::); - if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { - macro_rules! reg_range { - ($lib:expr, $x:expr, $( $y:ty ),*) => ( - $( - $lib.set_iterator::>(); - $lib.set_fn_2($x, get_range::<$y>); - )* - ) - } - + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { reg_range!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64); if cfg!(not(target_arch = "wasm32")) { @@ -70,16 +80,9 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { lib.set_iterator::>(); lib.set_fn_3("range", get_step_range::); - if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { - macro_rules! reg_step { - ($lib:expr, $x:expr, $( $y:ty ),*) => ( - $( - $lib.set_iterator::>(); - $lib.set_fn_3($x, get_step_range::<$y>); - )* - ) - } - + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { reg_step!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64); if cfg!(not(target_arch = "wasm32")) { diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index ae665b18..7b905ca9 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -45,6 +45,20 @@ macro_rules! reg_functions { } def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { + // Integer functions + combine_with_exported_module!(lib, "int", int_functions); + + reg_functions!(lib += basic_to_int::to_int(char)); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_functions!(lib += numbers_to_int::to_int(i8, u8, i16, u16, i32, u32, i64, u64)); + + #[cfg(not(target_arch = "wasm32"))] + reg_functions!(lib += num_128_to_int::to_int(i128, u128)); + } + #[cfg(not(feature = "no_float"))] { // Floating point functions @@ -64,22 +78,36 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { reg_functions!(lib += num_128_to_float::to_float(i128, u128)); } } - - reg_functions!(lib += basic_to_int::to_int(char)); - - set_exported_fn!(lib, "parse_int", parse_int); - set_exported_fn!(lib, "parse_int", parse_int_radix); - - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { - reg_functions!(lib += numbers_to_int::to_int(i8, u8, i16, u16, i32, u32, i64, u64)); - - #[cfg(not(target_arch = "wasm32"))] - reg_functions!(lib += num_128_to_int::to_int(i128, u128)); - } }); +#[export_module] +mod int_functions { + #[rhai_fn(name = "parse_int", return_raw)] + pub fn parse_int_radix(s: &str, radix: INT) -> Result> { + if radix < 2 || radix > 36 { + return EvalAltResult::ErrorArithmetic( + format!("Invalid radix: '{}'", radix), + Position::none(), + ) + .into(); + } + + INT::from_str_radix(s.trim(), radix as u32) + .map(Into::::into) + .map_err(|err| { + EvalAltResult::ErrorArithmetic( + format!("Error parsing integer number '{}': {}", s, err), + Position::none(), + ) + .into() + }) + } + #[rhai_fn(name = "parse_int", return_raw)] + pub fn parse_int(s: &str) -> Result> { + parse_int_radix(s, 10) + } +} + #[cfg(not(feature = "no_float"))] #[export_module] mod trig_functions { @@ -199,7 +227,6 @@ mod float_functions { Ok((x.trunc() as INT).into()) } } - #[rhai_fn(return_raw)] pub fn parse_float(s: &str) -> Result> { s.trim() @@ -239,29 +266,3 @@ gen_conversion_functions!(numbers_to_int => to_int (i8, u8, i16, u16, i32, u32, #[cfg(not(feature = "only_i64"))] #[cfg(not(target_arch = "wasm32"))] gen_conversion_functions!(num_128_to_int => to_int (i128, u128) -> INT); - -#[export_fn(return_raw)] -fn parse_int_radix(s: &str, radix: INT) -> Result> { - if radix < 2 || radix > 36 { - return EvalAltResult::ErrorArithmetic( - format!("Invalid radix: '{}'", radix), - Position::none(), - ) - .into(); - } - - INT::from_str_radix(s.trim(), radix as u32) - .map(Into::::into) - .map_err(|err| { - EvalAltResult::ErrorArithmetic( - format!("Error parsing integer number '{}': {}", s, err), - Position::none(), - ) - .into() - }) -} - -#[export_fn(return_raw)] -fn parse_int(s: &str) -> Result> { - parse_int_radix(s, 10) -} diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 2733532d..988e1dac 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -47,16 +47,10 @@ macro_rules! reg_debug_functions { } def_package!(crate:BasicStringPackage:"Basic string utilities, including printing.", lib, { - reg_print_functions!(lib += print_basic; INT, bool, char, FnPtr); - set_exported_fn!(lib, KEYWORD_PRINT, print_empty_string); - set_exported_fn!(lib, KEYWORD_PRINT, print_unit); - set_exported_fn!(lib, FN_TO_STRING, print_unit); - set_exported_fn!(lib, KEYWORD_PRINT, print_string); - set_exported_fn!(lib, FN_TO_STRING, print_string); + combine_with_exported_module!(lib, "print_debug", print_debug_functions); + reg_print_functions!(lib += print_basic; INT, bool, char, FnPtr); reg_debug_functions!(lib += debug_basic; INT, bool, Unit, char, ImmutableString); - set_exported_fn!(lib, KEYWORD_DEBUG, print_empty_string); - set_exported_fn!(lib, KEYWORD_DEBUG, debug_fn_ptr); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] @@ -82,15 +76,15 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin reg_print_functions!(lib += print_array; Array); reg_debug_functions!(lib += print_array; Array); } - - #[cfg(not(feature = "no_object"))] - { - set_exported_fn!(lib, KEYWORD_PRINT, format_map::format_map); - set_exported_fn!(lib, FN_TO_STRING, format_map::format_map); - set_exported_fn!(lib, KEYWORD_DEBUG, format_map::format_map); - } }); +fn to_string(x: &mut T) -> ImmutableString { + x.to_string().into() +} +fn to_debug(x: &mut T) -> ImmutableString { + format!("{:?}", x).into() +} + gen_functions!(print_basic => to_string(INT, bool, char, FnPtr)); gen_functions!(debug_basic => to_debug(INT, bool, Unit, char, ImmutableString)); @@ -122,35 +116,32 @@ gen_functions!(debug_float => to_debug(f32, f64)); gen_functions!(print_array => to_debug(Array)); // Register print and debug -#[export_fn] -fn print_empty_string() -> ImmutableString { - "".to_string().into() -} -#[export_fn] -fn print_unit(_x: ()) -> ImmutableString { - "".to_string().into() -} -#[export_fn] -fn print_string(s: ImmutableString) -> ImmutableString { - s -} -#[export_fn] -fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString { - to_string(f) -} -fn to_string(x: &mut T) -> ImmutableString { - x.to_string().into() -} -fn to_debug(x: &mut T) -> ImmutableString { - format!("{:?}", x).into() -} +#[export_module] +mod print_debug_functions { + #[rhai_fn(name = "print", name = "debug")] + pub fn print_empty_string() -> ImmutableString { + "".to_string().into() + } + #[rhai_fn(name = "print", name = "to_string")] + pub fn print_unit(_x: ()) -> ImmutableString { + "".to_string().into() + } + #[rhai_fn(name = "print", name = "to_string")] + pub fn print_string(s: ImmutableString) -> ImmutableString { + s + } + #[rhai_fn(name = "debug")] + pub fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString { + to_string(f) + } -#[cfg(not(feature = "no_object"))] -mod format_map { - use super::*; + #[cfg(not(feature = "no_object"))] + pub mod map_functions { + use super::*; - #[export_fn] - pub fn format_map(x: &mut Map) -> ImmutableString { - format!("#{:?}", x).into() + #[rhai_fn(name = "print", name = "debug", name = "to_string")] + pub fn format_map(x: &mut Map) -> ImmutableString { + format!("#{:?}", x).into() + } } } diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 06217d11..1d515558 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -247,6 +247,7 @@ mod string_functions { pub fn replace_char(s: &mut ImmutableString, find: char, sub: char) { *s = s.replace(&find.to_string(), &sub.to_string()).into(); } + #[rhai_fn(return_raw)] pub fn pad( _context: NativeCallContext, @@ -363,7 +364,6 @@ mod string_functions { pub fn prepend(x: &mut Array, y: &str) -> String { format!("{:?}{}", x, y) } - pub fn split(s: &str, delimiter: ImmutableString) -> Array { s.split(delimiter.as_str()) .map(Into::::into) diff --git a/src/parser.rs b/src/parser.rs index 0828c532..b6a57086 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -758,6 +758,14 @@ pub enum Stmt { Const(Box<((String, Position), Option, Position)>), /// { stmt; ... } Block(Box<(StaticVec, Position)>), + /// try { stmt; ... } catch ( var ) { stmt; ... } + TryCatch( + Box<( + (Stmt, Position), + Option<(String, Position)>, + (Stmt, Position), + )>, + ), /// expr Expr(Box), /// continue @@ -766,10 +774,10 @@ pub enum Stmt { Break(Position), /// return/throw ReturnWithVal(Box<((ReturnType, Position), Option, Position)>), - /// import expr as module + /// import expr as var #[cfg(not(feature = "no_module"))] Import(Box<(Expr, Option<(ImmutableString, Position)>, Position)>), - /// expr id as name, ... + /// export var as var, ... #[cfg(not(feature = "no_module"))] Export( Box<( @@ -796,13 +804,14 @@ impl Stmt { Stmt::Noop(pos) | Stmt::Continue(pos) | Stmt::Break(pos) => *pos, Stmt::Let(x) => (x.0).1, Stmt::Const(x) => (x.0).1, - Stmt::ReturnWithVal(x) => (x.0).1, Stmt::Block(x) => x.1, Stmt::IfThenElse(x) => x.3, Stmt::Expr(x) => x.position(), Stmt::While(x) => x.2, Stmt::Loop(x) => x.1, Stmt::For(x) => x.3, + Stmt::ReturnWithVal(x) => (x.0).1, + Stmt::TryCatch(x) => (x.0).1, #[cfg(not(feature = "no_module"))] Stmt::Import(x) => x.2, @@ -820,7 +829,6 @@ impl Stmt { Stmt::Noop(pos) | Stmt::Continue(pos) | Stmt::Break(pos) => *pos = new_pos, Stmt::Let(x) => (x.0).1 = new_pos, Stmt::Const(x) => (x.0).1 = new_pos, - Stmt::ReturnWithVal(x) => (x.0).1 = new_pos, Stmt::Block(x) => x.1 = new_pos, Stmt::IfThenElse(x) => x.3 = new_pos, Stmt::Expr(x) => { @@ -829,6 +837,8 @@ impl Stmt { Stmt::While(x) => x.2 = new_pos, Stmt::Loop(x) => x.1 = new_pos, Stmt::For(x) => x.3 = new_pos, + Stmt::ReturnWithVal(x) => (x.0).1 = new_pos, + Stmt::TryCatch(x) => (x.0).1 = new_pos, #[cfg(not(feature = "no_module"))] Stmt::Import(x) => x.2 = new_pos, @@ -849,7 +859,8 @@ impl Stmt { | Stmt::While(_) | Stmt::Loop(_) | Stmt::For(_) - | Stmt::Block(_) => true, + | Stmt::Block(_) + | Stmt::TryCatch(_) => true, // A No-op requires a semicolon in order to know it is an empty statement! Stmt::Noop(_) => false, @@ -884,6 +895,7 @@ impl Stmt { Stmt::Let(_) | Stmt::Const(_) => false, Stmt::Block(x) => x.0.iter().all(Stmt::is_pure), Stmt::Continue(_) | Stmt::Break(_) | Stmt::ReturnWithVal(_) => false, + Stmt::TryCatch(x) => (x.0).0.is_pure() && (x.2).0.is_pure(), #[cfg(not(feature = "no_module"))] Stmt::Import(_) => false, @@ -1358,13 +1370,12 @@ fn eat_token(input: &mut TokenStream, token: Token) -> Position { } /// Match a particular token, consuming it if matched. -fn match_token(input: &mut TokenStream, token: Token) -> Result { - let (t, _) = input.peek().unwrap(); +fn match_token(input: &mut TokenStream, token: Token) -> (bool, Position) { + let (t, pos) = input.peek().unwrap(); if *t == token { - eat_token(input, token); - Ok(true) + (true, eat_token(input, token)) } else { - Ok(false) + (false, *pos) } } @@ -1378,7 +1389,7 @@ fn parse_paren_expr( #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; - if match_token(input, Token::RightParen)? { + if match_token(input, Token::RightParen).0 { return Ok(Expr::Unit(settings.pos)); } @@ -1989,7 +2000,7 @@ fn parse_primary( // Qualified function call with ! #[cfg(not(feature = "no_closure"))] (Expr::Variable(x), Token::Bang) if x.1.is_some() => { - return Err(if !match_token(input, Token::LeftParen)? { + return Err(if !match_token(input, Token::LeftParen).0 { LexError::UnexpectedInput(Token::Bang.syntax().to_string()).into_err(token_pos) } else { PERR::BadInput("'!' cannot be used to call module functions".to_string()) @@ -1999,12 +2010,13 @@ fn parse_primary( // Function call with ! #[cfg(not(feature = "no_closure"))] (Expr::Variable(x), Token::Bang) => { - if !match_token(input, Token::LeftParen)? { + let (matched, pos) = match_token(input, Token::LeftParen); + if !matched { return Err(PERR::MissingToken( Token::LeftParen.syntax().into(), "to start arguments list of function call".into(), ) - .into_err(input.peek().unwrap().1)); + .into_err(pos)); } let ((name, pos), modules, _, _) = *x; @@ -2813,7 +2825,7 @@ fn parse_if( let if_body = parse_block(input, state, lib, settings.level_up())?; // if guard { if_body } else ... - let else_body = if match_token(input, Token::Else).unwrap_or(false) { + let else_body = if match_token(input, Token::Else).0 { Some(if let (Token::If, _) = input.peek().unwrap() { // if guard { if_body } else if ... parse_if(input, state, lib, settings.level_up())? @@ -2957,7 +2969,7 @@ fn parse_let( }; // let name = ... - let init_value = if match_token(input, Token::Equals)? { + let init_value = if match_token(input, Token::Equals).0 { // let name = expr Some(parse_expr(input, state, lib, settings.level_up())?) } else { @@ -2997,7 +3009,7 @@ fn parse_import( let expr = parse_expr(input, state, lib, settings.level_up())?; // import expr as ... - if !match_token(input, Token::As)? { + if !match_token(input, Token::As).0 { return Ok(Stmt::Import(Box::new((expr, None, token_pos)))); } @@ -3046,7 +3058,7 @@ fn parse_export( (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), }; - let rename = if match_token(input, Token::As)? { + let rename = if match_token(input, Token::As).0 { match input.next().unwrap() { (Token::Identifier(s), pos) => Some((s.clone(), pos)), (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { @@ -3121,7 +3133,7 @@ fn parse_block( #[cfg(not(feature = "no_module"))] let prev_mods_len = state.modules.len(); - while !match_token(input, Token::RightBrace)? { + while !match_token(input, Token::RightBrace).0 { // Parse statements inside the block settings.is_global = false; @@ -3297,30 +3309,31 @@ 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, ))))) } } } + Token::Try => parse_try_catch(input, state, lib, settings.level_up()).map(Some), + Token::Let => parse_let(input, state, lib, Normal, settings.level_up()).map(Some), Token::Const => parse_let(input, state, lib, Constant, settings.level_up()).map(Some), @@ -3337,6 +3350,65 @@ fn parse_stmt( } } +/// Parse a try/catch statement. +fn parse_try_catch( + input: &mut TokenStream, + state: &mut ParseState, + lib: &mut FunctionsLib, + mut settings: ParseSettings, +) -> Result { + // try ... + let token_pos = eat_token(input, Token::Try); + settings.pos = token_pos; + + #[cfg(not(feature = "unchecked"))] + settings.ensure_level_within_max_limit(state.max_expr_depth)?; + + // try { body } + let body = parse_block(input, state, lib, settings.level_up())?; + + // try { body } catch + let (matched, catch_pos) = match_token(input, Token::Catch); + + if !matched { + return Err( + PERR::MissingToken(Token::Catch.into(), "for the 'try' statement".into()) + .into_err(catch_pos), + ); + } + + // try { body } catch ( + let var_def = if match_token(input, Token::LeftParen).0 { + let id = match input.next().unwrap() { + (Token::Identifier(s), pos) => (s, pos), + (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), + }; + + let (matched, pos) = match_token(input, Token::RightParen); + + if !matched { + return Err(PERR::MissingToken( + Token::RightParen.into(), + "to enclose the catch variable".into(), + ) + .into_err(pos)); + } + + Some(id) + } else { + None + }; + + // try { body } catch ( var ) { catch_block } + let catch_body = parse_block(input, state, lib, settings.level_up())?; + + Ok(Stmt::TryCatch(Box::new(( + (body, token_pos), + var_def, + (catch_body, catch_pos), + )))) +} + /// Parse a function definition. #[cfg(not(feature = "no_function"))] fn parse_fn( @@ -3365,7 +3437,7 @@ fn parse_fn( let mut params = Vec::new(); - if !match_token(input, Token::RightParen)? { + if !match_token(input, Token::RightParen).0 { let sep_err = format!("to separate the parameters of function '{}'", name); loop { @@ -3515,7 +3587,7 @@ fn parse_anon_fn( let mut params = Vec::new(); if input.next().unwrap().0 != Token::Or { - if !match_token(input, Token::Pipe)? { + if !match_token(input, Token::Pipe).0 { loop { match input.next().unwrap() { (Token::Pipe, _) => break, 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/serde_impl/ser.rs b/src/serde_impl/ser.rs index 90dab3c5..78c64f38 100644 --- a/src/serde_impl/ser.rs +++ b/src/serde_impl/ser.rs @@ -99,7 +99,7 @@ pub fn to_dynamic(value: T) -> Result> impl Error for Box { fn custom(err: T) -> Self { - EvalAltResult::ErrorRuntime(err.to_string(), Position::none()).into() + EvalAltResult::ErrorRuntime(err.to_string().into(), Position::none()).into() } } diff --git a/src/syntax.rs b/src/syntax.rs index 4e305f41..c5e9d138 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -53,7 +53,7 @@ impl Expression<'_> { } } -impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_> { +impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_, '_> { /// Evaluate an expression tree. /// /// ## WARNING - Low Level API @@ -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.namespace(), + self.lib, self.this_ptr, expr.expr(), - self.call_level(), + self.level, ) } } diff --git a/src/token.rs b/src/token.rs index 55dc2d38..fef81ac8 100644 --- a/src/token.rs +++ b/src/token.rs @@ -282,6 +282,10 @@ pub enum Token { Return, /// `throw` Throw, + /// `try` + Try, + /// `catch` + Catch, /// `+=` PlusAssign, /// `-=` @@ -397,6 +401,8 @@ impl Token { Break => "break", Return => "return", Throw => "throw", + Try => "try", + Catch => "catch", PlusAssign => "+=", MinusAssign => "-=", MultiplyAssign => "*=", @@ -479,6 +485,8 @@ impl Token { "break" => Break, "return" => Return, "throw" => Throw, + "try" => Try, + "catch" => Catch, "+=" => PlusAssign, "-=" => MinusAssign, "*=" => MultiplyAssign, @@ -516,9 +524,9 @@ impl Token { "===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public" | "new" | "use" | "module" | "package" | "var" | "static" | "shared" | "with" - | "do" | "each" | "then" | "goto" | "exit" | "switch" | "match" | "case" | "try" - | "catch" | "default" | "void" | "null" | "nil" | "spawn" | "go" | "sync" | "async" - | "await" | "yield" => Reserved(syntax.into()), + | "do" | "each" | "then" | "goto" | "exit" | "switch" | "match" | "case" + | "default" | "void" | "null" | "nil" | "spawn" | "go" | "sync" | "async" | "await" + | "yield" => Reserved(syntax.into()), KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR diff --git a/tests/closures.rs b/tests/closures.rs index 4f5f1b17..1d80f46e 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -270,7 +270,7 @@ fn test_closures_external() -> Result<(), Box> { ast.retain_functions(|_, _, _| true); // Closure 'f' captures: the engine, the AST, and the curried function pointer - let f = move |x: INT| fn_ptr.call_dynamic((&engine, ast.as_ref()).into(), None, [x.into()]); + let f = move |x: INT| fn_ptr.call_dynamic((&engine, &[ast.as_ref()]).into(), None, [x.into()]); assert_eq!(f(42)?.as_str(), Ok("hello42")); diff --git a/tests/throw.rs b/tests/throw.rs index 5f5c58a0..0b8b717f 100644 --- a/tests/throw.rs +++ b/tests/throw.rs @@ -1,16 +1,41 @@ -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, INT}; #[test] 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::<()>() )); } + +#[test] +fn test_try_catch() -> Result<(), Box> { + let engine = Engine::new(); + + assert_eq!( + engine.eval::("try { throw 42; } catch (x) { return x; }")?, + 42 + ); + + assert_eq!( + engine.eval::("try { throw 42; } catch { return 123; }")?, + 123 + ); + + #[cfg(not(feature = "unchecked"))] + assert!(matches!( + *engine + .eval::<()>("try { 42/0; } catch { throw; }") + .expect_err("expects error"), + EvalAltResult::ErrorArithmetic(_, _) + )); + + Ok(()) +}