From c8c4ca21adb94918a4647c3cc148bff0bc06e8af Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 19 Oct 2020 14:26:15 +0800 Subject: [PATCH 1/4] Update docs. --- README.md | 13 ++- doc/src/SUMMARY.md | 4 +- doc/src/about/related.md | 12 +- doc/src/images/rhai.png | Bin 0 -> 5158 bytes doc/src/plugins/module.md | 2 +- doc/src/rust/custom.md | 167 +++++++++++++-------------- doc/src/rust/getters-setters.md | 29 ++--- doc/src/rust/indexers.md | 47 +++++--- doc/src/rust/register-raw.md | 2 +- examples/arrays_and_structs.rs | 2 +- examples/custom_types_and_methods.rs | 2 +- src/api.rs | 30 +++-- src/engine.rs | 13 ++- tests/arrays.rs | 2 +- tests/float.rs | 2 +- tests/get_set.rs | 2 +- tests/method_call.rs | 2 +- tests/mismatched_op.rs | 2 +- 18 files changed, 181 insertions(+), 152 deletions(-) create mode 100644 doc/src/images/rhai.png diff --git a/README.md b/README.md index cc68898c..73ca1bb5 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,13 @@ Rhai - Embedded Scripting for Rust [![crates.io](https://img.shields.io/crates/v/rhai.svg)](https://crates.io/crates/rhai/) [![crates.io](https://img.shields.io/crates/d/rhai)](https://crates.io/crates/rhai/) [![API Docs](https://docs.rs/rhai/badge.svg)](https://docs.rs/rhai/) +[![chat](https://img.shields.io/discord/767611025456889857.svg?logo=discord)](https://discord.gg/yZMKAQ) +[![Reddit](https://img.shields.io/reddit/subreddit-subscribers/Rhai)](https://www.reddit.com/r/Rhai) Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way to add scripting to any application. + Supported targets and builds --------------------------- @@ -19,6 +22,7 @@ Supported targets and builds * `no-std` * Minimum Rust version 1.45 + Standard features ----------------- @@ -41,13 +45,15 @@ Standard features * Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature). * Support for [minimal builds](https://schungx.github.io/rhai/start/builds/minimal.html) by excluding unneeded language [features](https://schungx.github.io/rhai/start/features.html). -Protection against attacks --------------------------- + +Protected against attacks +------------------------- * Sand-boxed - the scripting engine, if declared immutable, cannot mutate the containing environment unless [explicitly permitted](https://schungx.github.io/rhai/patterns/control.html). * Rugged - protected against malicious attacks (such as [stack-overflow](https://schungx.github.io/rhai/safety/max-call-stack.html), [over-sized data](https://schungx.github.io/rhai/safety/max-string-size.html), and [runaway scripts](https://schungx.github.io/rhai/safety/max-operations.html) etc.) that may come from untrusted third-party user-land scripts. * Track script evaluation [progress](https://schungx.github.io/rhai/safety/progress.html) and manually terminate a script run. + For those who actually want their own language --------------------------------------------- @@ -56,6 +62,7 @@ For those who actually want their own language * Define [custom operators](https://schungx.github.io/rhai/engine/custom-op.html). * Extend the language with [custom syntax](https://schungx.github.io/rhai/engine/custom-syntax.html). + Documentation ------------- @@ -65,12 +72,14 @@ To build _The Book_, first install [`mdbook`](https://github.com/rust-lang/mdBoo and [`mdbook-tera`](https://github.com/avitex/mdbook-tera) (for templating). Running `mdbook build` builds it. + Playground ---------- An [Online Playground](https://alvinhochun.github.io/rhai-demo/) is available with syntax-highlighting editor. Scripts can be evaluated directly from the editor. + License ------- diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 1e59b3e7..962bdca1 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -36,8 +36,8 @@ The Rhai Scripting Language 4. [Register a Fallible Rust Function](rust/fallible.md) 6. [Override a Built-in Function](rust/override.md) 7. [Operator Overloading](rust/operators.md) - 8. [Register a Custom Type and its Methods](rust/custom.md) - 1. [Getters and Setters](rust/getters-setters.md) + 8. [Register any Rust Type and its Methods](rust/custom.md) + 1. [Property Getters and Setters](rust/getters-setters.md) 2. [Indexers](rust/indexers.md) 3. [Disable Custom Types](rust/disable-custom.md) 4. [Printing Custom Types](rust/print-custom.md) diff --git a/doc/src/about/related.md b/doc/src/about/related.md index 8174676b..a284c7f0 100644 --- a/doc/src/about/related.md +++ b/doc/src/about/related.md @@ -3,7 +3,9 @@ Related Resources {{#include ../links.md}} -Other online documentation resources for Rhai: + +Other Online Resources for Rhai +------------------------------ * [`crates.io`](https://crates.io/crates/rhai) - Rhai crate @@ -13,7 +15,13 @@ Other online documentation resources for Rhai: * [Online Playground][playground] - Run scripts directly from editor -Other cool projects to check out: +* [Discord Chat](https://discord.gg/yZMKAQ) - Rhai channel + +* [Reddit](https://www.reddit.com/r/Rhai) - Rhai community + + +Other Cool Projects +------------------- * [ChaiScript](http://chaiscript.com) - A strong inspiration for Rhai. An embedded scripting language for C++. diff --git a/doc/src/images/rhai.png b/doc/src/images/rhai.png new file mode 100644 index 0000000000000000000000000000000000000000..73d16a99e6fd65d5a44ee093098571ba2bd1ede4 GIT binary patch literal 5158 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 U5WF%)4FCWD07*qoM6N<$f+NY&3IG5A literal 0 HcmV?d00001 diff --git a/doc/src/plugins/module.md b/doc/src/plugins/module.md index 81195d47..6d07abdc 100644 --- a/doc/src/plugins/module.md +++ b/doc/src/plugins/module.md @@ -8,7 +8,7 @@ Prelude ------- When using the plugins system, the entire `rhai::plugin` module must be imported as a prelude -because code generated will these imports. +because code generated will need these imports. ```rust use rhai::plugin::*; diff --git a/doc/src/rust/custom.md b/doc/src/rust/custom.md index eb548f69..59815079 100644 --- a/doc/src/rust/custom.md +++ b/doc/src/rust/custom.md @@ -1,124 +1,118 @@ -Register a Custom Type and its Methods +Register any Rust Type and its Methods ===================================== {{#include ../links.md}} -Rhai works seamlessly with _any_ complex Rust type. The type can be registered with the `Engine`, as below. + +Free Typing +----------- + +Rhai works seamlessly with _any_ Rust type. The type can be _anything_; it does not +have any prerequisites other than being `Clone`. It does not need to implement +any other trait or use any custom `#[derive]`. + +This allows Rhai to be integrated into an existing code base with as little plumbing +as possible, usually silently and seamlessly. External types that are not defined +within the same crate (and thus cannot implement special Rhai traits or +use special `#[derive]`) can also be used easily with Rhai. + +The reason why it is termed a _custom_ type throughout this documentation is that +Rhai natively supports a number of data types with fast, internal treatment (see +the list of [standard types]). Any type outside of this list is considered _custom_. + +Any type not supported natively by Rhai is stored as a Rust _trait object_, with no +restrictions other than being `Clone` (plus `Send + Sync` under the [`sync`] feature). +It runs slightly slower than natively-supported types as it does not have built-in, +optimized implementations for commonly-used functions, but for all other purposes has +no difference. Support for custom types can be turned off via the [`no_object`] feature. -```rust -use rhai::{Engine, EvalAltResult}; -use rhai::RegisterFn; // remember 'RegisterFn' is needed -#[derive(Clone)] -struct TestStruct { - field: i64 -} +Register a Custom Type and its Methods +------------------------------------- -impl TestStruct { - fn update(&mut self) { - self.field += 41; - } +Any custom type must implement the `Clone` trait as this allows the [`Engine`] to pass by value. - fn new() -> Self { - TestStruct { field: 1 } - } -} - -let mut engine = Engine::new(); - -engine - .register_type::() // most API's can be chained up - .register_fn("update", TestStruct::update) - .register_fn("new_ts", TestStruct::new); - -let result = engine.eval::("let x = new_ts(); x.update(); x")?; - -println!("result: {}", result.field); // prints 42 -``` - - -Register a Custom Type ---------------------- - -A custom type must implement `Clone` as this allows the [`Engine`] to pass by value. +If the [`sync`] feature is used, it must also be `Send + Sync`. Notice that the custom type needs to be _registered_ using `Engine::register_type` or `Engine::register_type_with_name`. +To use native methods on custom types in Rhai scripts, it is common to register an API +for the type using one of the `Engine::register_XXX` functions. + ```rust +use rhai::{Engine, EvalAltResult}; +use rhai::RegisterFn; // remember 'RegisterFn' is needed + #[derive(Clone)] struct TestStruct { field: i64 } impl TestStruct { - fn update(&mut self) { // methods take &mut as first parameter - self.field += 41; + fn new() -> Self { + Self { field: 1 } } - fn new() -> Self { - TestStruct { field: 1 } + fn update(&mut self, x: i64) { // methods take &mut as first parameter + self.field += x; } } let mut engine = Engine::new(); -engine.register_type::(); -``` - - -Methods on the Custom Type -------------------------- - -To use native custom types, methods and functions in Rhai scripts, simply register them -using one of the `Engine::register_XXX` API. - -Below, the `update` and `new` methods are registered using `Engine::register_fn`. - -```rust +// Most Engine API's can be chained up. engine - .register_fn("update", TestStruct::update) // registers 'update(&mut TestStruct)' - .register_fn("new_ts", TestStruct::new); // registers 'new()' + .register_type::() // register custom type + .register_fn("new_ts", TestStruct::new) + .register_fn("update", TestStruct::update); + +// Cast result back to custom type. +let result = engine.eval::( + r" + let x = new_ts(); // calls 'TestStruct::new' + x.update(41); // calls 'TestStruct::update' + x // 'x' holds a 'TestStruct' + " +)?; + +println!("result: {}", result.field); // prints 42 ``` -***Note**: Rhai follows the convention that methods of custom types take a `&mut` first parameter -so that invoking methods can update the types. All other parameters in Rhai are passed by value (i.e. clones).* +Rhai follows the convention that methods of custom types take a `&mut` first parameter +to that type, so that invoking methods can always update it. + +All other parameters in Rhai are passed by value (i.e. clones). **IMPORTANT: Rhai does NOT support normal references (i.e. `&T`) as parameters.** -Use the Custom Type in Scripts ------------------------------ - -The custom type is then ready for use in scripts. Scripts can see the functions and methods registered earlier. -Get the evaluation result back out just as before, this time casting to the custom type: - -```rust -let result = engine.eval::("let x = new_ts(); x.update(); x")?; - -println!("result: {}", result.field); // prints 42 -``` - - Method-Call Style vs. Function-Call Style ---------------------------------------- Any function with a first argument that is a `&mut` reference can be used as method calls because internally they are the same thing: methods on a type is implemented as a functions taking a `&mut` first argument. + This design is similar to Rust. ```rust -fn foo(ts: &mut TestStruct) -> i64 { - ts.field +impl TestStruct { + fn foo(&mut self) -> i64 { + self.field + } } -engine.register_fn("foo", foo); // register a Rust native function +engine.register_fn("foo", TestStruct::foo); let result = engine.eval::( - "let x = new_ts(); x.foo()" // 'foo' can be called like a method on 'x' + r" + let x = new_ts(); + foo(x); // normal call to 'foo' + x.foo() // 'foo' can also be called like a method on 'x' + " )?; println!("result: {}", result); // prints 1 @@ -128,8 +122,9 @@ Under [`no_object`], however, the _method_ style of function calls (i.e. calling a function as an object-method) is no longer supported. ```rust -// Below is a syntax error under 'no_object' because 'clear' cannot be called in method style. -let result = engine.eval::<()>("let x = [1, 2, 3]; x.clear()")?; +// Below is a syntax error under 'no_object'. +let result = engine.eval("let x = [1, 2, 3]; x.clear();")?; + // ^ cannot call in method style under 'no_object' ``` @@ -143,18 +138,16 @@ with a special "pretty-print" name, [`type_of()`] will return that name instead. ```rust engine - .register_type::() - .register_fn("new_ts", TestStruct::new); + .register_type::() + .register_fn("new_ts1", TestStruct1::new) + .register_type_with_name::("MyType") + .register_fn("new_ts2", TestStruct2::new); -let x = new_ts(); -x.type_of() == "path::to::module::TestStruct"; +let ts1_type = engine.eval::(r#"let x = new_ts1(); x.type_of()"#)?; +let ts2_type = engine.eval::(r#"let x = new_ts2(); x.type_of()"#)?; -engine - .register_type_with_name::("Hello") - .register_fn("new_ts", TestStruct::new); - -let x = new_ts(); -x.type_of() == "Hello"; +println!("{}", ts1_type); // prints 'path::to::TestStruct' +println!("{}", ts1_type); // prints 'MyType' ``` @@ -190,7 +183,9 @@ the `==` operator must be registered for the custom type: ```rust // Assume 'TestStruct' implements `PartialEq` -engine.register_fn("==", |item1: &mut TestStruct, item2: TestStruct| item1 == item2); +engine.register_fn("==", + |item1: &mut TestStruct, item2: TestStruct| item1 == &item2 +); // Then this works in Rhai: let item = new_ts(); // construct a new 'TestStruct' diff --git a/doc/src/rust/getters-setters.md b/doc/src/rust/getters-setters.md index 586070af..334c7438 100644 --- a/doc/src/rust/getters-setters.md +++ b/doc/src/rust/getters-setters.md @@ -1,29 +1,32 @@ -Custom Type Getters and Setters -============================== +Custom Type Property Getters and Setters +======================================= {{#include ../links.md}} -A custom type can also expose members by registering `get` and/or `set` functions. +A [custom type] can also expose properties by registering `get` and/or `set` functions. Getters and setters each take a `&mut` reference to the first parameter. Getters and setters are disabled when the [`no_object`] feature is used. -| `Engine` API | Description | Return Value of Function | -| --------------------- | ------------------------------------------------- | :-----------------------------------: | -| `register_get` | register a getter | _any_ `T: Clone` | -| `register_set` | register a setter | _none_ | -| `register_get_set` | short-hand to register both a getter and a setter | _none_ | -| `register_get_result` | register a getter | `Result>` | -| `register_set_result` | register a setter | `Result<(), Box>` | +| `Engine` API | Function signature(s)
(`T: Clone` = custom type,
`V: Clone` = data type) | Can mutate `T`? | +| --------------------- | -------------------------------------------------------------------------------- | :----------------------------: | +| `register_get` | `Fn(&mut T) -> V` | yes, but not advised | +| `register_set` | `Fn(&mut T, V)` | yes | +| `register_get_set` | getter: `Fn(&mut T) -> V`
setter: `Fn(&mut T, V)` | yes, but not advised in getter | +| `register_get_result` | `Fn(&mut T) -> Result>` | yes, but not advised | +| `register_set_result` | `Fn(&mut T, V) -> Result<(), Box>` | yes | + +By convention, property getters are not supposed to mutate the [custom type], although there is nothing +that prevents this mutation. Cannot Override Object Maps -------------------------- -Getters and setters are only intended for [custom types]. +Property getters and setters are mainly intended for [custom types]. -Any getter or setter function registered for [object maps] is simply ignored because +Any getter or setter function registered for [object maps] is simply _ignored_ because the get/set calls will be interpreted as properties on the [object maps]. @@ -47,7 +50,7 @@ impl TestStruct { } fn new() -> Self { - TestStruct { field: "hello" } + Self { field: "hello" } } } diff --git a/doc/src/rust/indexers.md b/doc/src/rust/indexers.md index 2f76277f..f7cff1af 100644 --- a/doc/src/rust/indexers.md +++ b/doc/src/rust/indexers.md @@ -3,23 +3,30 @@ Custom Type Indexers {{#include ../links.md}} -A custom type can also expose an _indexer_ by registering an indexer function. +A [custom type] can also expose an _indexer_ by registering an indexer function. -A custom type with an indexer function defined can use the bracket notation to get a property value: +A [custom type] with an indexer function defined can use the bracket notation to get a property value: > _object_ `[` _index_ `]` -Like getters and setters, indexers take a `&mut` reference to the first parameter. +Like property [getters/setters], indexers take a `&mut` reference to the first parameter. + +They also take an additional parameter of any type that serves as the _index_ within brackets. Indexers are disabled when the [`no_index`] feature is used. -| `Engine` API | Description | Return Value of Function | -| ----------------------------- | -------------------------------------------------------- | :-----------------------------------: | -| `register_indexer_get` | register an index getter | _any_ `T: Clone` | -| `register_indexer_set` | register an index setter | _none_ | -| `register_indexer_get_set` | short-hand to register both an index getter and a setter | _none_ | -| `register_indexer_get_result` | register an index getter | `Result>` | -| `register_indexer_set_result` | register an index setter | `Result<(), Box>` | +| `Engine` API | Function signature(s)
(`T: Clone` = custom type,
`X: Clone` = index type,
`V: Clone` = data type) | Can mutate `T`? | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------- | :----------------------------: | +| `register_indexer_get` | `Fn(&mut T, X) -> V` | yes, but not advised | +| `register_indexer_set` | `Fn(&mut T, X, V)` | yes | +| `register_indexer_get_set` | getter: `Fn(&mut T, X) -> V`
setter: `Fn(&mut T, X, V)` | yes, but not advised in getter | +| `register_indexer_get_result` | `Fn(&mut T, X) -> Result>` | yes, but not advised | +| `register_indexer_set_result` | `Fn(&mut T, X, V) -> Result<(), Box>` | yes | + +By convention, index getters are not supposed to mutate the [custom type], although there is nothing +that prevents this mutation. + +**IMPORTANT: Rhai does NOT support normal references (i.e. `&T`) as parameters.** Cannot Override Arrays, Object Maps and Strings @@ -42,15 +49,15 @@ struct TestStruct { impl TestStruct { // Remember &mut must be used even for getters - fn get_field(&mut self, index: i64) -> i64 { - self.fields[index as usize] + fn get_field(&mut self, index: String) -> i64 { + self.fields[index.len()] } - fn set_field(&mut self, index: i64, value: i64) { - self.fields[index as usize] = value + fn set_field(&mut self, index: String, value: i64) { + self.fields[index.len()] = value } fn new() -> Self { - TestStruct { fields: vec![1, 2, 3, 4, 5] } + Self { fields: vec![1, 2, 3, 4, 5] } } } @@ -63,9 +70,13 @@ engine .register_indexer_get(TestStruct::get_field) .register_indexer_set(TestStruct::set_field); -let result = engine.eval::("let a = new_ts(); a[2] = 42; a[2]")?; +let result = engine.eval::( + r#" + let a = new_ts(); + a["xyz"] = 42; // these indexers use strings + a["xyz"] // as the index type + "# +)?; println!("Answer: {}", result); // prints 42 ``` - -**IMPORTANT: Rhai does NOT support normal references (i.e. `&T`) as parameters.** diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md index dc4e3ca2..87854acc 100644 --- a/doc/src/rust/register-raw.md +++ b/doc/src/rust/register-raw.md @@ -65,7 +65,7 @@ The function signature passed to `Engine::register_raw_fn` takes the following f where: -* `T: Variant + Clone` - return type of the function. +* `T: Clone` - return type of the function. * `context: NativeCallContext` - the current _native call context_, which exposes the following: diff --git a/examples/arrays_and_structs.rs b/examples/arrays_and_structs.rs index 6aa9fafa..0ad34051 100644 --- a/examples/arrays_and_structs.rs +++ b/examples/arrays_and_structs.rs @@ -11,7 +11,7 @@ impl TestStruct { } fn new() -> Self { - TestStruct { x: 1 } + Self { x: 1 } } } diff --git a/examples/custom_types_and_methods.rs b/examples/custom_types_and_methods.rs index 4a8e260a..4b0118fa 100644 --- a/examples/custom_types_and_methods.rs +++ b/examples/custom_types_and_methods.rs @@ -11,7 +11,7 @@ impl TestStruct { } fn new() -> Self { - TestStruct { x: 1 } + Self { x: 1 } } } diff --git a/src/api.rs b/src/api.rs index 031e06b7..07f0cef1 100644 --- a/src/api.rs +++ b/src/api.rs @@ -88,7 +88,7 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { TestStruct { field: 1 } } + /// fn new() -> Self { Self { field: 1 } } /// fn update(&mut self, offset: i64) { self.field += offset; } /// } /// @@ -130,7 +130,7 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { TestStruct { field: 1 } } + /// fn new() -> Self { Self { field: 1 } } /// } /// /// # fn main() -> Result<(), Box> { @@ -200,7 +200,7 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { TestStruct { field: 1 } } + /// fn new() -> Self { Self { field: 1 } } /// // Even a getter must start with `&mut self` and not `&self`. /// fn get_field(&mut self) -> i64 { self.field } /// } @@ -252,7 +252,7 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { TestStruct { field: 1 } } + /// fn new() -> Self { Self { field: 1 } } /// // Even a getter must start with `&mut self` and not `&self`. /// fn get_field(&mut self) -> Result> { /// Ok(self.field.into()) @@ -295,7 +295,7 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { TestStruct { field: 1 } } + /// fn new() -> Self { Self { field: 1 } } /// fn set_field(&mut self, new_val: i64) { self.field = new_val; } /// } /// @@ -348,7 +348,7 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { TestStruct { field: 1 } } + /// fn new() -> Self { Self { field: 1 } } /// fn set_field(&mut self, new_val: i64) -> Result<(), Box> { /// self.field = new_val; /// Ok(()) @@ -386,8 +386,7 @@ impl Engine { U: Variant + Clone, { self.register_result_fn(&make_setter(name), move |obj: &mut T, value: U| { - callback(obj, value)?; - Ok(().into()) + callback(obj, value).map(Into::into) }) } @@ -405,7 +404,7 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { TestStruct { field: 1 } } + /// fn new() -> Self { Self { field: 1 } } /// // Even a getter must start with `&mut self` and not `&self`. /// fn get_field(&mut self) -> i64 { self.field } /// fn set_field(&mut self, new_val: i64) { self.field = new_val; } @@ -462,7 +461,7 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } } + /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } /// // Even a getter must start with `&mut self` and not `&self`. /// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] } /// } @@ -534,7 +533,7 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } } + /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } /// // Even a getter must start with `&mut self` and not `&self`. /// fn get_field(&mut self, index: i64) -> Result> { /// Ok(self.fields[index as usize].into()) @@ -600,7 +599,7 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } } + /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } /// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; } /// } /// @@ -672,7 +671,7 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } } + /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } /// fn set_field(&mut self, index: i64, value: i64) -> Result<(), Box> { /// self.fields[index as usize] = value; /// Ok(()) @@ -724,8 +723,7 @@ impl Engine { } self.register_result_fn(FN_IDX_SET, move |obj: &mut T, index: X, value: U| { - callback(obj, index, value)?; - Ok(().into()) + callback(obj, index, value).map(Into::into) }) } @@ -745,7 +743,7 @@ impl Engine { /// } /// /// impl TestStruct { - /// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } } + /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } /// // Even a getter must start with `&mut self` and not `&self`. /// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] } /// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; } diff --git a/src/engine.rs b/src/engine.rs index bc7063dc..c526b561 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -934,7 +934,9 @@ impl Engine { level, ) .map_err(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(_, _) => { + EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + if fn_sig.ends_with("]=") => + { EvalAltResult::ErrorIndexingType( self.map_type_name(val_type_name).into(), Position::none(), @@ -1381,9 +1383,12 @@ impl Engine { ) .map(|(v, _)| v.into()) .map_err(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(_, _) => Box::new( - EvalAltResult::ErrorIndexingType(type_name.into(), Position::none()), - ), + EvalAltResult::ErrorFunctionNotFound(fn_sig, _) if fn_sig.ends_with("]") => { + Box::new(EvalAltResult::ErrorIndexingType( + type_name.into(), + Position::none(), + )) + } _ => err, }) } diff --git a/tests/arrays.rs b/tests/arrays.rs index cbd4daf5..65811a23 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -84,7 +84,7 @@ fn test_array_with_structs() -> Result<(), Box> { } fn new() -> Self { - TestStruct { x: 1 } + Self { x: 1 } } } diff --git a/tests/float.rs b/tests/float.rs index ea87ece3..534115e3 100644 --- a/tests/float.rs +++ b/tests/float.rs @@ -51,7 +51,7 @@ fn test_struct_with_float() -> Result<(), Box> { } fn new() -> Self { - TestStruct { x: 1.0 } + Self { x: 1.0 } } } diff --git a/tests/get_set.rs b/tests/get_set.rs index ca5c0c47..b654348c 100644 --- a/tests/get_set.rs +++ b/tests/get_set.rs @@ -25,7 +25,7 @@ fn test_get_set() -> Result<(), Box> { } fn new() -> Self { - TestStruct { + Self { x: 1, y: 0, array: vec![1, 2, 3, 4, 5], diff --git a/tests/method_call.rs b/tests/method_call.rs index 20612146..e7b863e6 100644 --- a/tests/method_call.rs +++ b/tests/method_call.rs @@ -15,7 +15,7 @@ fn test_method_call() -> Result<(), Box> { } fn new() -> Self { - TestStruct { x: 1 } + Self { x: 1 } } } diff --git a/tests/mismatched_op.rs b/tests/mismatched_op.rs index 01373df2..5ef401f0 100644 --- a/tests/mismatched_op.rs +++ b/tests/mismatched_op.rs @@ -20,7 +20,7 @@ fn test_mismatched_op_custom_type() { impl TestStruct { fn new() -> Self { - TestStruct { x: 1 } + Self { x: 1 } } } From d1f9f4096d5b00b3046db0a444941a76c2fa60e2 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 19 Oct 2020 17:26:47 +0800 Subject: [PATCH 2/4] Update badges --- README.md | 12 ++++++------ src/engine.rs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 73ca1bb5..19ed70c0 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ Rhai - Embedded Scripting for Rust ================================= -![GitHub last commit](https://img.shields.io/github/last-commit/jonathandturner/rhai) +![GitHub last commit](https://img.shields.io/github/last-commit/jonathandturner/rhai?logo=github) [![Build Status](https://github.com/jonathandturner/rhai/workflows/Build/badge.svg)](https://github.com/jonathandturner/rhai/actions) -[![license](https://img.shields.io/github/license/jonathandturner/rhai)](https://github.com/license/jonathandturner/rhai) -[![crates.io](https://img.shields.io/crates/v/rhai.svg)](https://crates.io/crates/rhai/) -[![crates.io](https://img.shields.io/crates/d/rhai)](https://crates.io/crates/rhai/) -[![API Docs](https://docs.rs/rhai/badge.svg)](https://docs.rs/rhai/) +[![license](https://img.shields.io/crates/l/rhai)](https://github.com/license/jonathandturner/rhai) +[![crates.io](https://img.shields.io/crates/v/rhai?logo=rust)](https://crates.io/crates/rhai/) +[![crates.io](https://img.shields.io/crates/d/rhai?logo=rust)](https://crates.io/crates/rhai/) +[![API Docs](https://docs.rs/rhai/badge.svg?logo=docs.rs)](https://docs.rs/rhai/) [![chat](https://img.shields.io/discord/767611025456889857.svg?logo=discord)](https://discord.gg/yZMKAQ) -[![Reddit](https://img.shields.io/reddit/subreddit-subscribers/Rhai)](https://www.reddit.com/r/Rhai) +[![Reddit](https://img.shields.io/reddit/subreddit-subscribers/Rhai?logo=reddit)](https://www.reddit.com/r/Rhai) Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way to add scripting to any application. diff --git a/src/engine.rs b/src/engine.rs index c526b561..f5ce3981 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1383,7 +1383,7 @@ impl Engine { ) .map(|(v, _)| v.into()) .map_err(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(fn_sig, _) if fn_sig.ends_with("]") => { + EvalAltResult::ErrorFunctionNotFound(fn_sig, _) if fn_sig.ends_with(']') => { Box::new(EvalAltResult::ErrorIndexingType( type_name.into(), Position::none(), From a9fd0ff4de7b6827afd58ba595dc959e4d9ef3f9 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 19 Oct 2020 19:11:55 +0800 Subject: [PATCH 3/4] Encapsulate scope into EvalContext; Fix bug with custom syntax delta. --- RELEASES.md | 1 + doc/src/engine/custom-syntax.md | 38 ++++++++++------------- doc/src/engine/var.md | 6 ++-- src/api.rs | 27 +++++++++-------- src/engine.rs | 26 +++++++++++----- src/fn_call.rs | 12 ++++++-- src/fn_native.rs | 9 ++---- src/module/mod.rs | 4 +-- src/parser.rs | 18 ++++++++++- src/plugin.rs | 4 +-- src/syntax.rs | 19 ++++++------ src/token.rs | 54 +++++++++++++++++---------------- tests/call_fn.rs | 4 +-- tests/syntax.rs | 12 ++++---- tests/var_scope.rs | 12 +++++--- 15 files changed, 138 insertions(+), 108 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index cb37b9d8..db52a753 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -11,6 +11,7 @@ Breaking changes * `EvalAltResult::ErrorReadingScriptFile` is removed in favor of the new `EvalAltResult::ErrorSystem`. * `EvalAltResult::ErrorLoopBreak` is renamed to `EvalAltResult::LoopBreak`. * `Engine::register_raw_fn` and `FnPtr::call_dynamic` function signatures have changed. +* Callback signatures to `Engine::on_var` and `Engine::register_custom_syntax` have changed. New features ------------ diff --git a/doc/src/engine/custom-syntax.md b/doc/src/engine/custom-syntax.md index a0d38b9a..6bbe1157 100644 --- a/doc/src/engine/custom-syntax.md +++ b/doc/src/engine/custom-syntax.md @@ -114,25 +114,19 @@ Any custom syntax must include an _implementation_ of it. The function signature of an implementation is: -> `Fn(scope: &mut Scope, context: &mut EvalContext, inputs: &[Expression])` -> `-> Result>` +> `Fn(context: &mut EvalContext, inputs: &[Expression]) -> Result>` where: -* `scope: &mut Scope` - mutable reference to the current [`Scope`]; variables can be added to it. - -* `context: &mut EvalContext` - mutable reference to the current evaluation _context_ (**do not touch**) which exposes the following fields: +* `context: &mut EvalContext` - mutable reference to the current evaluation _context_, exposing the following: + * `context.scope: &mut Scope` - mutable reference to the current [`Scope`]; variables can be added to/removed from it. * `context.engine(): &Engine` - reference to the current [`Engine`]. * `context.namespace(): &Module` - reference to the current _global namespace_ (as a [module]) containing all script-defined functions. + * `context.this_ptr(): Option<&Dynamic>` - reference to the current bound [`this`] pointer, if any. * `context.call_level(): usize` - the current nesting level of function calls. * `inputs: &[Expression]` - a list of input expression trees. -#### 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 The most important argument is `inputs` where the matched identifiers (`$ident$`), expressions/statements (`$expr$`) @@ -152,8 +146,8 @@ Use the `EvalContext::eval_expression_tree` method to evaluate an arbitrary expr within the current evaluation context. ```rust -let expr = inputs.get(0).unwrap(); -let result = context.eval_expression_tree(scope, expr)?; +let expression = inputs.get(0).unwrap(); +let result = context.eval_expression_tree(expression)?; ``` ### Declare Variables @@ -163,15 +157,16 @@ New variables maybe declared (usually with a variable name that is passed in via It can simply be pushed into the [`Scope`]. However, beware that all new variables must be declared _prior_ to evaluating any expression tree. -In other words, any `Scope::push` calls must come _before_ any `EvalContext::eval_expression_tree` calls. +In other words, any [`Scope`] calls that change the list of must come _before_ any +`EvalContext::eval_expression_tree` calls. ```rust -let var_name = inputs[0].get_variable_name().unwrap().to_string(); -let expr = inputs.get(1).unwrap(); +let var_name = inputs[0].get_variable_name().unwrap(); +let expression = inputs.get(1).unwrap(); -scope.push(var_name, 0 as INT); // do this BEFORE 'context.eval_expression_tree'! +context.scope.push(var_name, 0 as INT); // do this BEFORE 'context.eval_expression_tree'! -let result = context.eval_expression_tree(context, scope, expr)?; +let result = context.eval_expression_tree(expression)?; ``` @@ -188,7 +183,6 @@ The syntax is passed simply as a slice of `&str`. ```rust // Custom syntax implementation fn implementation_func( - scope: &mut Scope, context: &mut EvalContext, inputs: &[Expression] ) -> Result> { @@ -196,15 +190,15 @@ fn implementation_func( let stmt = inputs.get(1).unwrap(); let condition = inputs.get(2).unwrap(); - // Push one new variable into the 'scope' BEFORE 'context.eval_expression_tree' - scope.push(var_name, 0 as INT); + // Push one new variable into the scope BEFORE 'context.eval_expression_tree' + context.scope.push(var_name, 0 as INT); loop { // Evaluate the statement block - context.eval_expression_tree(scope, stmt)?; + context.eval_expression_tree(stmt)?; // Evaluate the condition expression - let stop = !context.eval_expression_tree(scope, condition)? + let stop = !context.eval_expression_tree(condition)? .as_bool().map_err(|err| Box::new( EvalAltResult::ErrorMismatchDataType( "bool".to_string(), diff --git a/doc/src/engine/var.md b/doc/src/engine/var.md index f1119117..00b6a4fd 100644 --- a/doc/src/engine/var.md +++ b/doc/src/engine/var.md @@ -62,7 +62,7 @@ Function Signature The function signature passed to `Engine::on_var` takes the following form: -> `Fn(name: &str, index: usize, scope: &Scope, context: &EvalContext)` +> `Fn(name: &str, index: usize, context: &EvalContext)` > `-> Result, Box> + 'static` where: @@ -74,11 +74,11 @@ where: If `index` is zero, then there is no pre-calculated offset position and a search through the current [`Scope`] must be performed. -* `scope: &Scope` - reference to the current [`Scope`] containing all variables up to the current evaluation position. - * `context: &EvalContext` - reference to the current evaluation _context_, which exposes the following fields: + * `context.scope: &Scope` - reference to the current [`Scope`] containing all variables up to the current evaluation position. * `context.engine(): &Engine` - reference to the current [`Engine`]. * `context.namespace(): &Module` - reference to the current _global namespace_ (as a [module]) containing all script-defined functions. + * `context.this_ptr(): Option<&Dynamic>` - reference to the current bound [`this`] pointer, if any. * `context.call_level(): usize` - the current nesting level of function calls. ### Return Value diff --git a/src/api.rs b/src/api.rs index 07f0cef1..2e962b4f 100644 --- a/src/api.rs +++ b/src/api.rs @@ -3,12 +3,12 @@ use crate::any::{Dynamic, Variant}; use crate::engine::{Engine, EvalContext, Imports, State}; use crate::error::ParseError; -use crate::fn_native::{NativeCallContext, SendSync}; +use crate::fn_native::{FnCallArgs, NativeCallContext, SendSync}; use crate::optimize::OptimizationLevel; use crate::parser::AST; use crate::result::EvalAltResult; use crate::scope::Scope; -use crate::token::{lex, Position}; +use crate::token::Position; #[cfg(not(feature = "no_index"))] use crate::{ @@ -68,7 +68,7 @@ impl Engine { &mut self, name: &str, arg_types: &[TypeId], - func: impl Fn(NativeCallContext, &mut [&mut Dynamic]) -> Result> + func: impl Fn(NativeCallContext, &mut FnCallArgs) -> Result> + SendSync + 'static, ) -> &mut Self { @@ -913,7 +913,7 @@ impl Engine { scripts: &[&str], optimization_level: OptimizationLevel, ) -> Result { - let stream = lex(scripts, None, self); + let stream = self.lex(scripts, None); self.parse(&mut stream.peekable(), scope, optimization_level) } @@ -1067,7 +1067,7 @@ impl Engine { .into()); }; - let stream = lex( + let stream = self.lex( &scripts, if has_null { Some(Box::new(|token| match token { @@ -1078,7 +1078,6 @@ impl Engine { } else { None }, - self, ); let ast = self.parse_global_expr(&mut stream.peekable(), &scope, OptimizationLevel::None)?; @@ -1162,7 +1161,7 @@ impl Engine { script: &str, ) -> Result { let scripts = [script]; - let stream = lex(&scripts, None, self); + let stream = self.lex(&scripts, None); { let mut peekable = stream.peekable(); self.parse_global_expr(&mut peekable, scope, self.optimization_level) @@ -1323,7 +1322,7 @@ impl Engine { script: &str, ) -> Result> { let scripts = [script]; - let stream = lex(&scripts, None, self); + let stream = self.lex(&scripts, None); // No need to optimize a lone expression let ast = self.parse_global_expr(&mut stream.peekable(), scope, OptimizationLevel::None)?; @@ -1421,6 +1420,7 @@ impl Engine { }) .or_else(|err| match *err { EvalAltResult::Return(out, _) => Ok(out), + EvalAltResult::LoopBreak(_, _) => unreachable!(), _ => Err(err), }) .map(|v| (v, state.operations)) @@ -1464,7 +1464,7 @@ impl Engine { script: &str, ) -> Result<(), Box> { let scripts = [script]; - let stream = lex(&scripts, None, self); + let stream = self.lex(&scripts, None); let ast = self.parse(&mut stream.peekable(), scope, self.optimization_level)?; self.consume_ast_with_scope(scope, &ast) } @@ -1495,7 +1495,8 @@ impl Engine { .map_or_else( |err| match *err { EvalAltResult::Return(_, _) => Ok(()), - err => Err(Box::new(err)), + EvalAltResult::LoopBreak(_, _) => unreachable!(), + _ => Err(err), }, |_| Ok(()), ) @@ -1643,7 +1644,7 @@ impl Engine { lib: &Module, name: &str, this_ptr: &mut Option<&mut Dynamic>, - args: &mut [&mut Dynamic], + args: &mut FnCallArgs, ) -> Result> { let fn_def = lib .get_script_fn(name, args.len(), true) @@ -1713,7 +1714,7 @@ impl Engine { /// let mut engine = Engine::new(); /// /// // Register a variable resolver. - /// engine.on_var(|name, _, _, _| { + /// engine.on_var(|name, _, _| { /// match name { /// "MYSTIC_NUMBER" => Ok(Some(42_i64.into())), /// _ => Ok(None) @@ -1728,7 +1729,7 @@ impl Engine { #[inline(always)] pub fn on_var( &mut self, - callback: impl Fn(&str, usize, &Scope, &EvalContext) -> Result, Box> + callback: impl Fn(&str, usize, &EvalContext) -> Result, Box> + SendSync + 'static, ) -> &mut Self { diff --git a/src/engine.rs b/src/engine.rs index f5ce3981..5f1022c5 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -441,16 +441,17 @@ pub struct Limits { /// Context of a script evaluation process. #[derive(Debug)] -pub struct EvalContext<'e, 'a, 's, 'm, 't, 'd: 't> { +pub struct EvalContext<'e, 'x, 'px: 'x, 'a, 's, 'm, 't, 'pt: 't> { engine: &'e Engine, + pub scope: &'x mut Scope<'px>, pub(crate) mods: &'a mut Imports, pub(crate) state: &'s mut State, lib: &'m Module, - pub(crate) this_ptr: &'t mut Option<&'d mut Dynamic>, + pub(crate) this_ptr: &'t mut Option<&'pt mut Dynamic>, level: usize, } -impl<'e, 'a, 's, 'm, 't, 'd> EvalContext<'e, 'a, 's, 'm, 't, 'd> { +impl<'e, 'x, 'px, 'a, 's, 'm, 't, 'pt> EvalContext<'e, 'x, 'px, 'a, 's, 'm, 't, 'pt> { /// The current `Engine`. #[inline(always)] pub fn engine(&self) -> &'e Engine { @@ -469,6 +470,11 @@ impl<'e, 'a, 's, 'm, 't, 'd> EvalContext<'e, 'a, 's, 'm, 't, 'd> { pub fn namespace(&self) -> &'m Module { self.lib } + /// The current bound `this` pointer, if any. + #[inline(always)] + pub fn this_ptr(&self) -> Option<&Dynamic> { + self.this_ptr.as_ref().map(|v| &**v) + } /// The current nesting level of function calls. #[inline(always)] pub fn call_level(&self) -> usize { @@ -815,6 +821,7 @@ impl Engine { if let Some(ref resolve_var) = self.resolve_var { let context = EvalContext { engine: self, + scope, mods, state, lib, @@ -822,7 +829,7 @@ impl Engine { level: 0, }; if let Some(result) = - resolve_var(name, index, scope, &context).map_err(|err| err.fill_position(*pos))? + resolve_var(name, index, &context).map_err(|err| err.fill_position(*pos))? { return Ok((result.into(), name, ScopeEntryType::Constant, *pos)); } @@ -1742,17 +1749,22 @@ impl Engine { Expr::Unit(_) => Ok(().into()), Expr::Custom(x) => { - let func = (x.0).1.as_ref(); - let ep = (x.0).0.iter().map(|e| e.into()).collect::>(); + let func = (x.0).func(); + let expressions = (x.0) + .keywords() + .iter() + .map(Into::into) + .collect::>(); let mut context = EvalContext { engine: self, + scope, mods, state, lib, this_ptr, level, }; - func(scope, &mut context, ep.as_ref()) + func(&mut context, &expressions) } _ => unreachable!(), diff --git a/src/fn_call.rs b/src/fn_call.rs index 8260c0e1..8e9c1940 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -435,9 +435,11 @@ impl Engine { &self, lib: &Module, name: &str, - arg_types: &[TypeId], + arg_types: impl AsRef<[TypeId]>, pub_only: bool, ) -> bool { + let arg_types = arg_types.as_ref(); + let arg_len = if arg_types.is_empty() { usize::MAX } else { @@ -840,7 +842,7 @@ impl Engine { lib: &Module, this_ptr: &mut Option<&mut Dynamic>, name: &str, - args_expr: &[Expr], + args_expr: impl AsRef<[Expr]>, def_val: &Option, mut hash_script: u64, native: bool, @@ -848,6 +850,8 @@ impl Engine { capture: bool, level: usize, ) -> Result> { + let args_expr = args_expr.as_ref(); + // Handle Fn() if name == KEYWORD_FN_PTR && args_expr.len() == 1 { let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); @@ -1085,11 +1089,13 @@ impl Engine { this_ptr: &mut Option<&mut Dynamic>, modules: &Option>, name: &str, - args_expr: &[Expr], + args_expr: impl AsRef<[Expr]>, def_val: Option, hash_script: u64, level: usize, ) -> Result> { + let args_expr = args_expr.as_ref(); + let modules = modules.as_ref().unwrap(); let mut arg_values: StaticVec<_>; let mut first_arg_value = None; diff --git a/src/fn_native.rs b/src/fn_native.rs index 8a3c9a1d..c37c8b28 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -6,7 +6,6 @@ use crate::module::Module; use crate::parser::{FnAccess, ScriptFnDef}; use crate::plugin::PluginFunction; use crate::result::EvalAltResult; -use crate::scope::Scope; use crate::token::{is_valid_identifier, Position}; use crate::utils::ImmutableString; use crate::{calc_fn_hash, StaticVec}; @@ -265,14 +264,12 @@ pub type Callback = Box R + Send + Sync + 'static>; /// A standard callback function. #[cfg(not(feature = "sync"))] -pub type OnVarCallback = Box< - dyn Fn(&str, usize, &Scope, &EvalContext) -> Result, Box> - + 'static, ->; +pub type OnVarCallback = + Box Result, Box> + 'static>; /// A standard callback function. #[cfg(feature = "sync")] pub type OnVarCallback = Box< - dyn Fn(&str, usize, &Scope, &EvalContext) -> Result, Box> + dyn Fn(&str, usize, &EvalContext) -> Result, Box> + Send + Sync + 'static, diff --git a/src/module/mod.rs b/src/module/mod.rs index 53fd915f..4c2b3780 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -536,11 +536,11 @@ impl Module { &mut self, name: impl Into, arg_types: &[TypeId], - func: impl Fn(NativeCallContext, &mut [&mut Dynamic]) -> Result> + func: impl Fn(NativeCallContext, &mut FnCallArgs) -> Result> + SendSync + 'static, ) -> u64 { - let f = move |context: NativeCallContext, args: &mut [&mut Dynamic]| { + let f = move |context: NativeCallContext, args: &mut FnCallArgs| { func(context, args).map(Dynamic::from) }; self.set_fn( diff --git a/src/parser.rs b/src/parser.rs index 8cedfae7..8f503caf 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -919,6 +919,19 @@ impl Hash for CustomExpr { } } +impl CustomExpr { + /// Get the keywords for this `CustomExpr`. + #[inline(always)] + pub fn keywords(&self) -> &[Expr] { + &self.0 + } + /// Get the implementation function for this `CustomExpr`. + #[inline(always)] + pub fn func(&self) -> &FnCustomSyntaxEval { + self.1.as_ref() + } +} + /// _[INTERNALS]_ A type wrapping a floating-point number. /// Exported under the `internals` feature only. /// @@ -2670,7 +2683,10 @@ fn parse_expr( // Adjust the variables stack match syntax.scope_delta { delta if delta > 0 => { - state.stack.push(("".to_string(), ScopeEntryType::Normal)) + state.stack.resize( + state.stack.len() + delta as usize, + ("".to_string(), ScopeEntryType::Normal), + ); } delta if delta < 0 && state.stack.len() <= delta.abs() as usize => { state.stack.clear() diff --git a/src/plugin.rs b/src/plugin.rs index 30edcc25..b2fde8e6 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -2,7 +2,7 @@ pub use crate::any::Dynamic; pub use crate::engine::Engine; -pub use crate::fn_native::{CallableFunction, NativeCallContext}; +pub use crate::fn_native::{CallableFunction, FnCallArgs, NativeCallContext}; pub use crate::fn_register::{RegisterFn, RegisterResultFn}; pub use crate::module::Module; pub use crate::parser::FnAccess; @@ -25,7 +25,7 @@ pub trait PluginFunction { fn call( &self, context: NativeCallContext, - args: &mut [&mut Dynamic], + args: &mut FnCallArgs, ) -> Result>; /// Is this plugin function a method? diff --git a/src/syntax.rs b/src/syntax.rs index 08b102b7..4e305f41 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -6,7 +6,6 @@ use crate::error::{LexError, ParseError}; use crate::fn_native::{SendSync, Shared}; use crate::parser::Expr; use crate::result::EvalAltResult; -use crate::scope::Scope; use crate::token::{is_valid_identifier, Position, Token}; use crate::StaticVec; @@ -19,12 +18,11 @@ use crate::stdlib::{ /// A general expression evaluation trait object. #[cfg(not(feature = "sync"))] pub type FnCustomSyntaxEval = - dyn Fn(&mut Scope, &mut EvalContext, &[Expression]) -> Result>; + dyn Fn(&mut EvalContext, &[Expression]) -> Result>; /// A general expression evaluation trait object. #[cfg(feature = "sync")] -pub type FnCustomSyntaxEval = dyn Fn(&mut Scope, &mut EvalContext, &[Expression]) -> Result> - + Send - + Sync; +pub type FnCustomSyntaxEval = + dyn Fn(&mut EvalContext, &[Expression]) -> Result> + Send + Sync; /// An expression sub-tree in an AST. #[derive(Debug, Clone, Hash)] @@ -55,7 +53,7 @@ impl Expression<'_> { } } -impl EvalContext<'_, '_, '_, '_, '_, '_> { +impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_> { /// Evaluate an expression tree. /// /// ## WARNING - Low Level API @@ -64,11 +62,10 @@ impl EvalContext<'_, '_, '_, '_, '_, '_> { #[inline(always)] pub fn eval_expression_tree( &mut self, - scope: &mut Scope, expr: &Expression, ) -> Result> { self.engine().eval_expr( - scope, + self.scope, self.mods, self.state, self.namespace(), @@ -101,12 +98,14 @@ impl Engine { /// * `func` is the implementation function. pub fn register_custom_syntax + ToString>( &mut self, - keywords: &[S], + keywords: impl AsRef<[S]>, new_vars: isize, - func: impl Fn(&mut Scope, &mut EvalContext, &[Expression]) -> Result> + func: impl Fn(&mut EvalContext, &[Expression]) -> Result> + SendSync + 'static, ) -> Result<&mut Self, ParseError> { + let keywords = keywords.as_ref(); + let mut segments: StaticVec<_> = Default::default(); for s in keywords { diff --git a/src/token.rs b/src/token.rs index 418765d6..55dc2d38 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1736,31 +1736,33 @@ impl<'a> Iterator for TokenIterator<'a, '_> { } } -/// Tokenize an input text stream. -#[inline] -pub fn lex<'a, 'e>( - input: &'a [&'a str], - map: Option Token>>, - engine: &'e Engine, -) -> TokenIterator<'a, 'e> { - TokenIterator { - engine, - state: TokenizeState { - #[cfg(not(feature = "unchecked"))] - max_string_size: engine.limits_set.max_string_size, - #[cfg(feature = "unchecked")] - max_string_size: 0, - non_unary: false, - comment_level: 0, - end_with_none: false, - include_comments: false, - }, - pos: Position::new(1, 0), - stream: MultiInputsStream { - buf: None, - streams: input.iter().map(|s| s.chars().peekable()).collect(), - index: 0, - }, - map, +impl Engine { + /// Tokenize an input text stream. + #[inline] + pub fn lex<'a, 'e>( + &'e self, + input: impl IntoIterator, + map: Option Token>>, + ) -> TokenIterator<'a, 'e> { + TokenIterator { + engine: self, + state: TokenizeState { + #[cfg(not(feature = "unchecked"))] + max_string_size: self.limits_set.max_string_size, + #[cfg(feature = "unchecked")] + max_string_size: 0, + non_unary: false, + comment_level: 0, + end_with_none: false, + include_comments: false, + }, + pos: Position::new(1, 0), + stream: MultiInputsStream { + buf: None, + streams: input.into_iter().map(|s| s.chars().peekable()).collect(), + index: 0, + }, + map, + } } } diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 4d4e3552..7e9170b0 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -1,7 +1,5 @@ #![cfg(not(feature = "no_function"))] -use rhai::{ - Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseErrorType, RegisterFn, Scope, INT, -}; +use rhai::{Engine, EvalAltResult, FnPtr, Func, ParseErrorType, RegisterFn, Scope, INT}; use std::any::TypeId; #[test] diff --git a/tests/syntax.rs b/tests/syntax.rs index f9c5acda..c0433e9e 100644 --- a/tests/syntax.rs +++ b/tests/syntax.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult, EvalContext, Expression, ParseErrorType, Scope, INT}; +use rhai::{Engine, EvalAltResult, EvalContext, Expression, ParseErrorType, INT}; #[test] fn test_custom_syntax() -> Result<(), Box> { @@ -22,18 +22,18 @@ fn test_custom_syntax() -> Result<(), Box> { "do", "|", "$ident$", "|", "->", "$block$", "while", "$expr$", ], 1, - |scope: &mut Scope, context: &mut EvalContext, inputs: &[Expression]| { + |context: &mut EvalContext, inputs: &[Expression]| { let var_name = inputs[0].get_variable_name().unwrap().to_string(); let stmt = inputs.get(1).unwrap(); let condition = inputs.get(2).unwrap(); - scope.push(var_name, 0 as INT); + context.scope.push(var_name, 0 as INT); loop { - context.eval_expression_tree(scope, stmt)?; + context.eval_expression_tree(stmt)?; let stop = !context - .eval_expression_tree(scope, condition)? + .eval_expression_tree(condition)? .as_bool() .map_err(|err| { Box::new(EvalAltResult::ErrorMismatchDataType( @@ -68,7 +68,7 @@ fn test_custom_syntax() -> Result<(), Box> { // The first symbol must be an identifier assert_eq!( *engine - .register_custom_syntax(&["!"], 0, |_, _, _| Ok(().into())) + .register_custom_syntax(&["!"], 0, |_, _| Ok(().into())) .expect_err("should error") .0, ParseErrorType::BadInput("Improper symbol for custom syntax: '!'".to_string()) diff --git a/tests/var_scope.rs b/tests/var_scope.rs index ede4ef1d..4faf37b5 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -62,7 +62,7 @@ fn test_var_resolver() -> Result<(), Box> { scope.push("chameleon", 123 as INT); scope.push("DO_NOT_USE", 999 as INT); - engine.on_var(|name, _, scope, _| { + engine.on_var(|name, _, context| { match name { "MYSTIC_NUMBER" => Ok(Some((42 as INT).into())), // Override a variable - make it not found even if it exists! @@ -70,9 +70,13 @@ fn test_var_resolver() -> Result<(), Box> { Err(EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::none()).into()) } // Silently maps 'chameleon' into 'innocent'. - "chameleon" => scope.get_value("innocent").map(Some).ok_or_else(|| { - EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::none()).into() - }), + "chameleon" => context + .scope + .get_value("innocent") + .map(Some) + .ok_or_else(|| { + EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::none()).into() + }), // Return Ok(None) to continue with the normal variable resolution process. _ => Ok(None), } From ccba5f218875df9809fcfa3d8bdf52e33e5d5bc2 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 19 Oct 2020 19:21:40 +0800 Subject: [PATCH 4/4] Move custom syntax into separate function. --- src/parser.rs | 131 +++++++++++++++++++++++++++----------------------- 1 file changed, 70 insertions(+), 61 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 8f503caf..0828c532 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -7,7 +7,7 @@ use crate::fn_native::{FnPtr, Shared}; use crate::module::{Module, ModuleRef}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::scope::{EntryType as ScopeEntryType, Scope}; -use crate::syntax::FnCustomSyntaxEval; +use crate::syntax::{CustomSyntax, FnCustomSyntaxEval}; use crate::token::{is_keyword_function, is_valid_identifier, Position, Token, TokenStream}; use crate::utils::StraightHasherBuilder; use crate::{calc_fn_hash, StaticVec}; @@ -2654,6 +2654,74 @@ fn parse_binary_op( } } +/// Parse a custom syntax. +fn parse_custom( + input: &mut TokenStream, + state: &mut ParseState, + lib: &mut FunctionsLib, + mut settings: ParseSettings, + key: &str, + syntax: &CustomSyntax, + pos: Position, +) -> Result { + let mut exprs: StaticVec = Default::default(); + + // Adjust the variables stack + match syntax.scope_delta { + delta if delta > 0 => { + state.stack.resize( + state.stack.len() + delta as usize, + ("".to_string(), ScopeEntryType::Normal), + ); + } + delta if delta < 0 && state.stack.len() <= delta.abs() as usize => state.stack.clear(), + delta if delta < 0 => state + .stack + .truncate(state.stack.len() - delta.abs() as usize), + _ => (), + } + + for segment in syntax.segments.iter() { + settings.pos = input.peek().unwrap().1; + let settings = settings.level_up(); + + match segment.as_str() { + MARKER_IDENT => match input.next().unwrap() { + (Token::Identifier(s), pos) => { + exprs.push(Expr::Variable(Box::new(((s, pos), None, 0, None)))); + } + (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { + return Err(PERR::Reserved(s).into_err(pos)); + } + (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), + }, + MARKER_EXPR => exprs.push(parse_expr(input, state, lib, settings)?), + MARKER_BLOCK => { + let stmt = parse_block(input, state, lib, settings)?; + let pos = stmt.position(); + exprs.push(Expr::Stmt(Box::new((stmt, pos)))) + } + s => match input.peek().unwrap() { + (t, _) if t.syntax().as_ref() == s => { + input.next().unwrap(); + } + (_, pos) => { + return Err(PERR::MissingToken( + s.to_string(), + format!("for '{}' expression", key), + ) + .into_err(*pos)) + } + }, + } + } + + Ok(Expr::Custom(Box::new(( + CustomExpr(exprs, syntax.func.clone()), + pos, + )))) +} + /// Parse an expression. fn parse_expr( input: &mut TokenStream, @@ -2675,67 +2743,8 @@ fn parse_expr( Token::Custom(key) if custom.contains_key(key) => { let custom = custom.get_key_value(key).unwrap(); let (key, syntax) = custom; - input.next().unwrap(); - - let mut exprs: StaticVec = Default::default(); - - // Adjust the variables stack - match syntax.scope_delta { - delta if delta > 0 => { - state.stack.resize( - state.stack.len() + delta as usize, - ("".to_string(), ScopeEntryType::Normal), - ); - } - delta if delta < 0 && state.stack.len() <= delta.abs() as usize => { - state.stack.clear() - } - delta if delta < 0 => state - .stack - .truncate(state.stack.len() - delta.abs() as usize), - _ => (), - } - - for segment in syntax.segments.iter() { - settings.pos = input.peek().unwrap().1; - let settings = settings.level_up(); - - match segment.as_str() { - MARKER_IDENT => match input.next().unwrap() { - (Token::Identifier(s), pos) => { - exprs.push(Expr::Variable(Box::new(((s, pos), None, 0, None)))); - } - (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { - return Err(PERR::Reserved(s).into_err(pos)); - } - (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), - }, - MARKER_EXPR => exprs.push(parse_expr(input, state, lib, settings)?), - MARKER_BLOCK => { - let stmt = parse_block(input, state, lib, settings)?; - let pos = stmt.position(); - exprs.push(Expr::Stmt(Box::new((stmt, pos)))) - } - s => match input.peek().unwrap() { - (t, _) if t.syntax().as_ref() == s => { - input.next().unwrap(); - } - (_, pos) => { - return Err(PERR::MissingToken( - s.to_string(), - format!("for '{}' expression", key), - ) - .into_err(*pos)) - } - }, - } - } - - return Ok(Expr::Custom(Box::new(( - CustomExpr(exprs, syntax.func.clone()), - token_pos, - )))); + return parse_custom(input, state, lib, settings, key, syntax, token_pos); } _ => (), }