diff --git a/RELEASES.md b/RELEASES.md index b09e10ce..6248f124 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -16,6 +16,11 @@ New features * `x.call(f, ...)` allows binding `x` to `this` for the function referenced by the function pointer `f`. * Anonymous functions in the syntax of a closure, e.g. `|x, y, z| x + y - z`. +Breaking changes +---------------- + +* Function signature for defining custom syntax is simplified. + Version 0.17.0 ============== diff --git a/doc/src/about/non-design.md b/doc/src/about/non-design.md index da3747df..52888164 100644 --- a/doc/src/about/non-design.md +++ b/doc/src/about/non-design.md @@ -25,12 +25,12 @@ It doesn't attempt to be a new language. For example: * No closures - do your closure magic in Rust instead; [turn a Rhai scripted function into a Rust closure]({{rootUrl}}/engine/call-fn.md). * No byte-codes/JIT - Rhai has an AST-walking interpreter which will not win any speed races. The purpose of Rhai is not - to be extremely _fast_, but to make it as easy as possible to integrate with native Rust programs. + to be extremely _fast_, but to make it as easy as possible to integrate with native Rust applications. Due to this intended usage, Rhai deliberately keeps the language simple and small by omitting advanced language features such as classes, inheritance, first-class functions, closures, concurrency, byte-codes, JIT etc. -Avoid the temptation to write full-fledge program logic entirely in Rhai - that use case is best fulfilled by +Avoid the temptation to write full-fledge application logic entirely in Rhai - that use case is best fulfilled by more complete languages such as JavaScript or Lua. Therefore, in actual practice, it is usually best to expose a Rust API into Rhai for scripts to call. diff --git a/doc/src/engine/custom-syntax.md b/doc/src/engine/custom-syntax.md index 0bd9fb30..f97e4203 100644 --- a/doc/src/engine/custom-syntax.md +++ b/doc/src/engine/custom-syntax.md @@ -126,32 +126,21 @@ Any custom syntax must include an _implementation_ of it. The function signature of an implementation is: ```rust -Fn( - engine: &Engine, - scope: &mut Scope, - mods: &mut Imports, - state: &mut State, - lib: &Module, - this_ptr: &mut Option<&mut Dynamic>, - inputs: &[Expression], - level: usize -) -> Result> +Fn(engine: &Engine, context: &mut EvalContext, scope: &mut Scope, inputs: &[Expression]) + -> Result> ``` where: -* `engine : &Engine` - reference to the current [`Engine`]. -* `scope : &mut Scope` - mutable reference to the current [`Scope`]; variables can be added to it. -* `mods : &mut Imports` - mutable reference to the current collection of imported [`Module`]'s; **do not touch**. -* `state : &mut State` - mutable reference to the current evaluation state; **do not touch**. -* `lib : &Module` - reference to the current collection of script-defined functions. -* `this_ptr : &mut Option<&mut Dynamic>` - mutable reference to the current binding of the `this` pointer; **do not touch**. -* `inputs : &[Expression]` - a list of input expression trees. -* `level : usize` - the current function call level. +* `engine: &Engine` - reference to the current [`Engine`]. +* `context: &mut EvalContext` - mutable reference to the current evaluation _context_; **do not touch**. +* `scope: &mut Scope` - mutable reference to the current [`Scope`]; variables can be added to it. +* `inputs: &[Expression]` - a list of input expression trees. -There are a lot of parameters, most of which should not be touched or Bad Things Happen™. -They represent the running _content_ of a script evaluation and should simply be passed -straight-through the the [`Engine`]. +#### WARNING - Lark's Vomit + +The `context` parameter contains the evaluation _context_ and should not be touched or Bad Things Happen™. +It should simply be passed straight-through the the [`Engine`]. ### Access Arguments @@ -172,7 +161,7 @@ Use the `engine::eval_expression_tree` method to evaluate an expression tree. ```rust let expr = inputs.get(0).unwrap(); -let result = engine.eval_expression_tree(scope, mods, state, lib, this_ptr, expr, level)?; +let result = engine.eval_expression_tree(context, scope, expr)?; ``` As can be seem above, most arguments are simply passed straight-through to `engine::eval_expression_tree`. @@ -210,13 +199,9 @@ The syntax is passed simply as a slice of `&str`. // Custom syntax implementation fn implementation_func( engine: &Engine, + context: &mut EvalContext, scope: &mut Scope, - mods: &mut Imports, - state: &mut State, - lib: &Module, - this_ptr: &mut Option<&mut Dynamic>, - inputs: &[Expression], - level: usize + inputs: &[Expression] ) -> Result> { let var_name = inputs[0].get_variable_name().unwrap().to_string(); let stmt = inputs.get(1).unwrap(); @@ -227,15 +212,12 @@ fn implementation_func( loop { // Evaluate the statement block - engine.eval_expression_tree(scope, mods, state, lib, this_ptr, stmt, level)?; + engine.eval_expression_tree(context, scope, stmt)?; // Evaluate the condition expression - let stop = !engine - .eval_expression_tree(scope, mods, state, lib, this_ptr, condition, level)? - .as_bool() - .map_err(|_| EvalAltResult::ErrorBooleanArgMismatch( - "do-while".into(), expr.position() - ))?; + let stop = !engine.eval_expression_tree(context, scope, condition)? + .as_bool().map_err(|_| EvalAltResult::ErrorBooleanArgMismatch( + "do-while".into(), expr.position()))?; if stop { break; diff --git a/doc/src/engine/raw.md b/doc/src/engine/raw.md index ed7154ed..ce61aa91 100644 --- a/doc/src/engine/raw.md +++ b/doc/src/engine/raw.md @@ -7,7 +7,7 @@ Raw `Engine` `Engine::new` creates a scripting [`Engine`] with common functionalities (e.g. printing to the console via `print`). In many controlled embedded environments, however, these may not be needed and unnecessarily occupy -program code storage space. +application code storage space. Use `Engine::new_raw` to create a _raw_ `Engine`, in which only a minimal set of basic arithmetic and logical operators are supported. diff --git a/doc/src/start/builds/no-std.md b/doc/src/start/builds/no-std.md index 46a8e986..75395ed6 100644 --- a/doc/src/start/builds/no-std.md +++ b/doc/src/start/builds/no-std.md @@ -13,3 +13,9 @@ Nightly Required ---------------- Currently, [`no_std`] requires the nightly compiler due to the crates that it uses. + + +Samples +------- + +Check out the [`no-std` sample applications](../examples/rust.md#no-std-samples) for different operating environments. diff --git a/doc/src/start/examples/rust.md b/doc/src/start/examples/rust.md index 3784778d..276b3d15 100644 --- a/doc/src/start/examples/rust.md +++ b/doc/src/start/examples/rust.md @@ -3,7 +3,7 @@ Rust Examples {{#include ../../links.md}} -A number of examples can be found in the `examples` folder: +A number of examples can be found in the `examples` directory: | Example | Description | | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | @@ -29,3 +29,22 @@ Examples can be run with the following command: ```bash cargo run --example {example_name} ``` + +`no-std` Samples +---------------- + +To illustrate `no-std` builds, a number of sample applications are available under the `no_std` directory: + +| Sample | Description | Optimization | Allocator | Panics | +| --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | :----------: | :-----------------------------------------------: | :----: | +| [`no_std_test`](https://github.com/jonathandturner/rhai/tree/master/no_std/no_std_test) | Bare-bones test application that evaluates a Rhai expression and sets the result as the return value. | Size | [`wee_alloc`](https://crates.io/crates/wee_alloc) | Abort | + +`cargo run` cannot be used to run a `no-std` sample. It must first be built: + +```bash +cd no_std/no_std_test + +cargo +nightly build --release + +./target/release/no_std_test +``` diff --git a/doc/src/start/examples/scripts.md b/doc/src/start/examples/scripts.md index 20836aef..0875f869 100644 --- a/doc/src/start/examples/scripts.md +++ b/doc/src/start/examples/scripts.md @@ -6,27 +6,27 @@ Example Scripts Language Feature Scripts ----------------------- -There are also a number of examples scripts that showcase Rhai's features, all in the `scripts` folder: +There are also a number of examples scripts that showcase Rhai's features, all in the `scripts` directory: -| Script | Description | -| -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | -| [`array.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/array.rhai) | [Arrays] | -| [`assignment.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/assignment.rhai) | Variable declarations | -| [`comments.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/comments.rhai) | Just comments | -| [`for1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/for1.rhai) | [`for`](#for-loop) loops | -| [`for2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/for2.rhai) | [`for`](#for-loop) loops on [arrays] | -| [`function_decl1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl1.rhai) | A [function] without parameters | -| [`function_decl2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl2.rhai) | A [function] with two parameters | -| [`function_decl3.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl3.rhai) | A [function] with many parameters | -| [`if1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/if1.rhai) | [`if`](#if-statement) example | -| [`loop.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/loop.rhai) | Count-down [`loop`](#infinite-loop) in Rhai, emulating a `do` .. `while` loop | -| [`oop.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/oop.rhai) | Simulate [object-oriented programming (OOP)][OOP] | -| [`op1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op1.rhai) | Just simple addition | -| [`op2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op2.rhai) | Simple addition and multiplication | -| [`op3.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op3.rhai) | Change evaluation order with parenthesis | -| [`string.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/string.rhai) | [String] operations | -| [`strings_map.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/strings_map.rhai) | [String] and [object map] operations | -| [`while.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/while.rhai) | [`while`](#while-loop) loop | +| Script | Description | +| -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | +| [`array.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/array.rhai) | [Arrays] | +| [`assignment.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/assignment.rhai) | Variable declarations | +| [`comments.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/comments.rhai) | Just comments | +| [`for1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/for1.rhai) | [`for`]({{rootUrl}}/language/for.md) loops | +| [`for2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/for2.rhai) | [`for`]({{rootUrl}}/language/for.md) loops on [arrays] | +| [`function_decl1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl1.rhai) | A [function] without parameters | +| [`function_decl2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl2.rhai) | A [function] with two parameters | +| [`function_decl3.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl3.rhai) | A [function] with many parameters | +| [`if1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/if1.rhai) | [`if`]({{rootUrl}}/language/if.md) example | +| [`loop.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/loop.rhai) | Count-down [`loop`]({{rootUrl}}/language/loop.md) in Rhai, emulating a `do` .. `while` loop | +| [`oop.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/oop.rhai) | Simulate [object-oriented programming (OOP)][OOP] | +| [`op1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op1.rhai) | Just simple addition | +| [`op2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op2.rhai) | Simple addition and multiplication | +| [`op3.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op3.rhai) | Change evaluation order with parenthesis | +| [`string.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/string.rhai) | [String] operations | +| [`strings_map.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/strings_map.rhai) | [String] and [object map] operations | +| [`while.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/while.rhai) | [`while`]({{rootUrl}}/language/while.md) loop | Benchmark Scripts @@ -34,18 +34,18 @@ Benchmark Scripts The following scripts are for benchmarking the speed of Rhai: -| Scripts | Description | -| ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- | -| [`speed_test.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/speed_test.rhai) | A simple program to measure the speed of Rhai's interpreter (1 million iterations). | -| [`primes.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/primes.rhai) | Use Sieve of Eratosthenes to find all primes smaller than a limit. | -| [`fibonacci.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/fibonacci.rhai) | Calculate the n-th Fibonacci number using a really dumb algorithm. | -| [`mat_mul.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/mat_mul.rhai) | Matrix multiplication test to measure the speed of multi-dimensional array access. | +| Scripts | Description | +| ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- | +| [`speed_test.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/speed_test.rhai) | A simple application to measure the speed of Rhai's interpreter (1 million iterations). | +| [`primes.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/primes.rhai) | Use Sieve of Eratosthenes to find all primes smaller than a limit. | +| [`fibonacci.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/fibonacci.rhai) | Calculate the n-th Fibonacci number using a really dumb algorithm. | +| [`mat_mul.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/mat_mul.rhai) | Matrix multiplication test to measure the speed of multi-dimensional array access. | Running Example Scripts ---------------------- -To run the scripts, either make a tiny program or use of the `rhai_runner` example: +The [`rhai_runner`](../examples/rust.md) example can be used to run the scripts: ```bash cargo run --example rhai_runner scripts/any_script.rhai diff --git a/no_std/no_std_test/Cargo.toml b/no_std/no_std_test/Cargo.toml new file mode 100644 index 00000000..4b151047 --- /dev/null +++ b/no_std/no_std_test/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "no_std_test" +version = "0.1.0" +edition = "2018" +authors = ["Stephen Chung"] +description = "no-std test application" +homepage = "https://github.com/jonathandturner/rhai/tree/master/no_std/no_std_test" +repository = "https://github.com/jonathandturner/rhai" + +[dependencies] +rhai = { path = "../../", features = [ "no_std", "unchecked", "only_i32", "no_module" ], default_features = false } +wee_alloc = { version = "0.4.5", default_features = false } + +[profile.dev] +panic = "abort" + +[profile.release] +opt-level = "z" # optimize for size +debug = false +rpath = false +lto = "fat" +debug-assertions = false +codegen-units = 1 +panic = "abort" diff --git a/no_std/no_std_test/README.md b/no_std/no_std_test/README.md new file mode 100644 index 00000000..e7edfc51 --- /dev/null +++ b/no_std/no_std_test/README.md @@ -0,0 +1,18 @@ +`no-std` Test Sample +==================== + +This sample application is a bare-bones `no-std` build for testing. + +[`wee_alloc`](https://crates.io/crates/wee_alloc) is used as the allocator. + + +To Compile +---------- + +The nightly compiler is required: + +```bash +cargo +nightly build --release +``` + +The release build is optimized for size. It can be changed to optimize on speed instead. diff --git a/no_std/no_std_test/src/main.rs b/no_std/no_std_test/src/main.rs new file mode 100644 index 00000000..829802f8 --- /dev/null +++ b/no_std/no_std_test/src/main.rs @@ -0,0 +1,43 @@ +//! This is a bare-bones `no-std` application that evaluates +//! a simple expression and uses the result as the return value. + +#![no_std] +#![feature(alloc_error_handler, start, core_intrinsics, lang_items)] + +extern crate alloc; +extern crate wee_alloc; + +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; + +use rhai::{Engine, INT}; + +#[start] +fn main(_argc: isize, _argv: *const *const u8) -> isize { + // Notice that this is a _raw_ engine. + // To do anything useful, load a few packages from `rhai::packages`. + let engine = Engine::new_raw(); + + // Evaluate a simple expression: 40 + 2 + engine.eval_expression::("40 + 2").unwrap() as isize +} + +#[alloc_error_handler] +fn foo(_: core::alloc::Layout) -> ! { + core::intrinsics::abort(); +} + +#[panic_handler] +#[lang = "panic_impl"] +extern "C" fn rust_begin_panic(_: &core::panic::PanicInfo) -> ! { + core::intrinsics::abort(); +} + +#[lang = "eh_personality"] +extern "C" fn eh_personality() {} + +#[no_mangle] +extern "C" fn rust_eh_register_frames() {} + +#[no_mangle] +extern "C" fn rust_eh_unregister_frames() {} diff --git a/src/any.rs b/src/any.rs index 8ff20298..897bc922 100644 --- a/src/any.rs +++ b/src/any.rs @@ -203,6 +203,7 @@ impl Dynamic { } /// Map the name of a standard type into a friendly form. +#[inline] pub(crate) fn map_std_type_name(name: &str) -> &str { if name == type_name::() { "string" @@ -340,30 +341,47 @@ impl Dynamic { /// assert_eq!(new_result.type_name(), "string"); /// assert_eq!(new_result.to_string(), "hello"); /// ``` + #[inline(always)] pub fn from(value: T) -> Self { - if let Some(result) = ::downcast_ref::<()>(&value) { - return result.clone().into(); - } else if let Some(result) = ::downcast_ref::(&value) { - return result.clone().into(); - } else if let Some(result) = ::downcast_ref::(&value) { - return result.clone().into(); - } else if let Some(result) = ::downcast_ref::(&value) { - return result.clone().into(); - } else if let Some(result) = ::downcast_ref::(&value) { - return result.clone().into(); - } + let type_id = TypeId::of::(); + if type_id == TypeId::of::() { + return ::downcast_ref::(&value) + .unwrap() + .clone() + .into(); + } #[cfg(not(feature = "no_float"))] - if let Some(result) = ::downcast_ref::(&value) { - return result.clone().into(); + if type_id == TypeId::of::() { + return ::downcast_ref::(&value) + .unwrap() + .clone() + .into(); + } + if type_id == TypeId::of::() { + return ::downcast_ref::(&value) + .unwrap() + .clone() + .into(); + } + if type_id == TypeId::of::() { + return ::downcast_ref::(&value) + .unwrap() + .clone() + .into(); + } + if type_id == TypeId::of::() { + return ::downcast_ref::(&value) + .unwrap() + .clone() + .into(); + } + if type_id == TypeId::of::<()>() { + return ().into(); } let mut boxed = Box::new(value); - boxed = match unsafe_cast_box::<_, Dynamic>(boxed) { - Ok(d) => return *d, - Err(val) => val, - }; boxed = match unsafe_cast_box::<_, String>(boxed) { Ok(s) => return (*s).into(), Err(val) => val, @@ -384,6 +402,11 @@ impl Dynamic { } } + boxed = match unsafe_cast_box::<_, Dynamic>(boxed) { + Ok(d) => return *d, + Err(val) => val, + }; + Self(Union::Variant(Box::new(boxed))) } @@ -401,30 +424,80 @@ impl Dynamic { /// /// assert_eq!(x.try_cast::().unwrap(), 42); /// ``` + #[inline(always)] pub fn try_cast(self) -> Option { let type_id = TypeId::of::(); + if type_id == TypeId::of::() { + return match self.0 { + Union::Int(value) => unsafe_try_cast(value), + _ => None, + }; + } + #[cfg(not(feature = "no_float"))] + if type_id == TypeId::of::() { + return match self.0 { + Union::Float(value) => unsafe_try_cast(value), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match self.0 { + Union::Bool(value) => unsafe_try_cast(value), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match self.0 { + Union::Str(value) => unsafe_try_cast(value), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match self.0 { + Union::Str(value) => unsafe_try_cast(value.into_owned()), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match self.0 { + Union::Char(value) => unsafe_try_cast(value), + _ => None, + }; + } + #[cfg(not(feature = "no_index"))] + if type_id == TypeId::of::() { + return match self.0 { + Union::Array(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), + _ => None, + }; + } + #[cfg(not(feature = "no_object"))] + if type_id == TypeId::of::() { + return match self.0 { + Union::Map(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match self.0 { + Union::FnPtr(value) => unsafe_try_cast(value), + _ => None, + }; + } + if type_id == TypeId::of::<()>() { + return match self.0 { + Union::Unit(value) => unsafe_try_cast(value), + _ => None, + }; + } if type_id == TypeId::of::() { return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v); } match self.0 { - Union::Unit(value) => unsafe_try_cast(value), - Union::Bool(value) => unsafe_try_cast(value), - Union::Str(value) if type_id == TypeId::of::() => { - unsafe_try_cast(value) - } - Union::Str(value) => unsafe_try_cast(value.into_owned()), - Union::Char(value) => unsafe_try_cast(value), - Union::Int(value) => unsafe_try_cast(value), - #[cfg(not(feature = "no_float"))] - Union::Float(value) => unsafe_try_cast(value), - #[cfg(not(feature = "no_index"))] - Union::Array(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), - #[cfg(not(feature = "no_object"))] - Union::Map(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), - Union::FnPtr(value) => unsafe_try_cast(value), Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(), + _ => None, } } @@ -444,81 +517,162 @@ impl Dynamic { /// /// assert_eq!(x.cast::(), 42); /// ``` + #[inline(always)] pub fn cast(self) -> T { - let type_id = TypeId::of::(); - - if type_id == TypeId::of::() { - return *unsafe_cast_box::<_, T>(Box::new(self)).unwrap(); - } - - match self.0 { - Union::Unit(value) => unsafe_try_cast(value).unwrap(), - Union::Bool(value) => unsafe_try_cast(value).unwrap(), - Union::Str(value) if type_id == TypeId::of::() => { - unsafe_try_cast(value).unwrap() - } - Union::Str(value) => unsafe_try_cast(value.into_owned()).unwrap(), - Union::Char(value) => unsafe_try_cast(value).unwrap(), - Union::Int(value) => unsafe_try_cast(value).unwrap(), - #[cfg(not(feature = "no_float"))] - Union::Float(value) => unsafe_try_cast(value).unwrap(), - #[cfg(not(feature = "no_index"))] - Union::Array(value) => *unsafe_cast_box::<_, T>(value).unwrap(), - #[cfg(not(feature = "no_object"))] - Union::Map(value) => *unsafe_cast_box::<_, T>(value).unwrap(), - Union::FnPtr(value) => unsafe_try_cast(value).unwrap(), - Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).unwrap(), - } + self.try_cast::().unwrap() } /// Get a reference of a specific type to the `Dynamic`. /// Casting to `Dynamic` just returns a reference to it. /// Returns `None` if the cast fails. + #[inline(always)] pub fn downcast_ref(&self) -> Option<&T> { - if TypeId::of::() == TypeId::of::() { + let type_id = TypeId::of::(); + + if type_id == TypeId::of::() { + return match &self.0 { + Union::Int(value) => ::downcast_ref::(value), + _ => None, + }; + } + #[cfg(not(feature = "no_float"))] + if type_id == TypeId::of::() { + return match &self.0 { + Union::Float(value) => ::downcast_ref::(value), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match &self.0 { + Union::Bool(value) => ::downcast_ref::(value), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match &self.0 { + Union::Str(value) => ::downcast_ref::(value), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match &self.0 { + Union::Str(value) => ::downcast_ref::(value.as_ref()), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match &self.0 { + Union::Char(value) => ::downcast_ref::(value), + _ => None, + }; + } + #[cfg(not(feature = "no_index"))] + if type_id == TypeId::of::() { + return match &self.0 { + Union::Array(value) => ::downcast_ref::(value.as_ref()), + _ => None, + }; + } + #[cfg(not(feature = "no_object"))] + if type_id == TypeId::of::() { + return match &self.0 { + Union::Map(value) => ::downcast_ref::(value.as_ref()), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match &self.0 { + Union::FnPtr(value) => ::downcast_ref::(value), + _ => None, + }; + } + if type_id == TypeId::of::<()>() { + return match &self.0 { + Union::Unit(value) => ::downcast_ref::(value), + _ => None, + }; + } + if type_id == TypeId::of::() { return ::downcast_ref::(self); } match &self.0 { - Union::Unit(value) => ::downcast_ref::(value), - Union::Bool(value) => ::downcast_ref::(value), - Union::Str(value) => ::downcast_ref::(value) - .or_else(|| ::downcast_ref::(value.as_ref())), - Union::Char(value) => ::downcast_ref::(value), - Union::Int(value) => ::downcast_ref::(value), - #[cfg(not(feature = "no_float"))] - Union::Float(value) => ::downcast_ref::(value), - #[cfg(not(feature = "no_index"))] - Union::Array(value) => ::downcast_ref::(value.as_ref()), - #[cfg(not(feature = "no_object"))] - Union::Map(value) => ::downcast_ref::(value.as_ref()), - Union::FnPtr(value) => ::downcast_ref::(value), Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::(), + _ => None, } } /// Get a mutable reference of a specific type to the `Dynamic`. /// Casting to `Dynamic` just returns a mutable reference to it. /// Returns `None` if the cast fails. + #[inline(always)] pub fn downcast_mut(&mut self) -> Option<&mut T> { - if TypeId::of::() == TypeId::of::() { + let type_id = TypeId::of::(); + + if type_id == TypeId::of::() { + return match &mut self.0 { + Union::Int(value) => ::downcast_mut::(value), + _ => None, + }; + } + #[cfg(not(feature = "no_float"))] + if type_id == TypeId::of::() { + return match &mut self.0 { + Union::Float(value) => ::downcast_mut::(value), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match &mut self.0 { + Union::Bool(value) => ::downcast_mut::(value), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match &mut self.0 { + Union::Str(value) => ::downcast_mut::(value), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match &mut self.0 { + Union::Char(value) => ::downcast_mut::(value), + _ => None, + }; + } + #[cfg(not(feature = "no_index"))] + if type_id == TypeId::of::() { + return match &mut self.0 { + Union::Array(value) => ::downcast_mut::(value.as_mut()), + _ => None, + }; + } + #[cfg(not(feature = "no_object"))] + if type_id == TypeId::of::() { + return match &mut self.0 { + Union::Map(value) => ::downcast_mut::(value.as_mut()), + _ => None, + }; + } + if type_id == TypeId::of::() { + return match &mut self.0 { + Union::FnPtr(value) => ::downcast_mut::(value), + _ => None, + }; + } + if type_id == TypeId::of::<()>() { + return match &mut self.0 { + Union::Unit(value) => ::downcast_mut::(value), + _ => None, + }; + } + if type_id == TypeId::of::() { return ::downcast_mut::(self); } match &mut self.0 { - Union::Unit(value) => ::downcast_mut::(value), - Union::Bool(value) => ::downcast_mut::(value), - Union::Str(value) => ::downcast_mut::(value), - Union::Char(value) => ::downcast_mut::(value), - Union::Int(value) => ::downcast_mut::(value), - #[cfg(not(feature = "no_float"))] - Union::Float(value) => ::downcast_mut::(value), - #[cfg(not(feature = "no_index"))] - Union::Array(value) => ::downcast_mut::(value.as_mut()), - #[cfg(not(feature = "no_object"))] - Union::Map(value) => ::downcast_mut::(value.as_mut()), - Union::FnPtr(value) => ::downcast_mut::(value), Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::(), + _ => None, } } diff --git a/src/engine.rs b/src/engine.rs index a6b87770..18e01781 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -18,7 +18,7 @@ use crate::utils::StaticVec; use crate::parser::FLOAT; #[cfg(feature = "internals")] -use crate::syntax::CustomSyntax; +use crate::syntax::{CustomSyntax, EvalContext}; use crate::stdlib::{ any::{type_name, TypeId}, @@ -1738,15 +1738,19 @@ impl Engine { #[deprecated(note = "this method is volatile and may change")] pub fn eval_expression_tree( &self, + context: &mut EvalContext, scope: &mut Scope, - mods: &mut Imports, - state: &mut State, - lib: &Module, - this_ptr: &mut Option<&mut Dynamic>, expr: &Expression, - level: usize, ) -> Result> { - self.eval_expr(scope, mods, state, lib, this_ptr, expr.expr(), level) + self.eval_expr( + scope, + context.mods, + context.state, + context.lib, + context.this_ptr, + expr.expr(), + context.level, + ) } /// Evaluate an expression @@ -1773,8 +1777,8 @@ impl Engine { Expr::CharConstant(x) => Ok(x.0.into()), Expr::FnPointer(x) => Ok(FnPtr::new_unchecked(x.0.clone()).into()), Expr::Variable(x) if (x.0).0 == KEYWORD_THIS => { - if let Some(ref val) = this_ptr { - Ok((*val).clone()) + if let Some(val) = this_ptr { + Ok(val.clone()) } else { Err(Box::new(EvalAltResult::ErrorUnboundedThis((x.0).1))) } @@ -1829,15 +1833,16 @@ impl Engine { // Not built in, map to `var = var op rhs` let op = &op[..op.len() - 1]; // extract operator without = let hash = calc_fn_hash(empty(), op, 2, empty()); + // Clone the LHS value let args = &mut [&mut lhs_ptr.clone(), &mut rhs_val]; - - // Set variable value - *lhs_ptr = self + // Run function + let (value, _) = self .exec_fn_call( state, lib, op, true, hash, args, false, false, None, level, ) - .map(|(v, _)| v) .map_err(|err| err.new_position(*op_pos))?; + // Set value to LHS + *lhs_ptr = value; } Ok(Default::default()) } @@ -2215,7 +2220,14 @@ impl Engine { Expr::Custom(x) => { let func = (x.0).1.as_ref(); let ep: StaticVec<_> = (x.0).0.iter().map(|e| e.into()).collect(); - func(self, scope, mods, state, lib, this_ptr, ep.as_ref(), level) + let mut context = EvalContext { + mods, + state, + lib, + this_ptr, + level, + }; + func(self, &mut context, scope, ep.as_ref()) } _ => unreachable!(), @@ -2492,12 +2504,8 @@ impl Engine { for ((id, id_pos), rename) in list.iter() { // Mark scope variables as public if let Some(index) = scope.get_index(id).map(|(i, _)| i) { - let alias = rename - .as_ref() - .map(|(n, _)| n.clone()) - .unwrap_or_else(|| id.clone()); - - scope.set_entry_alias(index, alias); + let alias = rename.as_ref().map(|(n, _)| n).unwrap_or_else(|| id); + scope.set_entry_alias(index, alias.clone()); } else { return Err(Box::new(EvalAltResult::ErrorVariableNotFound( id.into(), diff --git a/src/lib.rs b/src/lib.rs index 49e0bcc5..dab4b17a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -175,6 +175,10 @@ pub use parser::{CustomExpr, Expr, ReturnType, ScriptFnDef, Stmt}; #[deprecated(note = "this type is volatile and may change")] pub use engine::{Expression, Imports, State as EvalState}; +#[cfg(feature = "internals")] +#[deprecated(note = "this type is volatile and may change")] +pub use syntax::EvalContext; + #[cfg(feature = "internals")] #[deprecated(note = "this type is volatile and may change")] pub use module::ModuleRef; diff --git a/src/optimize.rs b/src/optimize.rs index 945040ea..d47188a1 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -434,7 +434,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { (Expr::StringConstant(s), Expr::IntegerConstant(i)) if i.0 >= 0 && (i.0 as usize) < s.0.chars().count() => { // String literal indexing - get the character state.set_dirty(); - Expr::CharConstant(Box::new((s.0.chars().nth(i.0 as usize).expect("should get char"), s.1))) + Expr::CharConstant(Box::new((s.0.chars().nth(i.0 as usize).unwrap(), s.1))) } // lhs[rhs] (lhs, rhs) => Expr::Index(Box::new((optimize_expr(lhs, state), optimize_expr(rhs, state), x.2))), @@ -614,7 +614,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { state.set_dirty(); // Replace constant with value - state.find_constant(&name).expect("should find constant in scope!").clone().set_position(pos) + state.find_constant(&name).unwrap().clone().set_position(pos) } // Custom syntax @@ -655,10 +655,7 @@ fn optimize( && expr.as_ref().map(|v| v.is_constant()).unwrap_or(false) }) .for_each(|ScopeEntry { name, expr, .. }| { - state.push_constant( - name.as_ref(), - (**expr.as_ref().expect("should be Some(expr)")).clone(), - ) + state.push_constant(name.as_ref(), expr.as_ref().unwrap().as_ref().clone()) }); let orig_constants_len = state.constants.len(); diff --git a/src/syntax.rs b/src/syntax.rs index 9bd5fad1..78bf94ff 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -22,26 +22,13 @@ use crate::stdlib::{ #[cfg(not(feature = "sync"))] pub type FnCustomSyntaxEval = dyn Fn( &Engine, + &mut EvalContext, &mut Scope, - &mut Imports, - &mut State, - &Module, - &mut Option<&mut Dynamic>, &[Expression], - usize, ) -> Result>; /// A general function trail object. #[cfg(feature = "sync")] -pub type FnCustomSyntaxEval = dyn Fn( - &Engine, - &mut Scope, - &mut Imports, - &mut State, - &Module, - &mut Option<&mut Dynamic>, - &[Expression], - usize, - ) -> Result> +pub type FnCustomSyntaxEval = dyn Fn(&Engine, &mut EvalContext, &mut Scope, &[Expression]) -> Result> + Send + Sync; @@ -58,6 +45,14 @@ impl fmt::Debug for CustomSyntax { } } +pub struct EvalContext<'a, 'b: 'a, 's, 'm, 't, 'd: 't> { + pub(crate) mods: &'a mut Imports<'b>, + pub(crate) state: &'s mut State, + pub(crate) lib: &'m Module, + pub(crate) this_ptr: &'t mut Option<&'d mut Dynamic>, + pub(crate) level: usize, +} + impl Engine { pub fn register_custom_syntax + ToString>( &mut self, @@ -65,13 +60,9 @@ impl Engine { scope_delta: isize, func: impl Fn( &Engine, + &mut EvalContext, &mut Scope, - &mut Imports, - &mut State, - &Module, - &mut Option<&mut Dynamic>, &[Expression], - usize, ) -> Result> + SendSync + 'static, diff --git a/src/unsafe.rs b/src/unsafe.rs index fa5b271f..86c571f1 100644 --- a/src/unsafe.rs +++ b/src/unsafe.rs @@ -12,6 +12,7 @@ use crate::stdlib::{ }; /// Cast a type into another type. +#[inline(always)] pub fn unsafe_try_cast(a: A) -> Option { if TypeId::of::() == a.type_id() { // SAFETY: Just checked we have the right type. We explicitly forget the @@ -28,6 +29,7 @@ pub fn unsafe_try_cast(a: A) -> Option { } /// Cast a Boxed type into another type. +#[inline(always)] pub fn unsafe_cast_box(item: Box) -> Result, Box> { // Only allow casting to the exact same type if TypeId::of::() == TypeId::of::() { @@ -51,6 +53,7 @@ pub fn unsafe_cast_box(item: Box) -> Result, B /// /// Force-casting a local variable's lifetime to the current `Scope`'s larger lifetime saves /// on allocations and string cloning, thus avoids us having to maintain a chain of `Scope`'s. +#[inline] pub fn unsafe_cast_var_name_to_lifetime<'s>(name: &str, state: &State) -> Cow<'s, str> { // If not at global level, we can force-cast if state.scope_level > 0 { diff --git a/src/utils.rs b/src/utils.rs index 01fdd26b..12add69d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -36,11 +36,9 @@ use ahash::AHasher; pub struct StraightHasher(u64); impl Hasher for StraightHasher { - #[inline(always)] fn finish(&self) -> u64 { self.0 } - #[inline] fn write(&mut self, bytes: &[u8]) { let mut key = [0_u8; 8]; key.copy_from_slice(&bytes[..8]); // Panics if fewer than 8 bytes @@ -50,7 +48,6 @@ impl Hasher for StraightHasher { impl StraightHasher { /// Create a `StraightHasher`. - #[inline(always)] pub fn new() -> Self { Self(0) } @@ -63,7 +60,6 @@ pub struct StraightHasherBuilder; impl BuildHasher for StraightHasherBuilder { type Hasher = StraightHasher; - #[inline(always)] fn build_hasher(&self) -> Self::Hasher { StraightHasher::new() } @@ -150,7 +146,6 @@ pub struct StaticVec { const MAX_STATIC_VEC: usize = 4; impl Drop for StaticVec { - #[inline(always)] fn drop(&mut self) { self.clear(); } @@ -233,7 +228,6 @@ impl IntoIterator for StaticVec { impl StaticVec { /// Create a new `StaticVec`. - #[inline(always)] pub fn new() -> Self { Default::default() } @@ -249,7 +243,6 @@ impl StaticVec { self.len = 0; } /// Extract a `MaybeUninit` into a concrete initialized type. - #[inline(always)] fn extract(value: MaybeUninit) -> T { unsafe { value.assume_init() } } @@ -311,7 +304,6 @@ impl StaticVec { ); } /// Is data stored in fixed-size storage? - #[inline(always)] fn is_fixed_storage(&self) -> bool { self.len <= MAX_STATIC_VEC } @@ -418,12 +410,10 @@ impl StaticVec { } } /// Get the number of items in this `StaticVec`. - #[inline(always)] pub fn len(&self) -> usize { self.len } /// Is this `StaticVec` empty? - #[inline(always)] pub fn is_empty(&self) -> bool { self.len == 0 } @@ -664,48 +654,41 @@ pub struct ImmutableString(Shared); impl Deref for ImmutableString { type Target = String; - #[inline(always)] fn deref(&self) -> &Self::Target { &self.0 } } impl AsRef for ImmutableString { - #[inline(always)] fn as_ref(&self) -> &String { &self.0 } } impl Borrow for ImmutableString { - #[inline(always)] fn borrow(&self) -> &str { self.0.as_str() } } impl From<&str> for ImmutableString { - #[inline(always)] fn from(value: &str) -> Self { Self(value.to_string().into()) } } impl From for ImmutableString { - #[inline(always)] fn from(value: String) -> Self { Self(value.into()) } } impl From> for ImmutableString { - #[inline(always)] fn from(value: Box) -> Self { Self(value.into()) } } impl From for String { - #[inline(always)] fn from(value: ImmutableString) -> Self { value.into_owned() } @@ -714,49 +697,42 @@ impl From for String { impl FromStr for ImmutableString { type Err = (); - #[inline(always)] fn from_str(s: &str) -> Result { Ok(Self(s.to_string().into())) } } impl FromIterator for ImmutableString { - #[inline(always)] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } } impl<'a> FromIterator<&'a char> for ImmutableString { - #[inline(always)] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().cloned().collect::().into()) } } impl<'a> FromIterator<&'a str> for ImmutableString { - #[inline(always)] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } } impl<'a> FromIterator for ImmutableString { - #[inline(always)] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } } impl fmt::Display for ImmutableString { - #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self.0.as_str(), f) } } impl fmt::Debug for ImmutableString { - #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self.0.as_str(), f) } @@ -891,7 +867,6 @@ impl Add for &ImmutableString { } impl AddAssign for ImmutableString { - #[inline(always)] fn add_assign(&mut self, rhs: char) { self.make_mut().push(rhs); } @@ -906,7 +881,6 @@ impl ImmutableString { } /// Make sure that the `ImmutableString` is unique (i.e. no other outstanding references). /// Then return a mutable reference to the `String`. - #[inline(always)] pub fn make_mut(&mut self) -> &mut String { shared_make_mut(&mut self.0) } diff --git a/tests/syntax.rs b/tests/syntax.rs index d4737b71..11074c6a 100644 --- a/tests/syntax.rs +++ b/tests/syntax.rs @@ -1,8 +1,5 @@ #![cfg(feature = "internals")] -use rhai::{ - Dynamic, Engine, EvalAltResult, EvalState, Expression, Imports, LexError, Module, ParseError, - ParseErrorType, Scope, INT, -}; +use rhai::{Engine, EvalAltResult, EvalContext, Expression, LexError, ParseErrorType, Scope, INT}; #[test] fn test_custom_syntax() -> Result<(), Box> { @@ -28,13 +25,9 @@ fn test_custom_syntax() -> Result<(), Box> { ], 1, |engine: &Engine, + context: &mut EvalContext, scope: &mut Scope, - mods: &mut Imports, - state: &mut EvalState, - lib: &Module, - this_ptr: &mut Option<&mut Dynamic>, - inputs: &[Expression], - level: usize| { + inputs: &[Expression]| { let var_name = inputs[0].get_variable_name().unwrap().to_string(); let stmt = inputs.get(1).unwrap(); let expr = inputs.get(2).unwrap(); @@ -42,10 +35,10 @@ fn test_custom_syntax() -> Result<(), Box> { scope.push(var_name, 0 as INT); loop { - engine.eval_expression_tree(scope, mods, state, lib, this_ptr, stmt, level)?; + engine.eval_expression_tree(context, scope, stmt)?; if !engine - .eval_expression_tree(scope, mods, state, lib, this_ptr, expr, level)? + .eval_expression_tree(context, scope, expr)? .as_bool() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch( @@ -78,7 +71,7 @@ fn test_custom_syntax() -> Result<(), Box> { // The first symbol must be an identifier assert!(matches!( - *engine.register_custom_syntax(&["!"], 0, |_, _, _, _, _, _, _, _| Ok(().into())).expect_err("should error"), + *engine.register_custom_syntax(&["!"], 0, |_, _, _, _| Ok(().into())).expect_err("should error"), LexError::ImproperSymbol(s) if s == "!" ));