commit
93966e73d0
@ -6,7 +6,7 @@ members = [
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "rhai"
|
name = "rhai"
|
||||||
version = "0.19.2"
|
version = "0.19.3"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
|
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
|
||||||
description = "Embedded scripting for Rust"
|
description = "Embedded scripting for Rust"
|
||||||
|
@ -5,6 +5,9 @@ Rhai Release Notes
|
|||||||
Version 0.19.3
|
Version 0.19.3
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
This version streamlines some of the advanced API's, and adds the `try` ... `catch` statement
|
||||||
|
to catch exceptions.
|
||||||
|
|
||||||
Breaking changes
|
Breaking changes
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
@ -17,6 +20,8 @@ New features
|
|||||||
------------
|
------------
|
||||||
|
|
||||||
* The plugins system is enhanced to support functions taking a `NativeCallContext` as the first parameter.
|
* 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
|
Enhancements
|
||||||
------------
|
------------
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
use syn::{parse::ParseStream, parse::Parser, spanned::Spanned};
|
use syn::{
|
||||||
|
parse::{ParseStream, Parser},
|
||||||
|
spanned::Spanned,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ExportScope {
|
pub enum ExportScope {
|
||||||
|
@ -13,7 +13,10 @@ use std::format;
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use quote::{quote, quote_spanned};
|
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};
|
use crate::attrs::{ExportInfo, ExportScope, ExportedParams};
|
||||||
|
|
||||||
@ -360,7 +363,7 @@ impl Parse for ExportedFn {
|
|||||||
pass_context,
|
pass_context,
|
||||||
return_dynamic,
|
return_dynamic,
|
||||||
mut_receiver,
|
mut_receiver,
|
||||||
params: ExportedFnParams::default(),
|
params: Default::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -466,13 +469,10 @@ impl ExportedFn {
|
|||||||
//
|
//
|
||||||
// 1. Do not allow non-returning raw functions.
|
// 1. Do not allow non-returning raw functions.
|
||||||
//
|
//
|
||||||
if params.return_raw
|
if params.return_raw && self.return_type().is_none() {
|
||||||
&& mem::discriminant(&self.signature.output)
|
|
||||||
== mem::discriminant(&syn::ReturnType::Default)
|
|
||||||
{
|
|
||||||
return Err(syn::Error::new(
|
return Err(syn::Error::new(
|
||||||
self.signature.span(),
|
self.signature.span(),
|
||||||
"return_raw functions must return Result<T, Box<EvalAltResult>>",
|
"functions marked with 'return_raw' must return Result<Dynamic, Box<EvalAltResult>>",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -481,7 +481,7 @@ impl ExportedFn {
|
|||||||
FnSpecialAccess::Property(Property::Get(_)) if self.arg_count() != 1 => {
|
FnSpecialAccess::Property(Property::Get(_)) if self.arg_count() != 1 => {
|
||||||
return Err(syn::Error::new(
|
return Err(syn::Error::new(
|
||||||
self.signature.span(),
|
self.signature.span(),
|
||||||
"property getter requires exactly 1 argument",
|
"property getter requires exactly 1 parameter",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
// 2b. Property getters must return a value.
|
// 2b. Property getters must return a value.
|
||||||
@ -495,7 +495,7 @@ impl ExportedFn {
|
|||||||
FnSpecialAccess::Property(Property::Set(_)) if self.arg_count() != 2 => {
|
FnSpecialAccess::Property(Property::Set(_)) if self.arg_count() != 2 => {
|
||||||
return Err(syn::Error::new(
|
return Err(syn::Error::new(
|
||||||
self.signature.span(),
|
self.signature.span(),
|
||||||
"property setter requires exactly 2 arguments",
|
"property setter requires exactly 2 parameters",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
// 3b. Property setters must return nothing.
|
// 3b. Property setters must return nothing.
|
||||||
@ -509,7 +509,7 @@ impl ExportedFn {
|
|||||||
FnSpecialAccess::Index(Index::Get) if self.arg_count() != 2 => {
|
FnSpecialAccess::Index(Index::Get) if self.arg_count() != 2 => {
|
||||||
return Err(syn::Error::new(
|
return Err(syn::Error::new(
|
||||||
self.signature.span(),
|
self.signature.span(),
|
||||||
"index getter requires exactly 2 arguments",
|
"index getter requires exactly 2 parameters",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
// 4b. Index getters must return a value.
|
// 4b. Index getters must return a value.
|
||||||
@ -523,7 +523,7 @@ impl ExportedFn {
|
|||||||
FnSpecialAccess::Index(Index::Set) if self.arg_count() != 3 => {
|
FnSpecialAccess::Index(Index::Set) if self.arg_count() != 3 => {
|
||||||
return Err(syn::Error::new(
|
return Err(syn::Error::new(
|
||||||
self.signature.span(),
|
self.signature.span(),
|
||||||
"index setter requires exactly 3 arguments",
|
"index setter requires exactly 3 parameters",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
// 5b. Index setters must return nothing.
|
// 5b. Index setters must return nothing.
|
||||||
@ -593,19 +593,19 @@ impl ExportedFn {
|
|||||||
if self.params.return_raw {
|
if self.params.return_raw {
|
||||||
quote_spanned! { return_span=>
|
quote_spanned! { return_span=>
|
||||||
pub #dynamic_signature {
|
pub #dynamic_signature {
|
||||||
super::#name(#(#arguments),*)
|
#name(#(#arguments),*)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if self.return_dynamic {
|
} else if self.return_dynamic {
|
||||||
quote_spanned! { return_span=>
|
quote_spanned! { return_span=>
|
||||||
pub #dynamic_signature {
|
pub #dynamic_signature {
|
||||||
Ok(super::#name(#(#arguments),*))
|
Ok(#name(#(#arguments),*))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quote_spanned! { return_span=>
|
quote_spanned! { return_span=>
|
||||||
pub #dynamic_signature {
|
pub #dynamic_signature {
|
||||||
Ok(Dynamic::from(super::#name(#(#arguments),*)))
|
Ok(Dynamic::from(#name(#(#arguments),*)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -297,7 +297,7 @@ mod generate_tests {
|
|||||||
Token().input_types()
|
Token().input_types()
|
||||||
}
|
}
|
||||||
pub fn dynamic_result_fn() -> Result<Dynamic, Box<EvalAltResult> > {
|
pub fn dynamic_result_fn() -> Result<Dynamic, Box<EvalAltResult> > {
|
||||||
Ok(Dynamic::from(super::do_nothing()))
|
Ok(Dynamic::from(do_nothing()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -339,7 +339,7 @@ mod generate_tests {
|
|||||||
Token().input_types()
|
Token().input_types()
|
||||||
}
|
}
|
||||||
pub fn dynamic_result_fn(x: usize) -> Result<Dynamic, Box<EvalAltResult> > {
|
pub fn dynamic_result_fn(x: usize) -> Result<Dynamic, Box<EvalAltResult> > {
|
||||||
Ok(Dynamic::from(super::do_something(x)))
|
Ok(Dynamic::from(do_something(x)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -381,7 +381,7 @@ mod generate_tests {
|
|||||||
Token().input_types()
|
Token().input_types()
|
||||||
}
|
}
|
||||||
pub fn dynamic_result_fn(context: NativeCallContext, x: usize) -> Result<Dynamic, Box<EvalAltResult> > {
|
pub fn dynamic_result_fn(context: NativeCallContext, x: usize) -> Result<Dynamic, Box<EvalAltResult> > {
|
||||||
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()
|
Token().input_types()
|
||||||
}
|
}
|
||||||
pub fn dynamic_result_fn() -> Result<Dynamic, Box<EvalAltResult> > {
|
pub fn dynamic_result_fn() -> Result<Dynamic, Box<EvalAltResult> > {
|
||||||
Ok(super::return_dynamic())
|
Ok(return_dynamic())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -497,7 +497,7 @@ mod generate_tests {
|
|||||||
Token().input_types()
|
Token().input_types()
|
||||||
}
|
}
|
||||||
pub fn dynamic_result_fn(x: usize, y: usize) -> Result<Dynamic, Box<EvalAltResult> > {
|
pub fn dynamic_result_fn(x: usize, y: usize) -> Result<Dynamic, Box<EvalAltResult> > {
|
||||||
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()
|
Token().input_types()
|
||||||
}
|
}
|
||||||
pub fn dynamic_result_fn(x: &mut usize, y: usize) -> Result<Dynamic, Box<EvalAltResult> > {
|
pub fn dynamic_result_fn(x: &mut usize, y: usize) -> Result<Dynamic, Box<EvalAltResult> > {
|
||||||
Ok(Dynamic::from(super::increment(x, y)))
|
Ok(Dynamic::from(increment(x, y)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -584,7 +584,7 @@ mod generate_tests {
|
|||||||
Token().input_types()
|
Token().input_types()
|
||||||
}
|
}
|
||||||
pub fn dynamic_result_fn(message: &str) -> Result<Dynamic, Box<EvalAltResult> > {
|
pub fn dynamic_result_fn(message: &str) -> Result<Dynamic, Box<EvalAltResult> > {
|
||||||
Ok(Dynamic::from(super::special_print(message)))
|
Ok(Dynamic::from(special_print(message)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
error: return_raw functions must return Result<T, Box<EvalAltResult>>
|
error: functions marked with 'return_raw' must return Result<Dynamic, Box<EvalAltResult>>
|
||||||
--> $DIR/export_fn_raw_noreturn.rs:10:5
|
--> $DIR/export_fn_raw_noreturn.rs:10:5
|
||||||
|
|
|
|
||||||
10 | pub fn test_fn(input: &mut Point) {
|
10 | pub fn test_fn(input: &mut Point) {
|
||||||
|
@ -8,14 +8,3 @@ error[E0308]: mismatched types
|
|||||||
|
|
|
|
||||||
= note: expected enum `std::result::Result<rhai::Dynamic, std::boxed::Box<rhai::EvalAltResult>>`
|
= note: expected enum `std::result::Result<rhai::Dynamic, std::boxed::Box<rhai::EvalAltResult>>`
|
||||||
found type `bool`
|
found type `bool`
|
||||||
|
|
||||||
error[E0308]: mismatched types
|
|
||||||
--> $DIR/export_fn_raw_return.rs:10:33
|
|
||||||
|
|
|
||||||
9 | #[export_fn(return_raw)]
|
|
||||||
| ------------------------ expected `std::result::Result<rhai::Dynamic, std::boxed::Box<rhai::EvalAltResult>>` 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<rhai::Dynamic, std::boxed::Box<rhai::EvalAltResult>>`
|
|
||||||
found type `bool`
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
error: return_raw functions must return Result<T, Box<EvalAltResult>>
|
error: functions marked with 'return_raw' must return Result<Dynamic, Box<EvalAltResult>>
|
||||||
--> $DIR/export_mod_raw_noreturn.rs:12:5
|
--> $DIR/export_mod_raw_noreturn.rs:12:5
|
||||||
|
|
|
|
||||||
12 | pub fn test_fn(input: &mut Point) {
|
12 | pub fn test_fn(input: &mut Point) {
|
||||||
|
@ -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
|
--> $DIR/rhai_fn_getter_signature.rs:13:9
|
||||||
|
|
|
|
||||||
13 | pub fn test_fn(input: Point, value: bool) -> bool {
|
13 | pub fn test_fn(input: Point, value: bool) -> bool {
|
||||||
|
@ -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
|
--> $DIR/rhai_fn_index_getter_signature.rs:13:9
|
||||||
|
|
|
|
||||||
13 | pub fn test_fn(input: Point) -> bool {
|
13 | pub fn test_fn(input: Point) -> bool {
|
||||||
|
@ -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
|
--> $DIR/rhai_fn_setter_index_signature.rs:13:9
|
||||||
|
|
|
|
||||||
13 | pub fn test_fn(input: Point) -> bool {
|
13 | pub fn test_fn(input: Point) -> bool {
|
||||||
|
@ -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
|
--> $DIR/rhai_fn_setter_signature.rs:13:9
|
||||||
|
|
|
|
||||||
13 | pub fn test_fn(input: Point) -> bool {
|
13 | pub fn test_fn(input: Point) -> bool {
|
||||||
|
@ -82,7 +82,8 @@ The Rhai Scripting Language
|
|||||||
12. [For Loop](language/for.md)
|
12. [For Loop](language/for.md)
|
||||||
13. [Return Values](language/return.md)
|
13. [Return Values](language/return.md)
|
||||||
14. [Throw Exception on Error](language/throw.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)
|
1. [Call Method as Function](language/method.md)
|
||||||
2. [Overloading](language/overload.md)
|
2. [Overloading](language/overload.md)
|
||||||
3. [Namespaces](language/fn-namespaces.md)
|
3. [Namespaces](language/fn-namespaces.md)
|
||||||
@ -90,11 +91,11 @@ The Rhai Scripting Language
|
|||||||
5. [Currying](language/fn-curry.md)
|
5. [Currying](language/fn-curry.md)
|
||||||
6. [Anonymous Functions](language/fn-anon.md)
|
6. [Anonymous Functions](language/fn-anon.md)
|
||||||
7. [Closures](language/fn-closure.md)
|
7. [Closures](language/fn-closure.md)
|
||||||
16. [Print and Debug](language/print-debug.md)
|
17. [Print and Debug](language/print-debug.md)
|
||||||
17. [Modules](language/modules/index.md)
|
18. [Modules](language/modules/index.md)
|
||||||
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)
|
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)
|
||||||
2. [Import Modules](language/modules/import.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)
|
6. [Safety and Protection](safety/index.md)
|
||||||
1. [Checked Arithmetic](safety/checked.md)
|
1. [Checked Arithmetic](safety/checked.md)
|
||||||
2. [Sand-Boxing](safety/sandbox.md)
|
2. [Sand-Boxing](safety/sandbox.md)
|
||||||
|
@ -21,6 +21,8 @@ Keywords List
|
|||||||
| `break` | break out of loop iteration | | no | |
|
| `break` | break out of loop iteration | | no | |
|
||||||
| `return` | return value | | no | |
|
| `return` | return value | | no | |
|
||||||
| `throw` | throw exception | | no | |
|
| `throw` | throw exception | | no | |
|
||||||
|
| `try` | trap exception | | no | |
|
||||||
|
| `catch` | catch exception | | no | |
|
||||||
| `import` | import module | [`no_module`] | no | |
|
| `import` | import module | [`no_module`] | no | |
|
||||||
| `export` | export variable | [`no_module`] | no | |
|
| `export` | export variable | [`no_module`] | no | |
|
||||||
| `as` | alias for variable export | [`no_module`] | no | |
|
| `as` | alias for variable export | [`no_module`] | no | |
|
||||||
@ -55,8 +57,6 @@ Reserved Keywords
|
|||||||
| `case` | matching |
|
| `case` | matching |
|
||||||
| `public` | function/field access |
|
| `public` | function/field access |
|
||||||
| `new` | constructor |
|
| `new` | constructor |
|
||||||
| `try` | trap exception |
|
|
||||||
| `catch` | catch exception |
|
|
||||||
| `use` | import namespace |
|
| `use` | import namespace |
|
||||||
| `with` | scope |
|
| `with` | scope |
|
||||||
| `module` | module |
|
| `module` | module |
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "0.19.2",
|
"version": "0.19.3",
|
||||||
"repoHome": "https://github.com/jonathandturner/rhai/blob/master",
|
"repoHome": "https://github.com/jonathandturner/rhai/blob/master",
|
||||||
"repoTree": "https://github.com/jonathandturner/rhai/tree/master",
|
"repoTree": "https://github.com/jonathandturner/rhai/tree/master",
|
||||||
"rootUrl": "",
|
"rootUrl": "",
|
||||||
|
@ -121,7 +121,7 @@ where:
|
|||||||
* `context: &mut EvalContext` - mutable reference to the current evaluation _context_, exposing the following:
|
* `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.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.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<Item = &Module>` - 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.this_ptr(): Option<&Dynamic>` - reference to the current bound [`this`] pointer, if any.
|
||||||
* `context.call_level(): usize` - the current nesting level of function calls.
|
* `context.call_level(): usize` - the current nesting level of function calls.
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ where:
|
|||||||
* `context: &EvalContext` - reference to the current evaluation _context_, which exposes the following fields:
|
* `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.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.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<Item = &Module>` - 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.this_ptr(): Option<&Dynamic>` - reference to the current bound [`this`] pointer, if any.
|
||||||
* `context.call_level(): usize` - the current nesting level of function calls.
|
* `context.call_level(): usize` - the current nesting level of function calls.
|
||||||
|
|
||||||
|
@ -199,9 +199,9 @@ fn call_fn_ptr_with_value(context: NativeCallContext, args: &mut [&mut Dynamic])
|
|||||||
-> Result<Dynamic, Box<EvalAltResult>>
|
-> Result<Dynamic, Box<EvalAltResult>>
|
||||||
{
|
{
|
||||||
// 'args' is guaranteed to contain enough arguments of the correct types
|
// 'args' is guaranteed to contain enough arguments of the correct types
|
||||||
let fp = std::mem::take(args[1]).cast::<FnPtr>(); // 2nd argument - function pointer
|
let fp = std::mem::take(args[1]).cast::<FnPtr>(); // 2nd argument - function pointer
|
||||||
let value = args[2].clone(); // 3rd argument - function argument
|
let value = args[2].clone(); // 3rd argument - function argument
|
||||||
let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer
|
let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer
|
||||||
|
|
||||||
// Use 'FnPtr::call_dynamic' to call the function pointer.
|
// Use 'FnPtr::call_dynamic' to call the function pointer.
|
||||||
// Beware, private script-defined functions will not be found.
|
// Beware, private script-defined functions will not be found.
|
||||||
@ -238,7 +238,7 @@ let engine = Engine::new();
|
|||||||
let mut ast = engine.compile(
|
let mut ast = engine.compile(
|
||||||
r#"
|
r#"
|
||||||
let test = "hello";
|
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::<FnPtr>(&ast)?;
|
|||||||
// Get rid of the script, retaining only functions
|
// Get rid of the script, retaining only functions
|
||||||
ast.retain_functions(|_, _, _| true);
|
ast.retain_functions(|_, _, _| true);
|
||||||
|
|
||||||
// Create native call context via a tuple containing the Engine and the
|
// Create native call context via a tuple
|
||||||
// set of script-defined functions (within the AST)
|
let context =
|
||||||
let context = (&engine, ast.as_ref()).into();
|
(
|
||||||
|
&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
|
// 'f' captures: the engine, the AST, and the closure
|
||||||
let f = move |x: i64| fn_ptr.call_dynamic(context, None, [x.into()]);
|
let f = move |x: i64| fn_ptr.call_dynamic(context, None, [x.into()]);
|
||||||
|
@ -10,24 +10,43 @@ To deliberately return an error during an evaluation, use the `throw` keyword.
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
if some_bad_condition_has_happened {
|
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
|
Exceptions thrown via `throw` in the script can be captured in Rust by matching
|
||||||
`Err(Box<EvalAltResult::ErrorRuntime(reason, position)>)` with the exception text
|
`Err(Box<EvalAltResult::ErrorRuntime(value, position)>)` with the exception value
|
||||||
captured by `reason`.
|
captured by `value`.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let result = engine.eval::<i64>(r#"
|
let result = engine.eval::<i64>(r#"
|
||||||
let x = 42;
|
let x = 42;
|
||||||
|
|
||||||
if x > 0 {
|
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
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
104
doc/src/language/try-catch.md
Normal file
104
doc/src/language/try-catch.md
Normal file
@ -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<EvalAltResult::ErrorRuntime>)` 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
|
@ -89,6 +89,8 @@
|
|||||||
[function overloading]: {{rootUrl}}/rust/functions.md#function-overloading
|
[function overloading]: {{rootUrl}}/rust/functions.md#function-overloading
|
||||||
[fallible function]: {{rootUrl}}/rust/fallible.md
|
[fallible function]: {{rootUrl}}/rust/fallible.md
|
||||||
[fallible functions]: {{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 pointer]: {{rootUrl}}/language/fn-ptr.md
|
||||||
[function pointers]: {{rootUrl}}/language/fn-ptr.md
|
[function pointers]: {{rootUrl}}/language/fn-ptr.md
|
||||||
[currying]: {{rootUrl}}/language/fn-curry.md
|
[currying]: {{rootUrl}}/language/fn-curry.md
|
||||||
|
@ -70,10 +70,8 @@ where:
|
|||||||
* `context: NativeCallContext` - the current _native call context_, which exposes the following:
|
* `context: NativeCallContext` - the current _native call context_, which exposes the following:
|
||||||
|
|
||||||
* `context.engine(): &Engine` - the current [`Engine`], with all configurations and settings.
|
* `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
|
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].
|
||||||
using [`Engine::call_fn`][`call_fn`], or calling a [function pointer].
|
* `context.iter_namespaces(): impl Iterator<Item = &Module>` - iterator of the namespaces (as [modules]) containing all script-defined functions.
|
||||||
|
|
||||||
* `context.namespace(): &Module` - the global namespace of script-defined functions, as a [`Module`].
|
|
||||||
|
|
||||||
* `args: &mut [&mut Dynamic]` - a slice containing `&mut` references to [`Dynamic`] values.
|
* `args: &mut [&mut Dynamic]` - a slice containing `&mut` references to [`Dynamic`] values.
|
||||||
The slice is guaranteed to contain enough arguments _of the correct types_.
|
The slice is guaranteed to contain enough arguments _of the correct types_.
|
||||||
|
@ -6,7 +6,7 @@ Maximum Call Stack Depth
|
|||||||
Limit How Stack Usage by Scripts
|
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.
|
This limit may be changed via the `Engine::set_max_call_levels` method.
|
||||||
|
|
||||||
|
@ -29,18 +29,11 @@ fn print_error(input: &str, err: EvalAltResult) {
|
|||||||
// Specific position
|
// Specific position
|
||||||
println!("{}{}", line_no, lines[pos.line().unwrap() - 1]);
|
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!(
|
println!(
|
||||||
"{0:>1$} {2}",
|
"{0:>1$} {2}",
|
||||||
"^",
|
"^",
|
||||||
line_no.len() + pos.position().unwrap(),
|
line_no.len() + pos.position().unwrap(),
|
||||||
err_text.replace(&pos_text, "")
|
err.to_string().replace(&pos_text, "")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,14 +32,7 @@ fn eprint_error(input: &str, err: EvalAltResult) {
|
|||||||
eprintln!("{}", err);
|
eprintln!("{}", err);
|
||||||
} else {
|
} else {
|
||||||
// Specific position
|
// Specific position
|
||||||
let err_text = match err {
|
eprint_line(&lines, pos, &err.to_string())
|
||||||
EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => {
|
|
||||||
format!("Runtime error: {}", err)
|
|
||||||
}
|
|
||||||
err => err.to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
eprint_line(&lines, pos, &err_text)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
68
src/api.rs
68
src/api.rs
@ -804,7 +804,7 @@ impl Engine {
|
|||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn compile(&self, script: &str) -> Result<AST, ParseError> {
|
pub fn compile(&self, script: &str) -> Result<AST, ParseError> {
|
||||||
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.
|
/// 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"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn compile_file(&self, path: PathBuf) -> Result<AST, Box<EvalAltResult>> {
|
pub fn compile_file(&self, path: PathBuf) -> Result<AST, Box<EvalAltResult>> {
|
||||||
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.
|
/// 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"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
pub fn parse_json(&self, json: &str, has_null: bool) -> Result<Map, Box<EvalAltResult>> {
|
pub fn parse_json(&self, json: &str, has_null: bool) -> Result<Map, Box<EvalAltResult>> {
|
||||||
let mut scope = Scope::new();
|
let mut scope = Default::default();
|
||||||
|
|
||||||
// Trims the JSON string and add a '#' in front
|
// Trims the JSON string and add a '#' in front
|
||||||
let json_text = json.trim_start();
|
let json_text = json.trim_start();
|
||||||
@ -1112,7 +1112,7 @@ impl Engine {
|
|||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn compile_expression(&self, script: &str) -> Result<AST, ParseError> {
|
pub fn compile_expression(&self, script: &str) -> Result<AST, ParseError> {
|
||||||
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,
|
/// Compile a string containing an expression into an `AST` using own scope,
|
||||||
@ -1236,7 +1236,7 @@ impl Engine {
|
|||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn eval<T: Variant + Clone>(&self, script: &str) -> Result<T, Box<EvalAltResult>> {
|
pub fn eval<T: Variant + Clone>(&self, script: &str) -> Result<T, Box<EvalAltResult>> {
|
||||||
self.eval_with_scope(&mut Scope::new(), script)
|
self.eval_with_scope(&mut Default::default(), script)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a string with own scope.
|
/// Evaluate a string with own scope.
|
||||||
@ -1294,7 +1294,7 @@ impl Engine {
|
|||||||
&self,
|
&self,
|
||||||
script: &str,
|
script: &str,
|
||||||
) -> Result<T, Box<EvalAltResult>> {
|
) -> Result<T, Box<EvalAltResult>> {
|
||||||
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.
|
/// Evaluate a string containing an expression with own scope.
|
||||||
@ -1350,7 +1350,7 @@ impl Engine {
|
|||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn eval_ast<T: Variant + Clone>(&self, ast: &AST) -> Result<T, Box<EvalAltResult>> {
|
pub fn eval_ast<T: Variant + Clone>(&self, ast: &AST) -> Result<T, Box<EvalAltResult>> {
|
||||||
self.eval_ast_with_scope(&mut Scope::new(), ast)
|
self.eval_ast_with_scope(&mut Default::default(), ast)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate an `AST` with own scope.
|
/// Evaluate an `AST` with own scope.
|
||||||
@ -1388,7 +1388,7 @@ impl Engine {
|
|||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
ast: &AST,
|
ast: &AST,
|
||||||
) -> Result<T, Box<EvalAltResult>> {
|
) -> Result<T, Box<EvalAltResult>> {
|
||||||
let mut mods = Imports::new();
|
let mut mods = Default::default();
|
||||||
let (result, _) = self.eval_ast_with_scope_raw(scope, &mut mods, ast)?;
|
let (result, _) = self.eval_ast_with_scope_raw(scope, &mut mods, ast)?;
|
||||||
|
|
||||||
let typ = self.map_type_name(result.type_name());
|
let typ = self.map_type_name(result.type_name());
|
||||||
@ -1404,26 +1404,14 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate an `AST` with own scope.
|
/// Evaluate an `AST` with own scope.
|
||||||
#[inline]
|
#[inline(always)]
|
||||||
pub(crate) fn eval_ast_with_scope_raw<'a>(
|
pub(crate) fn eval_ast_with_scope_raw<'a>(
|
||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
ast: &'a AST,
|
ast: &'a AST,
|
||||||
) -> Result<(Dynamic, u64), Box<EvalAltResult>> {
|
) -> Result<(Dynamic, u64), Box<EvalAltResult>> {
|
||||||
let mut state = State::new();
|
self.eval_statements(scope, mods, ast.statements(), &[ast.lib()])
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a file, but throw away the result and only return error (if any).
|
/// 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.
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn consume(&self, script: &str) -> Result<(), Box<EvalAltResult>> {
|
pub fn consume(&self, script: &str) -> Result<(), Box<EvalAltResult>> {
|
||||||
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).
|
/// 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.
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn consume_ast(&self, ast: &AST) -> Result<(), Box<EvalAltResult>> {
|
pub fn consume_ast(&self, ast: &AST) -> Result<(), Box<EvalAltResult>> {
|
||||||
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).
|
/// 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.
|
/// 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(
|
pub fn consume_ast_with_scope(
|
||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
ast: &AST,
|
ast: &AST,
|
||||||
) -> Result<(), Box<EvalAltResult>> {
|
) -> Result<(), Box<EvalAltResult>> {
|
||||||
let mut state = State::new();
|
|
||||||
let mut mods = Default::default();
|
let mut mods = Default::default();
|
||||||
|
self.eval_statements(scope, &mut mods, ast.statements(), &[ast.lib()])
|
||||||
ast.statements()
|
.map(|_| ())
|
||||||
.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(()),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call a script function defined in an `AST` with multiple arguments.
|
/// 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()))?;
|
.ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::none()))?;
|
||||||
|
|
||||||
let mut state = State::new();
|
let mut state = State::new();
|
||||||
let mut mods = Imports::new();
|
let mut mods = Default::default();
|
||||||
|
|
||||||
// Check for data race.
|
// Check for data race.
|
||||||
if cfg!(not(feature = "no_closure")) {
|
if cfg!(not(feature = "no_closure")) {
|
||||||
ensure_no_data_race(name, args, false)?;
|
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.
|
/// Optimize the `AST` with constants defined in an external Scope.
|
||||||
|
101
src/engine.rs
101
src/engine.rs
@ -75,7 +75,7 @@ pub type Imports = Vec<(ImmutableString, Module)>;
|
|||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
pub const MAX_CALL_STACK_DEPTH: usize = 12;
|
pub const MAX_CALL_STACK_DEPTH: usize = 8;
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
pub const MAX_EXPR_DEPTH: usize = 32;
|
pub const MAX_EXPR_DEPTH: usize = 32;
|
||||||
@ -441,17 +441,17 @@ pub struct Limits {
|
|||||||
|
|
||||||
/// Context of a script evaluation process.
|
/// Context of a script evaluation process.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EvalContext<'e, 'x, 'px: 'x, 'a, 's, 'm, 't, 'pt: 't> {
|
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 scope: &'x mut Scope<'px>,
|
||||||
pub(crate) mods: &'a mut Imports,
|
pub(crate) mods: &'a mut Imports,
|
||||||
pub(crate) state: &'s mut State,
|
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>,
|
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`.
|
/// The current `Engine`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn engine(&self) -> &'e Engine {
|
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 {
|
pub fn imports(&self) -> &'a Imports {
|
||||||
self.mods
|
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)]
|
#[inline(always)]
|
||||||
pub fn namespace(&self) -> &'m Module {
|
pub fn iter_namespaces(&self) -> impl Iterator<Item = &'pm Module> + 'm {
|
||||||
self.lib
|
self.lib.iter().cloned()
|
||||||
}
|
}
|
||||||
/// The current bound `this` pointer, if any.
|
/// The current bound `this` pointer, if any.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -757,7 +757,7 @@ impl Engine {
|
|||||||
scope: &'s mut Scope,
|
scope: &'s mut Scope,
|
||||||
mods: &'s mut Imports,
|
mods: &'s mut Imports,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &Module,
|
lib: &[&Module],
|
||||||
this_ptr: &'s mut Option<&mut Dynamic>,
|
this_ptr: &'s mut Option<&mut Dynamic>,
|
||||||
expr: &'a Expr,
|
expr: &'a Expr,
|
||||||
) -> Result<(Target<'s>, &'a str, ScopeEntryType, Position), Box<EvalAltResult>> {
|
) -> Result<(Target<'s>, &'a str, ScopeEntryType, Position), Box<EvalAltResult>> {
|
||||||
@ -792,7 +792,7 @@ impl Engine {
|
|||||||
scope: &'s mut Scope,
|
scope: &'s mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &Module,
|
lib: &[&Module],
|
||||||
this_ptr: &'s mut Option<&mut Dynamic>,
|
this_ptr: &'s mut Option<&mut Dynamic>,
|
||||||
expr: &'a Expr,
|
expr: &'a Expr,
|
||||||
) -> Result<(Target<'s>, &'a str, ScopeEntryType, Position), Box<EvalAltResult>> {
|
) -> Result<(Target<'s>, &'a str, ScopeEntryType, Position), Box<EvalAltResult>> {
|
||||||
@ -862,7 +862,7 @@ impl Engine {
|
|||||||
fn eval_dot_index_chain_helper(
|
fn eval_dot_index_chain_helper(
|
||||||
&self,
|
&self,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &Module,
|
lib: &[&Module],
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
target: &mut Target,
|
target: &mut Target,
|
||||||
rhs: &Expr,
|
rhs: &Expr,
|
||||||
@ -1155,7 +1155,7 @@ impl Engine {
|
|||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &Module,
|
lib: &[&Module],
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
level: usize,
|
level: usize,
|
||||||
@ -1234,7 +1234,7 @@ impl Engine {
|
|||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &Module,
|
lib: &[&Module],
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
chain_type: ChainType,
|
chain_type: ChainType,
|
||||||
@ -1305,7 +1305,7 @@ impl Engine {
|
|||||||
fn get_indexed_mut<'a>(
|
fn get_indexed_mut<'a>(
|
||||||
&self,
|
&self,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
_lib: &Module,
|
_lib: &[&Module],
|
||||||
target: &'a mut Target,
|
target: &'a mut Target,
|
||||||
idx: Dynamic,
|
idx: Dynamic,
|
||||||
idx_pos: Position,
|
idx_pos: Position,
|
||||||
@ -1414,7 +1414,7 @@ impl Engine {
|
|||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &Module,
|
lib: &[&Module],
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
lhs: &Expr,
|
lhs: &Expr,
|
||||||
rhs: &Expr,
|
rhs: &Expr,
|
||||||
@ -1477,7 +1477,7 @@ impl Engine {
|
|||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &Module,
|
lib: &[&Module],
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
level: usize,
|
level: usize,
|
||||||
@ -1786,7 +1786,7 @@ impl Engine {
|
|||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &Module,
|
lib: &[&Module],
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
stmt: &Stmt,
|
stmt: &Stmt,
|
||||||
level: usize,
|
level: usize,
|
||||||
@ -1932,6 +1932,63 @@ impl Engine {
|
|||||||
// Break statement
|
// Break statement
|
||||||
Stmt::Break(pos) => EvalAltResult::LoopBreak(true, *pos).into(),
|
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
|
// Return value
|
||||||
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => {
|
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => {
|
||||||
let expr = x.1.as_ref().unwrap();
|
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 => {
|
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Exception => {
|
||||||
let expr = x.1.as_ref().unwrap();
|
let expr = x.1.as_ref().unwrap();
|
||||||
let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||||
EvalAltResult::ErrorRuntime(
|
EvalAltResult::ErrorRuntime(val, (x.0).1).into()
|
||||||
val.take_string().unwrap_or_else(|_| "".into()),
|
|
||||||
(x.0).1,
|
|
||||||
)
|
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty throw
|
// Empty throw
|
||||||
Stmt::ReturnWithVal(x) if (x.0).0 == ReturnType::Exception => {
|
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!(),
|
Stmt::ReturnWithVal(_) => unreachable!(),
|
||||||
|
@ -104,7 +104,7 @@ pub enum ParseErrorType {
|
|||||||
///
|
///
|
||||||
/// Never appears under the `no_object` feature.
|
/// Never appears under the `no_object` feature.
|
||||||
PropertyExpected,
|
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,
|
VariableExpected,
|
||||||
/// An identifier is a reserved keyword.
|
/// An identifier is a reserved keyword.
|
||||||
Reserved(String),
|
Reserved(String),
|
||||||
|
@ -10,7 +10,7 @@ use crate::error::ParseErrorType;
|
|||||||
use crate::fn_native::{FnCallArgs, FnPtr};
|
use crate::fn_native::{FnCallArgs, FnPtr};
|
||||||
use crate::module::{Module, ModuleRef};
|
use crate::module::{Module, ModuleRef};
|
||||||
use crate::optimize::OptimizationLevel;
|
use crate::optimize::OptimizationLevel;
|
||||||
use crate::parser::{Expr, ImmutableString, AST, INT};
|
use crate::parser::{Expr, ImmutableString, Stmt, INT};
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::scope::Scope;
|
use crate::scope::Scope;
|
||||||
use crate::stdlib::ops::Deref;
|
use crate::stdlib::ops::Deref;
|
||||||
@ -180,7 +180,7 @@ impl Engine {
|
|||||||
pub(crate) fn call_native_fn(
|
pub(crate) fn call_native_fn(
|
||||||
&self,
|
&self,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &Module,
|
lib: &[&Module],
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
hash_fn: u64,
|
hash_fn: u64,
|
||||||
args: &mut FnCallArgs,
|
args: &mut FnCallArgs,
|
||||||
@ -345,7 +345,7 @@ impl Engine {
|
|||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &Module,
|
lib: &[&Module],
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
fn_def: &ScriptFnDef,
|
fn_def: &ScriptFnDef,
|
||||||
args: &mut FnCallArgs,
|
args: &mut FnCallArgs,
|
||||||
@ -382,17 +382,15 @@ impl Engine {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Merge in encapsulated environment, if any
|
// 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 {
|
let unified_lib = if let Some(ref env_lib) = fn_def.lib {
|
||||||
if lib.is_empty() {
|
lib_merged = Default::default();
|
||||||
// In the special case of the main script not defining any function
|
lib_merged.push(env_lib.as_ref());
|
||||||
env_lib
|
if !lib.is_empty() {
|
||||||
} else {
|
lib_merged.extend(lib.iter().cloned());
|
||||||
lib_merged = lib.clone();
|
|
||||||
lib_merged.merge(env_lib);
|
|
||||||
&lib_merged
|
|
||||||
}
|
}
|
||||||
|
lib_merged.as_ref()
|
||||||
} else {
|
} else {
|
||||||
lib
|
lib
|
||||||
};
|
};
|
||||||
@ -433,7 +431,7 @@ impl Engine {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn has_override_by_name_and_arguments(
|
pub(crate) fn has_override_by_name_and_arguments(
|
||||||
&self,
|
&self,
|
||||||
lib: &Module,
|
lib: &[&Module],
|
||||||
name: &str,
|
name: &str,
|
||||||
arg_types: impl AsRef<[TypeId]>,
|
arg_types: impl AsRef<[TypeId]>,
|
||||||
pub_only: bool,
|
pub_only: bool,
|
||||||
@ -456,7 +454,7 @@ impl Engine {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn has_override(
|
pub(crate) fn has_override(
|
||||||
&self,
|
&self,
|
||||||
lib: &Module,
|
lib: &[&Module],
|
||||||
hash_fn: u64,
|
hash_fn: u64,
|
||||||
hash_script: u64,
|
hash_script: u64,
|
||||||
pub_only: bool,
|
pub_only: bool,
|
||||||
@ -464,8 +462,8 @@ impl Engine {
|
|||||||
// NOTE: We skip script functions for global_module and packages, and native functions for lib
|
// NOTE: We skip script functions for global_module and packages, and native functions for lib
|
||||||
|
|
||||||
// First check script-defined functions
|
// First check script-defined functions
|
||||||
lib.contains_fn(hash_script, pub_only)
|
lib.iter().any(|&m| m.contains_fn(hash_script, pub_only))
|
||||||
//|| lib.contains_fn(hash_fn, pub_only)
|
//|| lib.iter().any(|&m| m.contains_fn(hash_fn, pub_only))
|
||||||
// Then check registered functions
|
// Then check registered functions
|
||||||
//|| self.global_module.contains_fn(hash_script, pub_only)
|
//|| self.global_module.contains_fn(hash_script, pub_only)
|
||||||
|| self.global_module.contains_fn(hash_fn, pub_only)
|
|| self.global_module.contains_fn(hash_fn, pub_only)
|
||||||
@ -485,7 +483,7 @@ impl Engine {
|
|||||||
pub(crate) fn exec_fn_call(
|
pub(crate) fn exec_fn_call(
|
||||||
&self,
|
&self,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &Module,
|
lib: &[&Module],
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
hash_script: u64,
|
hash_script: u64,
|
||||||
args: &mut FnCallArgs,
|
args: &mut FnCallArgs,
|
||||||
@ -535,7 +533,8 @@ impl Engine {
|
|||||||
format!(
|
format!(
|
||||||
"'{}' should not be called in method style. Try {}(...);",
|
"'{}' should not be called in method style. Try {}(...);",
|
||||||
fn_name, fn_name
|
fn_name, fn_name
|
||||||
),
|
)
|
||||||
|
.into(),
|
||||||
Position::none(),
|
Position::none(),
|
||||||
)
|
)
|
||||||
.into()
|
.into()
|
||||||
@ -543,13 +542,14 @@ impl Engine {
|
|||||||
|
|
||||||
// Script-like function found
|
// Script-like function found
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[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.global_module.contains_fn(hash_script, pub_only)
|
||||||
|| self.packages.contains_fn(hash_script, pub_only) =>
|
|| self.packages.contains_fn(hash_script, pub_only) =>
|
||||||
{
|
{
|
||||||
// Get function
|
// Get function
|
||||||
let func = lib
|
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.global_module.get_fn(hash_script, pub_only))
|
||||||
.or_else(|| self.packages.get_fn(hash_script, pub_only))
|
.or_else(|| self.packages.get_fn(hash_script, pub_only))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -557,8 +557,8 @@ impl Engine {
|
|||||||
if func.is_script() {
|
if func.is_script() {
|
||||||
let func = func.get_fn_def();
|
let func = func.get_fn_def();
|
||||||
|
|
||||||
let scope = &mut Scope::new();
|
let scope: &mut Scope = &mut Default::default();
|
||||||
let mods = &mut Imports::new();
|
let mods = &mut Default::default();
|
||||||
|
|
||||||
// Move captured variables into scope
|
// Move captured variables into scope
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[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<Item = &'a Stmt>,
|
||||||
|
lib: &[&Module],
|
||||||
|
) -> Result<(Dynamic, u64), Box<EvalAltResult>> {
|
||||||
|
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'.
|
/// Evaluate a text string as a script - used primarily for 'eval'.
|
||||||
/// Position in `EvalAltResult` is `None` and must be set afterwards.
|
/// Position in `EvalAltResult` is `None` and must be set afterwards.
|
||||||
fn eval_script_expr(
|
fn eval_script_expr(
|
||||||
@ -641,7 +665,7 @@ impl Engine {
|
|||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &Module,
|
lib: &[&Module],
|
||||||
script: &str,
|
script: &str,
|
||||||
_level: usize,
|
_level: usize,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
@ -658,8 +682,8 @@ impl Engine {
|
|||||||
|
|
||||||
// Compile the script text
|
// Compile the script text
|
||||||
// No optimizations because we only run it once
|
// No optimizations because we only run it once
|
||||||
let mut ast = self.compile_with_scope_and_optimization_level(
|
let ast = self.compile_with_scope_and_optimization_level(
|
||||||
&Scope::new(),
|
&Default::default(),
|
||||||
&[script],
|
&[script],
|
||||||
OptimizationLevel::None,
|
OptimizationLevel::None,
|
||||||
)?;
|
)?;
|
||||||
@ -669,11 +693,8 @@ impl Engine {
|
|||||||
return Err(ParseErrorType::WrongFnDefinition.into());
|
return Err(ParseErrorType::WrongFnDefinition.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let statements = mem::take(ast.statements_mut());
|
|
||||||
let ast = AST::new(statements, lib.clone());
|
|
||||||
|
|
||||||
// Evaluate the AST
|
// 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;
|
state.operations += operations;
|
||||||
self.inc_operations(state)?;
|
self.inc_operations(state)?;
|
||||||
@ -687,7 +708,7 @@ impl Engine {
|
|||||||
pub(crate) fn make_method_call(
|
pub(crate) fn make_method_call(
|
||||||
&self,
|
&self,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &Module,
|
lib: &[&Module],
|
||||||
name: &str,
|
name: &str,
|
||||||
hash_script: u64,
|
hash_script: u64,
|
||||||
target: &mut Target,
|
target: &mut Target,
|
||||||
@ -839,7 +860,7 @@ impl Engine {
|
|||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &Module,
|
lib: &[&Module],
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
name: &str,
|
name: &str,
|
||||||
args_expr: impl AsRef<[Expr]>,
|
args_expr: impl AsRef<[Expr]>,
|
||||||
@ -984,7 +1005,7 @@ impl Engine {
|
|||||||
return Ok(false.into());
|
return Ok(false.into());
|
||||||
} else {
|
} else {
|
||||||
let hash = calc_fn_hash(empty(), fn_name, num_params as usize, empty());
|
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,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &Module,
|
lib: &[&Module],
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
modules: &Option<Box<ModuleRef>>,
|
modules: &Option<Box<ModuleRef>>,
|
||||||
name: &str,
|
name: &str,
|
||||||
@ -1186,8 +1207,8 @@ impl Engine {
|
|||||||
let args = args.as_mut();
|
let args = args.as_mut();
|
||||||
let fn_def = f.get_fn_def();
|
let fn_def = f.get_fn_def();
|
||||||
|
|
||||||
let new_scope = &mut Scope::new();
|
let new_scope = &mut Default::default();
|
||||||
let mods = &mut Imports::new();
|
let mods = &mut Default::default();
|
||||||
|
|
||||||
self.call_script_fn(new_scope, mods, state, lib, &mut None, fn_def, args, level)
|
self.call_script_fn(new_scope, mods, state, lib, &mut None, fn_def, args, level)
|
||||||
}
|
}
|
||||||
|
@ -50,30 +50,32 @@ pub type Locked<T> = RwLock<T>;
|
|||||||
|
|
||||||
/// Context of native Rust function call.
|
/// Context of native Rust function call.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct NativeCallContext<'e, 'm> {
|
pub struct NativeCallContext<'e, 'm, 'pm: 'm> {
|
||||||
engine: &'e Engine,
|
engine: &'e Engine,
|
||||||
lib: &'m Module,
|
lib: &'m [&'pm Module],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'e, 'm> From<(&'e Engine, &'m Module)> for NativeCallContext<'e, 'm> {
|
impl<'e, 'm, 'pm: 'm, M: AsRef<[&'pm Module]> + ?Sized> From<(&'e Engine, &'m M)>
|
||||||
fn from(value: (&'e Engine, &'m Module)) -> Self {
|
for NativeCallContext<'e, 'm, 'pm>
|
||||||
|
{
|
||||||
|
fn from(value: (&'e Engine, &'m M)) -> Self {
|
||||||
Self {
|
Self {
|
||||||
engine: value.0,
|
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`.
|
/// The current `Engine`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn engine(&self) -> &'e Engine {
|
pub fn engine(&self) -> &'e Engine {
|
||||||
self.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)]
|
#[inline(always)]
|
||||||
pub fn namespace(&self) -> &'m Module {
|
pub fn iter_namespaces(&self) -> impl Iterator<Item = &'pm Module> + 'm {
|
||||||
self.lib
|
self.lib.iter().cloned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,7 +186,7 @@ impl FnPtr {
|
|||||||
.engine()
|
.engine()
|
||||||
.exec_fn_call(
|
.exec_fn_call(
|
||||||
&mut Default::default(),
|
&mut Default::default(),
|
||||||
context.namespace(),
|
context.lib,
|
||||||
fn_name,
|
fn_name,
|
||||||
hash_script,
|
hash_script,
|
||||||
args.as_mut(),
|
args.as_mut(),
|
||||||
|
@ -70,7 +70,7 @@ struct State<'a> {
|
|||||||
/// An `Engine` instance for eager function evaluation.
|
/// An `Engine` instance for eager function evaluation.
|
||||||
engine: &'a Engine,
|
engine: &'a Engine,
|
||||||
/// Library of script-defined functions.
|
/// Library of script-defined functions.
|
||||||
lib: &'a Module,
|
lib: &'a [&'a Module],
|
||||||
/// Optimization level.
|
/// Optimization level.
|
||||||
optimization_level: OptimizationLevel,
|
optimization_level: OptimizationLevel,
|
||||||
}
|
}
|
||||||
@ -78,7 +78,7 @@ struct State<'a> {
|
|||||||
impl<'a> State<'a> {
|
impl<'a> State<'a> {
|
||||||
/// Create a new State.
|
/// Create a new State.
|
||||||
#[inline(always)]
|
#[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 {
|
Self {
|
||||||
changed: false,
|
changed: false,
|
||||||
constants: vec![],
|
constants: vec![],
|
||||||
@ -268,7 +268,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
|||||||
))),
|
))),
|
||||||
// let id;
|
// let id;
|
||||||
stmt @ Stmt::Let(_) => stmt,
|
stmt @ Stmt::Let(_) => stmt,
|
||||||
// import expr as id;
|
// import expr as var;
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Import(x) => Stmt::Import(Box::new((optimize_expr(x.0, state), x.1, x.2))),
|
Stmt::Import(x) => Stmt::Import(Box::new((optimize_expr(x.0, state), x.1, x.2))),
|
||||||
// { block }
|
// { block }
|
||||||
@ -389,6 +389,22 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
|||||||
_ => Stmt::Block(Box::new((result.into(), pos))),
|
_ => 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;
|
// expr;
|
||||||
Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))),
|
Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))),
|
||||||
// return expr;
|
// 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)
|
// First search for script-defined functions (can override built-in)
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[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")]
|
#[cfg(feature = "no_function")]
|
||||||
let has_script_fn = false;
|
let has_script_fn = false;
|
||||||
|
|
||||||
@ -686,7 +702,7 @@ fn optimize(
|
|||||||
statements: Vec<Stmt>,
|
statements: Vec<Stmt>,
|
||||||
engine: &Engine,
|
engine: &Engine,
|
||||||
scope: &Scope,
|
scope: &Scope,
|
||||||
lib: &Module,
|
lib: &[&Module],
|
||||||
level: OptimizationLevel,
|
level: OptimizationLevel,
|
||||||
) -> Vec<Stmt> {
|
) -> Vec<Stmt> {
|
||||||
// If optimization level is None then skip optimizing
|
// If optimization level is None then skip optimizing
|
||||||
@ -837,7 +853,8 @@ pub fn optimize_into_ast(
|
|||||||
let pos = fn_def.body.position();
|
let pos = fn_def.body.position();
|
||||||
|
|
||||||
// Optimize the function body
|
// 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
|
// {} -> Noop
|
||||||
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
|
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
|
||||||
@ -877,7 +894,7 @@ pub fn optimize_into_ast(
|
|||||||
match level {
|
match level {
|
||||||
OptimizationLevel::None => statements,
|
OptimizationLevel::None => statements,
|
||||||
OptimizationLevel::Simple | OptimizationLevel::Full => {
|
OptimizationLevel::Simple | OptimizationLevel::Full => {
|
||||||
optimize(statements, engine, &scope, &lib, level)
|
optimize(statements, engine, &scope, &[&lib], level)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
lib,
|
lib,
|
||||||
|
@ -45,21 +45,31 @@ where
|
|||||||
Ok(StepRange::<T>(from, to, step))
|
Ok(StepRange::<T>(from, to, step))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! reg_range {
|
||||||
|
($lib:expr, $x:expr, $( $y:ty ),*) => (
|
||||||
|
$(
|
||||||
|
$lib.set_iterator::<Range<$y>>();
|
||||||
|
$lib.set_fn_2($x, get_range::<$y>);
|
||||||
|
)*
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! reg_step {
|
||||||
|
($lib:expr, $x:expr, $( $y:ty ),*) => (
|
||||||
|
$(
|
||||||
|
$lib.set_iterator::<StepRange<$y>>();
|
||||||
|
$lib.set_fn_3($x, get_step_range::<$y>);
|
||||||
|
)*
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
|
def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
|
||||||
lib.set_iterator::<Range<INT>>();
|
lib.set_iterator::<Range<INT>>();
|
||||||
|
|
||||||
lib.set_fn_2("range", get_range::<INT>);
|
lib.set_fn_2("range", get_range::<INT>);
|
||||||
|
|
||||||
if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
|
#[cfg(not(feature = "only_i32"))]
|
||||||
macro_rules! reg_range {
|
#[cfg(not(feature = "only_i64"))]
|
||||||
($lib:expr, $x:expr, $( $y:ty ),*) => (
|
{
|
||||||
$(
|
|
||||||
$lib.set_iterator::<Range<$y>>();
|
|
||||||
$lib.set_fn_2($x, get_range::<$y>);
|
|
||||||
)*
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
reg_range!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64);
|
reg_range!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64);
|
||||||
|
|
||||||
if cfg!(not(target_arch = "wasm32")) {
|
if cfg!(not(target_arch = "wasm32")) {
|
||||||
@ -70,16 +80,9 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
|
|||||||
lib.set_iterator::<StepRange<INT>>();
|
lib.set_iterator::<StepRange<INT>>();
|
||||||
lib.set_fn_3("range", get_step_range::<INT>);
|
lib.set_fn_3("range", get_step_range::<INT>);
|
||||||
|
|
||||||
if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
|
#[cfg(not(feature = "only_i32"))]
|
||||||
macro_rules! reg_step {
|
#[cfg(not(feature = "only_i64"))]
|
||||||
($lib:expr, $x:expr, $( $y:ty ),*) => (
|
{
|
||||||
$(
|
|
||||||
$lib.set_iterator::<StepRange<$y>>();
|
|
||||||
$lib.set_fn_3($x, get_step_range::<$y>);
|
|
||||||
)*
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
reg_step!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64);
|
reg_step!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64);
|
||||||
|
|
||||||
if cfg!(not(target_arch = "wasm32")) {
|
if cfg!(not(target_arch = "wasm32")) {
|
||||||
|
@ -45,6 +45,20 @@ macro_rules! reg_functions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
|
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"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
{
|
{
|
||||||
// Floating point functions
|
// 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 += 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<Dynamic, Box<EvalAltResult>> {
|
||||||
|
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::<Dynamic>::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<Dynamic, Box<EvalAltResult>> {
|
||||||
|
parse_int_radix(s, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
#[export_module]
|
#[export_module]
|
||||||
mod trig_functions {
|
mod trig_functions {
|
||||||
@ -199,7 +227,6 @@ mod float_functions {
|
|||||||
Ok((x.trunc() as INT).into())
|
Ok((x.trunc() as INT).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rhai_fn(return_raw)]
|
#[rhai_fn(return_raw)]
|
||||||
pub fn parse_float(s: &str) -> Result<Dynamic, Box<EvalAltResult>> {
|
pub fn parse_float(s: &str) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
s.trim()
|
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(feature = "only_i64"))]
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
gen_conversion_functions!(num_128_to_int => to_int (i128, u128) -> INT);
|
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<Dynamic, Box<EvalAltResult>> {
|
|
||||||
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::<Dynamic>::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<Dynamic, Box<EvalAltResult>> {
|
|
||||||
parse_int_radix(s, 10)
|
|
||||||
}
|
|
||||||
|
@ -47,16 +47,10 @@ macro_rules! reg_debug_functions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def_package!(crate:BasicStringPackage:"Basic string utilities, including printing.", lib, {
|
def_package!(crate:BasicStringPackage:"Basic string utilities, including printing.", lib, {
|
||||||
reg_print_functions!(lib += print_basic; INT, bool, char, FnPtr);
|
combine_with_exported_module!(lib, "print_debug", print_debug_functions);
|
||||||
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);
|
|
||||||
|
|
||||||
|
reg_print_functions!(lib += print_basic; INT, bool, char, FnPtr);
|
||||||
reg_debug_functions!(lib += debug_basic; INT, bool, Unit, char, ImmutableString);
|
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_i32"))]
|
||||||
#[cfg(not(feature = "only_i64"))]
|
#[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_print_functions!(lib += print_array; Array);
|
||||||
reg_debug_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<T: Display>(x: &mut T) -> ImmutableString {
|
||||||
|
x.to_string().into()
|
||||||
|
}
|
||||||
|
fn to_debug<T: Debug>(x: &mut T) -> ImmutableString {
|
||||||
|
format!("{:?}", x).into()
|
||||||
|
}
|
||||||
|
|
||||||
gen_functions!(print_basic => to_string(INT, bool, char, FnPtr));
|
gen_functions!(print_basic => to_string(INT, bool, char, FnPtr));
|
||||||
gen_functions!(debug_basic => to_debug(INT, bool, Unit, char, ImmutableString));
|
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));
|
gen_functions!(print_array => to_debug(Array));
|
||||||
|
|
||||||
// Register print and debug
|
// Register print and debug
|
||||||
#[export_fn]
|
#[export_module]
|
||||||
fn print_empty_string() -> ImmutableString {
|
mod print_debug_functions {
|
||||||
"".to_string().into()
|
#[rhai_fn(name = "print", name = "debug")]
|
||||||
}
|
pub fn print_empty_string() -> ImmutableString {
|
||||||
#[export_fn]
|
"".to_string().into()
|
||||||
fn print_unit(_x: ()) -> ImmutableString {
|
}
|
||||||
"".to_string().into()
|
#[rhai_fn(name = "print", name = "to_string")]
|
||||||
}
|
pub fn print_unit(_x: ()) -> ImmutableString {
|
||||||
#[export_fn]
|
"".to_string().into()
|
||||||
fn print_string(s: ImmutableString) -> ImmutableString {
|
}
|
||||||
s
|
#[rhai_fn(name = "print", name = "to_string")]
|
||||||
}
|
pub fn print_string(s: ImmutableString) -> ImmutableString {
|
||||||
#[export_fn]
|
s
|
||||||
fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString {
|
}
|
||||||
to_string(f)
|
#[rhai_fn(name = "debug")]
|
||||||
}
|
pub fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString {
|
||||||
fn to_string<T: Display>(x: &mut T) -> ImmutableString {
|
to_string(f)
|
||||||
x.to_string().into()
|
}
|
||||||
}
|
|
||||||
fn to_debug<T: Debug>(x: &mut T) -> ImmutableString {
|
|
||||||
format!("{:?}", x).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
mod format_map {
|
pub mod map_functions {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[export_fn]
|
#[rhai_fn(name = "print", name = "debug", name = "to_string")]
|
||||||
pub fn format_map(x: &mut Map) -> ImmutableString {
|
pub fn format_map(x: &mut Map) -> ImmutableString {
|
||||||
format!("#{:?}", x).into()
|
format!("#{:?}", x).into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -247,6 +247,7 @@ mod string_functions {
|
|||||||
pub fn replace_char(s: &mut ImmutableString, find: char, sub: char) {
|
pub fn replace_char(s: &mut ImmutableString, find: char, sub: char) {
|
||||||
*s = s.replace(&find.to_string(), &sub.to_string()).into();
|
*s = s.replace(&find.to_string(), &sub.to_string()).into();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rhai_fn(return_raw)]
|
#[rhai_fn(return_raw)]
|
||||||
pub fn pad(
|
pub fn pad(
|
||||||
_context: NativeCallContext,
|
_context: NativeCallContext,
|
||||||
@ -363,7 +364,6 @@ mod string_functions {
|
|||||||
pub fn prepend(x: &mut Array, y: &str) -> String {
|
pub fn prepend(x: &mut Array, y: &str) -> String {
|
||||||
format!("{:?}{}", x, y)
|
format!("{:?}{}", x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn split(s: &str, delimiter: ImmutableString) -> Array {
|
pub fn split(s: &str, delimiter: ImmutableString) -> Array {
|
||||||
s.split(delimiter.as_str())
|
s.split(delimiter.as_str())
|
||||||
.map(Into::<Dynamic>::into)
|
.map(Into::<Dynamic>::into)
|
||||||
|
128
src/parser.rs
128
src/parser.rs
@ -758,6 +758,14 @@ pub enum Stmt {
|
|||||||
Const(Box<((String, Position), Option<Expr>, Position)>),
|
Const(Box<((String, Position), Option<Expr>, Position)>),
|
||||||
/// { stmt; ... }
|
/// { stmt; ... }
|
||||||
Block(Box<(StaticVec<Stmt>, Position)>),
|
Block(Box<(StaticVec<Stmt>, Position)>),
|
||||||
|
/// try { stmt; ... } catch ( var ) { stmt; ... }
|
||||||
|
TryCatch(
|
||||||
|
Box<(
|
||||||
|
(Stmt, Position),
|
||||||
|
Option<(String, Position)>,
|
||||||
|
(Stmt, Position),
|
||||||
|
)>,
|
||||||
|
),
|
||||||
/// expr
|
/// expr
|
||||||
Expr(Box<Expr>),
|
Expr(Box<Expr>),
|
||||||
/// continue
|
/// continue
|
||||||
@ -766,10 +774,10 @@ pub enum Stmt {
|
|||||||
Break(Position),
|
Break(Position),
|
||||||
/// return/throw
|
/// return/throw
|
||||||
ReturnWithVal(Box<((ReturnType, Position), Option<Expr>, Position)>),
|
ReturnWithVal(Box<((ReturnType, Position), Option<Expr>, Position)>),
|
||||||
/// import expr as module
|
/// import expr as var
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Import(Box<(Expr, Option<(ImmutableString, Position)>, Position)>),
|
Import(Box<(Expr, Option<(ImmutableString, Position)>, Position)>),
|
||||||
/// expr id as name, ...
|
/// export var as var, ...
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Export(
|
Export(
|
||||||
Box<(
|
Box<(
|
||||||
@ -796,13 +804,14 @@ impl Stmt {
|
|||||||
Stmt::Noop(pos) | Stmt::Continue(pos) | Stmt::Break(pos) => *pos,
|
Stmt::Noop(pos) | Stmt::Continue(pos) | Stmt::Break(pos) => *pos,
|
||||||
Stmt::Let(x) => (x.0).1,
|
Stmt::Let(x) => (x.0).1,
|
||||||
Stmt::Const(x) => (x.0).1,
|
Stmt::Const(x) => (x.0).1,
|
||||||
Stmt::ReturnWithVal(x) => (x.0).1,
|
|
||||||
Stmt::Block(x) => x.1,
|
Stmt::Block(x) => x.1,
|
||||||
Stmt::IfThenElse(x) => x.3,
|
Stmt::IfThenElse(x) => x.3,
|
||||||
Stmt::Expr(x) => x.position(),
|
Stmt::Expr(x) => x.position(),
|
||||||
Stmt::While(x) => x.2,
|
Stmt::While(x) => x.2,
|
||||||
Stmt::Loop(x) => x.1,
|
Stmt::Loop(x) => x.1,
|
||||||
Stmt::For(x) => x.3,
|
Stmt::For(x) => x.3,
|
||||||
|
Stmt::ReturnWithVal(x) => (x.0).1,
|
||||||
|
Stmt::TryCatch(x) => (x.0).1,
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Import(x) => x.2,
|
Stmt::Import(x) => x.2,
|
||||||
@ -820,7 +829,6 @@ impl Stmt {
|
|||||||
Stmt::Noop(pos) | Stmt::Continue(pos) | Stmt::Break(pos) => *pos = new_pos,
|
Stmt::Noop(pos) | Stmt::Continue(pos) | Stmt::Break(pos) => *pos = new_pos,
|
||||||
Stmt::Let(x) => (x.0).1 = new_pos,
|
Stmt::Let(x) => (x.0).1 = new_pos,
|
||||||
Stmt::Const(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::Block(x) => x.1 = new_pos,
|
||||||
Stmt::IfThenElse(x) => x.3 = new_pos,
|
Stmt::IfThenElse(x) => x.3 = new_pos,
|
||||||
Stmt::Expr(x) => {
|
Stmt::Expr(x) => {
|
||||||
@ -829,6 +837,8 @@ impl Stmt {
|
|||||||
Stmt::While(x) => x.2 = new_pos,
|
Stmt::While(x) => x.2 = new_pos,
|
||||||
Stmt::Loop(x) => x.1 = new_pos,
|
Stmt::Loop(x) => x.1 = new_pos,
|
||||||
Stmt::For(x) => x.3 = 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"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Import(x) => x.2 = new_pos,
|
Stmt::Import(x) => x.2 = new_pos,
|
||||||
@ -849,7 +859,8 @@ impl Stmt {
|
|||||||
| Stmt::While(_)
|
| Stmt::While(_)
|
||||||
| Stmt::Loop(_)
|
| Stmt::Loop(_)
|
||||||
| Stmt::For(_)
|
| 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!
|
// A No-op requires a semicolon in order to know it is an empty statement!
|
||||||
Stmt::Noop(_) => false,
|
Stmt::Noop(_) => false,
|
||||||
@ -884,6 +895,7 @@ impl Stmt {
|
|||||||
Stmt::Let(_) | Stmt::Const(_) => false,
|
Stmt::Let(_) | Stmt::Const(_) => false,
|
||||||
Stmt::Block(x) => x.0.iter().all(Stmt::is_pure),
|
Stmt::Block(x) => x.0.iter().all(Stmt::is_pure),
|
||||||
Stmt::Continue(_) | Stmt::Break(_) | Stmt::ReturnWithVal(_) => false,
|
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"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Import(_) => false,
|
Stmt::Import(_) => false,
|
||||||
@ -1358,13 +1370,12 @@ fn eat_token(input: &mut TokenStream, token: Token) -> Position {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Match a particular token, consuming it if matched.
|
/// Match a particular token, consuming it if matched.
|
||||||
fn match_token(input: &mut TokenStream, token: Token) -> Result<bool, ParseError> {
|
fn match_token(input: &mut TokenStream, token: Token) -> (bool, Position) {
|
||||||
let (t, _) = input.peek().unwrap();
|
let (t, pos) = input.peek().unwrap();
|
||||||
if *t == token {
|
if *t == token {
|
||||||
eat_token(input, token);
|
(true, eat_token(input, token))
|
||||||
Ok(true)
|
|
||||||
} else {
|
} else {
|
||||||
Ok(false)
|
(false, *pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1378,7 +1389,7 @@ fn parse_paren_expr(
|
|||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
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));
|
return Ok(Expr::Unit(settings.pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1989,7 +2000,7 @@ fn parse_primary(
|
|||||||
// Qualified function call with !
|
// Qualified function call with !
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
(Expr::Variable(x), Token::Bang) if x.1.is_some() => {
|
(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)
|
LexError::UnexpectedInput(Token::Bang.syntax().to_string()).into_err(token_pos)
|
||||||
} else {
|
} else {
|
||||||
PERR::BadInput("'!' cannot be used to call module functions".to_string())
|
PERR::BadInput("'!' cannot be used to call module functions".to_string())
|
||||||
@ -1999,12 +2010,13 @@ fn parse_primary(
|
|||||||
// Function call with !
|
// Function call with !
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
(Expr::Variable(x), Token::Bang) => {
|
(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(
|
return Err(PERR::MissingToken(
|
||||||
Token::LeftParen.syntax().into(),
|
Token::LeftParen.syntax().into(),
|
||||||
"to start arguments list of function call".into(),
|
"to start arguments list of function call".into(),
|
||||||
)
|
)
|
||||||
.into_err(input.peek().unwrap().1));
|
.into_err(pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
let ((name, pos), modules, _, _) = *x;
|
let ((name, pos), modules, _, _) = *x;
|
||||||
@ -2813,7 +2825,7 @@ fn parse_if(
|
|||||||
let if_body = parse_block(input, state, lib, settings.level_up())?;
|
let if_body = parse_block(input, state, lib, settings.level_up())?;
|
||||||
|
|
||||||
// if guard { if_body } else ...
|
// 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() {
|
Some(if let (Token::If, _) = input.peek().unwrap() {
|
||||||
// if guard { if_body } else if ...
|
// if guard { if_body } else if ...
|
||||||
parse_if(input, state, lib, settings.level_up())?
|
parse_if(input, state, lib, settings.level_up())?
|
||||||
@ -2957,7 +2969,7 @@ fn parse_let(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// let name = ...
|
// let name = ...
|
||||||
let init_value = if match_token(input, Token::Equals)? {
|
let init_value = if match_token(input, Token::Equals).0 {
|
||||||
// let name = expr
|
// let name = expr
|
||||||
Some(parse_expr(input, state, lib, settings.level_up())?)
|
Some(parse_expr(input, state, lib, settings.level_up())?)
|
||||||
} else {
|
} else {
|
||||||
@ -2997,7 +3009,7 @@ fn parse_import(
|
|||||||
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
||||||
|
|
||||||
// import expr as ...
|
// 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))));
|
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)),
|
(_, 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() {
|
match input.next().unwrap() {
|
||||||
(Token::Identifier(s), pos) => Some((s.clone(), pos)),
|
(Token::Identifier(s), pos) => Some((s.clone(), pos)),
|
||||||
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||||
@ -3121,7 +3133,7 @@ fn parse_block(
|
|||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
let prev_mods_len = state.modules.len();
|
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
|
// Parse statements inside the block
|
||||||
settings.is_global = false;
|
settings.is_global = false;
|
||||||
|
|
||||||
@ -3297,30 +3309,31 @@ fn parse_stmt(
|
|||||||
match input.peek().unwrap() {
|
match input.peek().unwrap() {
|
||||||
// `return`/`throw` at <EOF>
|
// `return`/`throw` at <EOF>
|
||||||
(Token::EOF, pos) => Ok(Some(Stmt::ReturnWithVal(Box::new((
|
(Token::EOF, pos) => Ok(Some(Stmt::ReturnWithVal(Box::new((
|
||||||
(return_type, *pos),
|
(return_type, token_pos),
|
||||||
None,
|
None,
|
||||||
token_pos,
|
*pos,
|
||||||
))))),
|
))))),
|
||||||
// `return;` or `throw;`
|
// `return;` or `throw;`
|
||||||
(Token::SemiColon, _) => Ok(Some(Stmt::ReturnWithVal(Box::new((
|
(Token::SemiColon, _) => Ok(Some(Stmt::ReturnWithVal(Box::new((
|
||||||
(return_type, settings.pos),
|
(return_type, token_pos),
|
||||||
None,
|
None,
|
||||||
token_pos,
|
settings.pos,
|
||||||
))))),
|
))))),
|
||||||
// `return` or `throw` with expression
|
// `return` or `throw` with expression
|
||||||
(_, _) => {
|
(_, _) => {
|
||||||
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
||||||
let pos = expr.position();
|
let pos = expr.position();
|
||||||
|
|
||||||
Ok(Some(Stmt::ReturnWithVal(Box::new((
|
Ok(Some(Stmt::ReturnWithVal(Box::new((
|
||||||
(return_type, pos),
|
(return_type, token_pos),
|
||||||
Some(expr),
|
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::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),
|
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<Stmt, ParseError> {
|
||||||
|
// 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.
|
/// Parse a function definition.
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
fn parse_fn(
|
fn parse_fn(
|
||||||
@ -3365,7 +3437,7 @@ fn parse_fn(
|
|||||||
|
|
||||||
let mut params = Vec::new();
|
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);
|
let sep_err = format!("to separate the parameters of function '{}'", name);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@ -3515,7 +3587,7 @@ fn parse_anon_fn(
|
|||||||
let mut params = Vec::new();
|
let mut params = Vec::new();
|
||||||
|
|
||||||
if input.next().unwrap().0 != Token::Or {
|
if input.next().unwrap().0 != Token::Or {
|
||||||
if !match_token(input, Token::Pipe)? {
|
if !match_token(input, Token::Pipe).0 {
|
||||||
loop {
|
loop {
|
||||||
match input.next().unwrap() {
|
match input.next().unwrap() {
|
||||||
(Token::Pipe, _) => break,
|
(Token::Pipe, _) => break,
|
||||||
|
@ -4,6 +4,7 @@ use crate::any::Dynamic;
|
|||||||
use crate::error::ParseErrorType;
|
use crate::error::ParseErrorType;
|
||||||
use crate::parser::INT;
|
use crate::parser::INT;
|
||||||
use crate::token::Position;
|
use crate::token::Position;
|
||||||
|
use crate::utils::ImmutableString;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
use crate::engine::is_anonymous_fn;
|
use crate::engine::is_anonymous_fn;
|
||||||
@ -82,8 +83,8 @@ pub enum EvalAltResult {
|
|||||||
ErrorDataTooLarge(String, usize, usize, Position),
|
ErrorDataTooLarge(String, usize, usize, Position),
|
||||||
/// The script is prematurely terminated.
|
/// The script is prematurely terminated.
|
||||||
ErrorTerminated(Position),
|
ErrorTerminated(Position),
|
||||||
/// Run-time error encountered. Wrapped value is the error message.
|
/// Run-time error encountered. Wrapped value is the error.
|
||||||
ErrorRuntime(String, Position),
|
ErrorRuntime(Dynamic, Position),
|
||||||
|
|
||||||
/// Breaking out of loops - not an error if within a loop.
|
/// 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).
|
/// 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::ErrorStackOverflow(_)
|
||||||
| Self::ErrorTerminated(_) => f.write_str(desc)?,
|
| 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::<ImmutableString>() => {
|
||||||
|
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::ErrorAssignmentToConstant(s, _) => write!(f, "{}: '{}'", desc, s)?,
|
||||||
Self::ErrorMismatchOutputType(r, s, _) => {
|
Self::ErrorMismatchOutputType(r, s, _) => {
|
||||||
@ -248,7 +254,7 @@ impl fmt::Display for EvalAltResult {
|
|||||||
impl<T: AsRef<str>> From<T> for EvalAltResult {
|
impl<T: AsRef<str>> From<T> for EvalAltResult {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn from(err: T) -> Self {
|
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<T: AsRef<str>> From<T> for Box<EvalAltResult> {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn from(err: T) -> Self {
|
fn from(err: T) -> Self {
|
||||||
Box::new(EvalAltResult::ErrorRuntime(
|
Box::new(EvalAltResult::ErrorRuntime(
|
||||||
err.as_ref().to_string(),
|
err.as_ref().to_string().into(),
|
||||||
Position::none(),
|
Position::none(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EvalAltResult {
|
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.
|
/// Get the `Position` of this error.
|
||||||
pub fn position(&self) -> Position {
|
pub fn position(&self) -> Position {
|
||||||
match self {
|
match self {
|
||||||
|
@ -99,7 +99,7 @@ pub fn to_dynamic<T: Serialize>(value: T) -> Result<Dynamic, Box<EvalAltResult>>
|
|||||||
|
|
||||||
impl Error for Box<EvalAltResult> {
|
impl Error for Box<EvalAltResult> {
|
||||||
fn custom<T: fmt::Display>(err: T) -> Self {
|
fn custom<T: fmt::Display>(err: T) -> Self {
|
||||||
EvalAltResult::ErrorRuntime(err.to_string(), Position::none()).into()
|
EvalAltResult::ErrorRuntime(err.to_string().into(), Position::none()).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ impl Expression<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_> {
|
impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_, '_> {
|
||||||
/// Evaluate an expression tree.
|
/// Evaluate an expression tree.
|
||||||
///
|
///
|
||||||
/// ## WARNING - Low Level API
|
/// ## WARNING - Low Level API
|
||||||
@ -64,14 +64,14 @@ impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
expr: &Expression,
|
expr: &Expression,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
self.engine().eval_expr(
|
self.engine.eval_expr(
|
||||||
self.scope,
|
self.scope,
|
||||||
self.mods,
|
self.mods,
|
||||||
self.state,
|
self.state,
|
||||||
self.namespace(),
|
self.lib,
|
||||||
self.this_ptr,
|
self.this_ptr,
|
||||||
expr.expr(),
|
expr.expr(),
|
||||||
self.call_level(),
|
self.level,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
14
src/token.rs
14
src/token.rs
@ -282,6 +282,10 @@ pub enum Token {
|
|||||||
Return,
|
Return,
|
||||||
/// `throw`
|
/// `throw`
|
||||||
Throw,
|
Throw,
|
||||||
|
/// `try`
|
||||||
|
Try,
|
||||||
|
/// `catch`
|
||||||
|
Catch,
|
||||||
/// `+=`
|
/// `+=`
|
||||||
PlusAssign,
|
PlusAssign,
|
||||||
/// `-=`
|
/// `-=`
|
||||||
@ -397,6 +401,8 @@ impl Token {
|
|||||||
Break => "break",
|
Break => "break",
|
||||||
Return => "return",
|
Return => "return",
|
||||||
Throw => "throw",
|
Throw => "throw",
|
||||||
|
Try => "try",
|
||||||
|
Catch => "catch",
|
||||||
PlusAssign => "+=",
|
PlusAssign => "+=",
|
||||||
MinusAssign => "-=",
|
MinusAssign => "-=",
|
||||||
MultiplyAssign => "*=",
|
MultiplyAssign => "*=",
|
||||||
@ -479,6 +485,8 @@ impl Token {
|
|||||||
"break" => Break,
|
"break" => Break,
|
||||||
"return" => Return,
|
"return" => Return,
|
||||||
"throw" => Throw,
|
"throw" => Throw,
|
||||||
|
"try" => Try,
|
||||||
|
"catch" => Catch,
|
||||||
"+=" => PlusAssign,
|
"+=" => PlusAssign,
|
||||||
"-=" => MinusAssign,
|
"-=" => MinusAssign,
|
||||||
"*=" => MultiplyAssign,
|
"*=" => MultiplyAssign,
|
||||||
@ -516,9 +524,9 @@ impl Token {
|
|||||||
|
|
||||||
"===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public"
|
"===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public"
|
||||||
| "new" | "use" | "module" | "package" | "var" | "static" | "shared" | "with"
|
| "new" | "use" | "module" | "package" | "var" | "static" | "shared" | "with"
|
||||||
| "do" | "each" | "then" | "goto" | "exit" | "switch" | "match" | "case" | "try"
|
| "do" | "each" | "then" | "goto" | "exit" | "switch" | "match" | "case"
|
||||||
| "catch" | "default" | "void" | "null" | "nil" | "spawn" | "go" | "sync" | "async"
|
| "default" | "void" | "null" | "nil" | "spawn" | "go" | "sync" | "async" | "await"
|
||||||
| "await" | "yield" => Reserved(syntax.into()),
|
| "yield" => Reserved(syntax.into()),
|
||||||
|
|
||||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
||||||
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR
|
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR
|
||||||
|
@ -270,7 +270,7 @@ fn test_closures_external() -> Result<(), Box<EvalAltResult>> {
|
|||||||
ast.retain_functions(|_, _, _| true);
|
ast.retain_functions(|_, _, _| true);
|
||||||
|
|
||||||
// Closure 'f' captures: the engine, the AST, and the curried function pointer
|
// 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"));
|
assert_eq!(f(42)?.as_str(), Ok("hello42"));
|
||||||
|
|
||||||
|
@ -1,16 +1,41 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_throw() {
|
fn test_throw() {
|
||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
*engine.eval::<()>(r#"if true { throw "hello" }"#).expect_err("expects error"),
|
*engine.eval::<()>("if true { throw 42 }").expect_err("expects error"),
|
||||||
EvalAltResult::ErrorRuntime(s, _) if s == "hello"
|
EvalAltResult::ErrorRuntime(s, _) if s.as_int().unwrap() == 42
|
||||||
));
|
));
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
*engine.eval::<()>(r#"throw"#).expect_err("expects error"),
|
*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<EvalAltResult>> {
|
||||||
|
let engine = Engine::new();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>("try { throw 42; } catch (x) { return x; }")?,
|
||||||
|
42
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>("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(())
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user