Merge branch 'master' into plugins

This commit is contained in:
Stephen Chung 2020-07-21 21:09:04 +08:00
commit c36e459feb
29 changed files with 460 additions and 216 deletions

View File

@ -46,10 +46,12 @@ codegen-units = 1
[dependencies.libm] [dependencies.libm]
version = "0.2.1" version = "0.2.1"
default_features = false
optional = true optional = true
[dependencies.core-error] [dependencies.core-error]
version = "0.0.0" version = "0.0.0"
default_features = false
features = ["alloc"] features = ["alloc"]
optional = true optional = true
@ -66,9 +68,9 @@ features = ["compile-time-rng"]
optional = true optional = true
[dependencies.serde] [dependencies.serde]
package = "serde"
version = "1.0.111" version = "1.0.111"
features = ["derive"] default_features = false
features = ["derive", "alloc"]
optional = true optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]

View File

@ -4,12 +4,17 @@ Rhai Release Notes
Version 0.18.0 Version 0.18.0
============== ==============
This version adds:
* Anonymous functions (in closure syntax). Simplifies creation of ad hoc functions.
New features New features
------------ ------------
* `call` can now be called function-call style for function pointers - this is to handle builds with `no_object`. * `call` can now be called function-call style for function pointers - this is to handle builds with `no_object`.
* Disallow many keywords as variables, such as `print`, `eval`, `call`, `this` etc. * Disallow many keywords as variables, such as `print`, `eval`, `call`, `this` etc.
* `x.call(f, ...)` allows binding `x` to `this` for the function referenced by the function pointer `f`. * `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`.
Version 0.17.0 Version 0.17.0

View File

@ -75,6 +75,7 @@ The Rhai Scripting Language
2. [Overloading](language/overload.md) 2. [Overloading](language/overload.md)
3. [Namespaces](language/fn-namespaces.md) 3. [Namespaces](language/fn-namespaces.md)
4. [Function Pointers](language/fn-ptr.md) 4. [Function Pointers](language/fn-ptr.md)
5. [Anonymous Functions](language/fn-anon.md)
15. [Print and Debug](language/print-debug.md) 15. [Print and Debug](language/print-debug.md)
16. [Modules](language/modules/index.md) 16. [Modules](language/modules/index.md)
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md) 1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)

View File

@ -68,11 +68,13 @@ These symbol types can be used:
### The First Symbol Must be a Keyword ### The First Symbol Must be a Keyword
There is no specific limit on the combination and sequencing of each symbol type, There is no specific limit on the combination and sequencing of each symbol type,
except the _first_ symbol which must be a [custom keyword]. except the _first_ symbol which must be a custom keyword that follows the naming rules
of [variables].
It _cannot_ be a [built-in keyword]({{rootUrl}}/appendix/keywords.md). The first symbol also cannot be a reserved [keyword], unless that keyword
has been [disabled][disable keywords and operators].
However, it _may_ be a built-in keyword that has been [disabled][disable keywords and operators]. In other words, any valid identifier that is not an active [keyword] will work fine.
### The First Symbol Must be Unique ### The First Symbol Must be Unique

View File

@ -20,8 +20,7 @@ engine
// The following all return parse errors. // The following all return parse errors.
engine.compile("let x = if true { 42 } else { 0 };")?; engine.compile("let x = if true { 42 } else { 0 };")?;
// ^ missing ';' after statement end // ^ 'if' is rejected as a reserved keyword
// ^ 'if' is parsed as a variable name
engine.compile("let x = 40 + 2; x += 1;")?; engine.compile("let x = 40 + 2; x += 1;")?;
// ^ '+=' is not recognized as an operator // ^ '+=' is not recognized as an operator

View File

@ -0,0 +1,57 @@
Anonymous Functions
===================
{{#include ../links.md}}
Sometimes it gets tedious to define separate functions only to dispatch them via single [function pointers].
This scenario is especially common when simulating object-oriented programming ([OOP]).
```rust
// Define object
let obj = #{
data: 42,
increment: Fn("inc_obj"), // use function pointers to
decrement: Fn("dec_obj"), // refer to method functions
print: Fn("print_obj")
};
// Define method functions one-by-one
fn inc_obj(x) { this.data += x; }
fn dec_obj(x) { this.data -= x; }
fn print_obj() { print(this.data); }
```
The above can be replaced by using _anonymous functions_ which have the same syntax as Rust's closures
(but they are **NOT** closures, merely syntactic sugar):
```rust
let obj = #{
data: 42,
increment: |x| this.data += x, // one-liner
decrement: |x| this.data -= x,
print_obj: || { print(this.data); } // full function body
};
```
The anonymous functions will be hoisted into separate functions in the global namespace.
The above is equivalent to:
```rust
let obj = #{
data: 42,
increment: Fn("anon_fn_1000"),
decrement: Fn("anon_fn_1001"),
print: Fn("anon_fn_1002")
};
fn anon_fn_1000(x) { this.data += x; }
fn anon_fn_1001(x) { this.data -= x; }
fn anon_fn_1002() { print this.data; }
```
WARNING - NOT Closures
----------------------
Remember: anonymous functions, though having the same syntax as Rust _closures_, are themselves
**not** closures. In particular, they do not capture their running environment. They are more like
Rust's function pointers.

View File

@ -163,4 +163,4 @@ x == 42;
Beware that this only works for _method-call_ style. Normal function-call style cannot bind Beware that this only works for _method-call_ style. Normal function-call style cannot bind
the `this` pointer (for syntactic reasons). the `this` pointer (for syntactic reasons).
Therefore, obviously, binding the `this` pointer is unsupported under [`no_function`]. Therefore, obviously, binding the `this` pointer is unsupported under [`no_object`].

View File

@ -20,17 +20,21 @@ to the [object map] before the function is called. There is no way to simulate
via a normal function-call syntax because all scripted function arguments are passed by value. via a normal function-call syntax because all scripted function arguments are passed by value.
```rust ```rust
fn do_action(x) { print(this.data + x); } // 'this' binds to the object when called fn do_action(x) { this.data += x; } // 'this' binds to the object when called
let obj = #{ let obj = #{
data: 40, data: 40,
action: Fn("do_action") // 'action' holds a function pointer to 'do_action' action: Fn("do_action") // 'action' holds a function pointer to 'do_action'
}; };
obj.action(2); // Short-hand syntax: prints 42 obj.action(2); // Calls 'do_action' with `this` bound to 'obj'
// To achieve the above with normal function pointer calls: obj.call(obj.action, 2); // The above de-sugars to this
fn do_action(map, x) { print(map.data + x); }
obj.action.call(obj, 2); // this call cannot mutate 'obj' obj.data == 42;
// To achieve the above with normal function pointer call will fail.
fn do_action(map, x) { map.data += x; } // 'map' is a copy
obj.action.call(obj, 2); // 'obj' is passed as a copy by value
``` ```

View File

@ -17,23 +17,22 @@ Rhai's [object maps] has [special support for OOP]({{rootUrl}}/language/object-m
| [Object map] properties holding values | properties | | [Object map] properties holding values | properties |
| [Object map] properties that hold [function pointers] | methods | | [Object map] properties that hold [function pointers] | methods |
When a property of an [object map] is called like a method function, and if it happens to hold
a valid [function pointer] (perhaps defined via an [anonymous function]), then the call will be
dispatched to the actual function with `this` binding to the [object map] itself.
Examples Examples
-------- --------
```rust ```rust
// Define the object // Define the object
let obj = #{ let obj =
data: 0, #{
increment: Fn("add"), // when called, 'this' binds to 'obj' data: 0,
update: Fn("update"), // when called, 'this' binds to 'obj' increment: |x| this.data += x, // when called, 'this' binds to 'obj'
action: Fn("action") // when called, 'this' binds to 'obj' update: |x| this.data = x, // when called, 'this' binds to 'obj'
}; action: || print(this.data) // when called, 'this' binds to 'obj'
};
// Define functions
fn add(x) { this.data += x; } // update using 'this'
fn update(x) { this.data = x; } // update using 'this'
fn action() { print(this.data); } // access properties of 'this'
// Use the object // Use the object
obj.increment(1); obj.increment(1);

View File

@ -78,6 +78,8 @@
[function pointers]: {{rootUrl}}/language/fn-ptr.md [function pointers]: {{rootUrl}}/language/fn-ptr.md
[function namespace]: {{rootUrl}}/language/fn-namespaces.md [function namespace]: {{rootUrl}}/language/fn-namespaces.md
[function namespaces]: {{rootUrl}}/language/fn-namespaces.md [function namespaces]: {{rootUrl}}/language/fn-namespaces.md
[anonymous function]: {{rootUrl}}/language/fn-anon.md
[anonymous functions]: {{rootUrl}}/language/fn-anon.md
[`Module`]: {{rootUrl}}/language/modules/index.md [`Module`]: {{rootUrl}}/language/modules/index.md
[module]: {{rootUrl}}/language/modules/index.md [module]: {{rootUrl}}/language/modules/index.md

View File

@ -31,8 +31,8 @@ Opt-Out of Features
------------------ ------------------
Opt out of as many features as possible, if they are not needed, to reduce code size because, remember, by default Opt out of as many features as possible, if they are not needed, to reduce code size because, remember, by default
all code is compiled in as what a script requires cannot be predicted. If a language feature is not needed, all code is compiled into the final binary since what a script requires cannot be predicted.
omitting them via special features is a prudent strategy to optimize the build for size. If a language feature will never be needed, omitting it is a prudent strategy to optimize the build for size.
Omitting arrays ([`no_index`]) yields the most code-size savings, followed by floating-point support Omitting arrays ([`no_index`]) yields the most code-size savings, followed by floating-point support
([`no_float`]), checked arithmetic/script resource limits ([`unchecked`]) and finally object maps and custom types ([`no_object`]). ([`no_float`]), checked arithmetic/script resource limits ([`unchecked`]) and finally object maps and custom types ([`no_object`]).

View File

@ -7,3 +7,9 @@ The feature [`no_std`] automatically converts the scripting engine into a `no-st
Usually, a `no-std` build goes hand-in-hand with [minimal builds] because typical embedded Usually, a `no-std` build goes hand-in-hand with [minimal builds] because typical embedded
hardware (the primary target for `no-std`) has limited storage. hardware (the primary target for `no-std`) has limited storage.
Nightly Required
----------------
Currently, [`no_std`] requires the nightly compiler due to the crates that it uses.

View File

@ -10,7 +10,6 @@ A number of examples can be found in the `examples` folder:
| [`arrays_and_structs`](https://github.com/jonathandturner/rhai/tree/master/examples/arrays_and_structs.rs) | Shows how to register a custom Rust type and using [arrays] on it. | | [`arrays_and_structs`](https://github.com/jonathandturner/rhai/tree/master/examples/arrays_and_structs.rs) | Shows how to register a custom Rust type and using [arrays] on it. |
| [`custom_types_and_methods`](https://github.com/jonathandturner/rhai/tree/master/examples/custom_types_and_methods.rs) | Shows how to register a custom Rust type and methods for it. | | [`custom_types_and_methods`](https://github.com/jonathandturner/rhai/tree/master/examples/custom_types_and_methods.rs) | Shows how to register a custom Rust type and methods for it. |
| [`hello`](https://github.com/jonathandturner/rhai/tree/master/examples/hello.rs) | Simple example that evaluates an expression and prints the result. | | [`hello`](https://github.com/jonathandturner/rhai/tree/master/examples/hello.rs) | Simple example that evaluates an expression and prints the result. |
| [`no_std`](https://github.com/jonathandturner/rhai/tree/master/examples/no_std.rs) | Example to test out `no-std` builds.</br>The [`no_std`] feature is required to build in `no-std`. |
| [`reuse_scope`](https://github.com/jonathandturner/rhai/tree/master/examples/reuse_scope.rs) | Evaluates two pieces of code in separate runs, but using a common [`Scope`]. | | [`reuse_scope`](https://github.com/jonathandturner/rhai/tree/master/examples/reuse_scope.rs) | Evaluates two pieces of code in separate runs, but using a common [`Scope`]. |
| [`rhai_runner`](https://github.com/jonathandturner/rhai/tree/master/examples/rhai_runner.rs) | Runs each filename passed to it as a Rhai script. | | [`rhai_runner`](https://github.com/jonathandturner/rhai/tree/master/examples/rhai_runner.rs) | Runs each filename passed to it as a Rhai script. |
| [`serde`](https://github.com/jonathandturner/rhai/tree/master/examples/serde.rs) | Example to serialize and deserialize Rust types with [`serde`](https://crates.io/crates/serde).<br/>The [`serde`] feature is required to run. | | [`serde`](https://github.com/jonathandturner/rhai/tree/master/examples/serde.rs) | Example to serialize and deserialize Rust types with [`serde`](https://crates.io/crates/serde).<br/>The [`serde`] feature is required to run. |

View File

@ -1,23 +0,0 @@
#![cfg_attr(feature = "no_std", no_std)]
use rhai::{Engine, EvalAltResult, INT};
#[cfg(feature = "no_std")]
extern crate alloc;
#[cfg(feature = "no_std")]
use alloc::boxed::Box;
fn main() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
let result = engine.eval::<INT>("40 + 2")?;
#[cfg(not(feature = "no_std"))]
println!("Answer: {}", result);
#[cfg(feature = "no_std")]
assert_eq!(result, 42);
Ok(())
}

View File

@ -243,12 +243,15 @@ impl fmt::Display for Dynamic {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(value) => fmt::Debug::fmt(value, f), Union::Array(value) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(value) => write!(f, "#{:?}", value), Union::Map(value) => {
f.write_str("#")?;
fmt::Debug::fmt(value, f)
}
Union::FnPtr(value) => fmt::Display::fmt(value, f), Union::FnPtr(value) => fmt::Display::fmt(value, f),
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"), Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
Union::Variant(_) => write!(f, "?"), Union::Variant(value) => write!(f, "{}", (*value).type_name()),
} }
} }
} }
@ -266,12 +269,15 @@ impl fmt::Debug for Dynamic {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(value) => fmt::Debug::fmt(value, f), Union::Array(value) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(value) => write!(f, "#{:?}", value), Union::Map(value) => {
f.write_str("#")?;
fmt::Debug::fmt(value, f)
}
Union::FnPtr(value) => fmt::Display::fmt(value, f), Union::FnPtr(value) => fmt::Display::fmt(value, f),
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"), Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
Union::Variant(_) => write!(f, "<dynamic>"), Union::Variant(value) => write!(f, "{}", (*value).type_name()),
} }
} }
} }

View File

@ -87,6 +87,7 @@ pub const FN_GET: &str = "get$";
pub const FN_SET: &str = "set$"; pub const FN_SET: &str = "set$";
pub const FN_IDX_GET: &str = "index$get$"; pub const FN_IDX_GET: &str = "index$get$";
pub const FN_IDX_SET: &str = "index$set$"; pub const FN_IDX_SET: &str = "index$set$";
pub const FN_ANONYMOUS: &str = "anon$";
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
pub const MARKER_EXPR: &str = "$expr$"; pub const MARKER_EXPR: &str = "$expr$";
@ -187,7 +188,7 @@ impl Target<'_> {
} }
} }
/// Update the value of the `Target`. /// Update the value of the `Target`.
/// Position in `EvalAltResult` is None and must be set afterwards. /// Position in `EvalAltResult` is `None` and must be set afterwards.
pub fn set_value(&mut self, new_val: Dynamic) -> Result<(), Box<EvalAltResult>> { pub fn set_value(&mut self, new_val: Dynamic) -> Result<(), Box<EvalAltResult>> {
match self { match self {
Self::Ref(r) => **r = new_val, Self::Ref(r) => **r = new_val,
@ -418,12 +419,9 @@ pub fn make_getter(id: &str) -> String {
fn extract_prop_from_getter(fn_name: &str) -> Option<&str> { fn extract_prop_from_getter(fn_name: &str) -> Option<&str> {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
if fn_name.starts_with(FN_GET) { if fn_name.starts_with(FN_GET) {
Some(&fn_name[FN_GET.len()..]) return Some(&fn_name[FN_GET.len()..]);
} else {
None
} }
#[cfg(feature = "no_object")]
None None
} }
@ -436,12 +434,9 @@ pub fn make_setter(id: &str) -> String {
fn extract_prop_from_setter(fn_name: &str) -> Option<&str> { fn extract_prop_from_setter(fn_name: &str) -> Option<&str> {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
if fn_name.starts_with(FN_SET) { if fn_name.starts_with(FN_SET) {
Some(&fn_name[FN_SET.len()..]) return Some(&fn_name[FN_SET.len()..]);
} else {
None
} }
#[cfg(feature = "no_object")]
None None
} }
@ -453,7 +448,7 @@ fn default_print(s: &str) {
} }
/// Search for a module within an imports stack. /// Search for a module within an imports stack.
/// Position in `EvalAltResult` is None and must be set afterwards. /// Position in `EvalAltResult` is `None` and must be set afterwards.
fn search_imports<'s>( fn search_imports<'s>(
mods: &'s Imports, mods: &'s Imports,
state: &mut State, state: &mut State,
@ -486,7 +481,7 @@ fn search_imports<'s>(
} }
/// Search for a module within an imports stack. /// Search for a module within an imports stack.
/// Position in `EvalAltResult` is None and must be set afterwards. /// Position in `EvalAltResult` is `None` and must be set afterwards.
fn search_imports_mut<'s>( fn search_imports_mut<'s>(
mods: &'s mut Imports, mods: &'s mut Imports,
state: &mut State, state: &mut State,
@ -636,7 +631,7 @@ impl Engine {
} }
/// Universal method for calling functions either registered with the `Engine` or written in Rhai. /// Universal method for calling functions either registered with the `Engine` or written in Rhai.
/// Position in `EvalAltResult` is None and must be set afterwards. /// Position in `EvalAltResult` is `None` and must be set afterwards.
/// ///
/// ## WARNING /// ## WARNING
/// ///
@ -654,7 +649,7 @@ impl Engine {
args: &mut FnCallArgs, args: &mut FnCallArgs,
is_ref: bool, is_ref: bool,
is_method: bool, is_method: bool,
def_val: Option<&Dynamic>, def_val: Option<bool>,
level: usize, level: usize,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
self.inc_operations(state)?; self.inc_operations(state)?;
@ -709,7 +704,7 @@ impl Engine {
/// This function restores the first argument that was replaced by `normalize_first_arg_of_method_call`. /// This function restores the first argument that was replaced by `normalize_first_arg_of_method_call`.
fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCallArgs<'a>) { fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCallArgs<'a>) {
if let Some(this_pointer) = old_this_ptr { if let Some(this_pointer) = old_this_ptr {
mem::replace(args.get_mut(0).unwrap(), this_pointer); args[0] = this_pointer;
} }
} }
@ -815,7 +810,7 @@ impl Engine {
// Return default value (if any) // Return default value (if any)
if let Some(val) = def_val { if let Some(val) = def_val {
return Ok((val.clone(), false)); return Ok((val.into(), false));
} }
// Getter function not found? // Getter function not found?
@ -877,7 +872,7 @@ impl Engine {
} }
/// Call a script-defined function. /// Call a script-defined function.
/// Position in `EvalAltResult` is None and must be set afterwards. /// Position in `EvalAltResult` is `None` and must be set afterwards.
/// ///
/// ## WARNING /// ## WARNING
/// ///
@ -959,7 +954,7 @@ impl Engine {
} }
/// Perform an actual function call, taking care of special functions /// Perform an actual function call, taking care of special functions
/// Position in `EvalAltResult` is None and must be set afterwards. /// Position in `EvalAltResult` is `None` and must be set afterwards.
/// ///
/// ## WARNING /// ## WARNING
/// ///
@ -976,7 +971,7 @@ impl Engine {
args: &mut FnCallArgs, args: &mut FnCallArgs,
is_ref: bool, is_ref: bool,
is_method: bool, is_method: bool,
def_val: Option<&Dynamic>, def_val: Option<bool>,
level: usize, level: usize,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
@ -1022,7 +1017,7 @@ impl Engine {
} }
/// Evaluate a text string as a script - used primarily for 'eval'. /// Evaluate a text string as a script - used primarily for 'eval'.
/// Position in `EvalAltResult` is None and must be set afterwards. /// Position in `EvalAltResult` is `None` and must be set afterwards.
fn eval_script_expr( fn eval_script_expr(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
@ -1081,7 +1076,6 @@ impl Engine {
let is_ref = target.is_ref(); let is_ref = target.is_ref();
let is_value = target.is_value(); let is_value = target.is_value();
let def_val = def_val.as_ref();
// Get a reference to the mutation target Dynamic // Get a reference to the mutation target Dynamic
let obj = target.as_mut(); let obj = target.as_mut();
@ -1100,7 +1094,7 @@ impl Engine {
// Map it to name(args) in function-call style // Map it to name(args) in function-call style
self.exec_fn_call( self.exec_fn_call(
state, lib, fn_name, *native, hash, args, false, false, def_val, level, state, lib, fn_name, *native, hash, args, false, false, *def_val, level,
) )
} else if fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::<FnPtr>() { } else if fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::<FnPtr>() {
// FnPtr call on object // FnPtr call on object
@ -1120,7 +1114,7 @@ impl Engine {
// Map it to name(args) in function-call style // Map it to name(args) in function-call style
self.exec_fn_call( self.exec_fn_call(
state, lib, &fn_name, *native, hash, args, is_ref, true, def_val, level, state, lib, &fn_name, *native, hash, args, is_ref, true, *def_val, level,
) )
} else { } else {
let redirected: Option<ImmutableString>; let redirected: Option<ImmutableString>;
@ -1146,7 +1140,7 @@ impl Engine {
let args = arg_values.as_mut(); let args = arg_values.as_mut();
self.exec_fn_call( self.exec_fn_call(
state, lib, fn_name, *native, hash, args, is_ref, true, def_val, level, state, lib, fn_name, *native, hash, args, is_ref, true, *def_val, level,
) )
} }
.map_err(|err| err.new_position(*pos))?; .map_err(|err| err.new_position(*pos))?;
@ -1161,7 +1155,7 @@ impl Engine {
} }
/// Chain-evaluate a dot/index chain. /// Chain-evaluate a dot/index chain.
/// Position in `EvalAltResult` is None and must be set afterwards. /// Position in `EvalAltResult` is `None` and must be set afterwards.
fn eval_dot_index_chain_helper( fn eval_dot_index_chain_helper(
&self, &self,
state: &mut State, state: &mut State,
@ -1210,14 +1204,14 @@ impl Engine {
} }
// xxx[rhs] = new_val // xxx[rhs] = new_val
_ if new_val.is_some() => { _ if new_val.is_some() => {
let mut new_val = new_val.unwrap();
let mut idx_val2 = idx_val.clone(); let mut idx_val2 = idx_val.clone();
match self.get_indexed_mut(state, lib, target, idx_val, pos, true, level) { match self.get_indexed_mut(state, lib, target, idx_val, pos, true, level) {
// Indexed value is an owned value - the only possibility is an indexer // Indexed value is an owned value - the only possibility is an indexer
// Try to call an index setter // Try to call an index setter
Ok(obj_ptr) if obj_ptr.is_value() => { Ok(obj_ptr) if obj_ptr.is_value() => {
let args = let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val];
&mut [target.as_mut(), &mut idx_val2, &mut new_val.unwrap()];
self.exec_fn_call( self.exec_fn_call(
state, lib, FN_IDX_SET, true, 0, args, is_ref, true, None, state, lib, FN_IDX_SET, true, 0, args, is_ref, true, None,
@ -1236,17 +1230,13 @@ impl Engine {
// Indexed value is a reference - update directly // Indexed value is a reference - update directly
Ok(ref mut obj_ptr) => { Ok(ref mut obj_ptr) => {
obj_ptr obj_ptr
.set_value(new_val.unwrap()) .set_value(new_val)
.map_err(|err| err.new_position(rhs.position()))?; .map_err(|err| err.new_position(rhs.position()))?;
} }
Err(err) => match *err { Err(err) => match *err {
// No index getter - try to call an index setter // No index getter - try to call an index setter
EvalAltResult::ErrorIndexingType(_, _) => { EvalAltResult::ErrorIndexingType(_, _) => {
let args = &mut [ let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val];
target.as_mut(),
&mut idx_val2,
&mut new_val.unwrap(),
];
self.exec_fn_call( self.exec_fn_call(
state, lib, FN_IDX_SET, true, 0, args, is_ref, true, None, state, lib, FN_IDX_SET, true, 0, args, is_ref, true, None,
@ -1694,13 +1684,12 @@ impl Engine {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(mut rhs_value)) => { Dynamic(Union::Array(mut rhs_value)) => {
let op = "=="; let op = "==";
let def_value = false.into();
let mut scope = Scope::new(); let mut scope = Scope::new();
// Call the `==` operator to compare each value // Call the `==` operator to compare each value
for value in rhs_value.iter_mut() { for value in rhs_value.iter_mut() {
let def_value = Some(false);
let args = &mut [&mut lhs_value.clone(), value]; let args = &mut [&mut lhs_value.clone(), value];
let def_value = Some(&def_value);
let hashes = ( let hashes = (
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
@ -1782,6 +1771,7 @@ impl Engine {
Expr::FloatConstant(x) => Ok(x.0.into()), Expr::FloatConstant(x) => Ok(x.0.into()),
Expr::StringConstant(x) => Ok(x.0.to_string().into()), Expr::StringConstant(x) => Ok(x.0.to_string().into()),
Expr::CharConstant(x) => Ok(x.0.into()), 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 => { Expr::Variable(x) if (x.0).0 == KEYWORD_THIS => {
if let Some(ref val) = this_ptr { if let Some(ref val) = this_ptr {
Ok((*val).clone()) Ok((*val).clone())
@ -1941,7 +1931,6 @@ impl Engine {
// Normal function call // Normal function call
Expr::FnCall(x) if x.1.is_none() => { Expr::FnCall(x) if x.1.is_none() => {
let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref(); let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref();
let def_val = def_val.as_ref();
// Handle Fn() // Handle Fn()
if name == KEYWORD_FN_PTR && args_expr.len() == 1 { if name == KEYWORD_FN_PTR && args_expr.len() == 1 {
@ -2072,7 +2061,7 @@ impl Engine {
let args = args.as_mut(); let args = args.as_mut();
self.exec_fn_call( self.exec_fn_call(
state, lib, name, *native, hash, args, is_ref, false, def_val, level, state, lib, name, *native, hash, args, is_ref, false, *def_val, level,
) )
.map(|(v, _)| v) .map(|(v, _)| v)
.map_err(|err| err.new_position(*pos)) .map_err(|err| err.new_position(*pos))
@ -2167,7 +2156,7 @@ impl Engine {
.map_err(|err| err.new_position(*pos)), .map_err(|err| err.new_position(*pos)),
Err(err) => match *err { Err(err) => match *err {
EvalAltResult::ErrorFunctionNotFound(_, _) if def_val.is_some() => { EvalAltResult::ErrorFunctionNotFound(_, _) if def_val.is_some() => {
Ok(def_val.clone().unwrap()) Ok(def_val.unwrap().into())
} }
EvalAltResult::ErrorFunctionNotFound(_, _) => { EvalAltResult::ErrorFunctionNotFound(_, _) => {
Err(Box::new(EvalAltResult::ErrorFunctionNotFound( Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
@ -2634,7 +2623,7 @@ impl Engine {
} }
/// Check if the number of operations stay within limit. /// Check if the number of operations stay within limit.
/// Position in `EvalAltResult` is None and must be set afterwards. /// Position in `EvalAltResult` is `None` and must be set afterwards.
fn inc_operations(&self, state: &mut State) -> Result<(), Box<EvalAltResult>> { fn inc_operations(&self, state: &mut State) -> Result<(), Box<EvalAltResult>> {
state.operations += 1; state.operations += 1;

View File

@ -206,6 +206,9 @@ impl fmt::Display for ParseErrorType {
Self::FnMissingParams(s) => write!(f, "Expecting parameters for function '{}'", s), Self::FnMissingParams(s) => write!(f, "Expecting parameters for function '{}'", s),
Self::FnMissingBody(s) if s.is_empty() => {
f.write_str("Expecting body statement block for anonymous function")
}
Self::FnMissingBody(s) => { Self::FnMissingBody(s) => {
write!(f, "Expecting body statement block for function '{}'", s) write!(f, "Expecting body statement block for function '{}'", s)
} }

View File

@ -30,6 +30,7 @@ use crate::stdlib::{
}; };
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(feature = "sync")]
use crate::stdlib::sync::RwLock; use crate::stdlib::sync::RwLock;
/// Return type of module-level Rust function. /// Return type of module-level Rust function.
@ -1098,7 +1099,7 @@ impl Module {
/// ///
/// A `StaticVec` is used because most module-level access contains only one level, /// A `StaticVec` is used because most module-level access contains only one level,
/// and it is wasteful to always allocate a `Vec` with one element. /// and it is wasteful to always allocate a `Vec` with one element.
#[derive(Clone, Eq, PartialEq, Default)] #[derive(Clone, Eq, PartialEq, Default, Hash)]
pub struct ModuleRef(StaticVec<(String, Position)>, Option<NonZeroUsize>); pub struct ModuleRef(StaticVec<(String, Position)>, Option<NonZeroUsize>);
impl fmt::Debug for ModuleRef { impl fmt::Debug for ModuleRef {

View File

@ -1,9 +1,12 @@
use crate::any::Dynamic; use crate::any::Dynamic;
use crate::calc_fn_hash; use crate::calc_fn_hash;
use crate::engine::{Engine, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF}; use crate::engine::{
Engine, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
};
use crate::module::Module; use crate::module::Module;
use crate::parser::{map_dynamic_to_expr, Expr, ReturnType, ScriptFnDef, Stmt, AST}; use crate::parser::{map_dynamic_to_expr, Expr, ReturnType, ScriptFnDef, Stmt, AST};
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
use crate::token::is_valid_identifier;
use crate::utils::StaticVec; use crate::utils::StaticVec;
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
@ -397,7 +400,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
// All other items can be thrown away. // All other items can be thrown away.
state.set_dirty(); state.set_dirty();
let pos = m.1; let pos = m.1;
m.0.into_iter().find(|((name, _), _)| name.as_str() == prop) m.0.into_iter().find(|((name, _), _)| name.as_str() == prop.as_str())
.map(|(_, expr)| expr.set_position(pos)) .map(|(_, expr)| expr.set_position(pos))
.unwrap_or_else(|| Expr::Unit(pos)) .unwrap_or_else(|| Expr::Unit(pos))
} }
@ -528,6 +531,19 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
Expr::FnCall(x) Expr::FnCall(x)
} }
// Fn("...")
Expr::FnCall(x)
if x.1.is_none()
&& (x.0).0 == KEYWORD_FN_PTR
&& x.3.len() == 1
&& matches!(x.3[0], Expr::StringConstant(_))
=> {
match &x.3[0] {
Expr::StringConstant(s) if is_valid_identifier(s.0.chars()) => Expr::FnPointer(s.clone()),
_ => Expr::FnCall(x)
}
}
// Eagerly call functions // Eagerly call functions
Expr::FnCall(mut x) Expr::FnCall(mut x)
if x.1.is_none() // Non-qualified if x.1.is_none() // Non-qualified
@ -571,7 +587,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
Some(arg_for_type_of.to_string().into()) Some(arg_for_type_of.to_string().into())
} else { } else {
// Otherwise use the default value, if any // Otherwise use the default value, if any
def_value.clone() def_value.map(|v| v.into())
} }
}) })
.and_then(|result| map_dynamic_to_expr(result, *pos)) .and_then(|result| map_dynamic_to_expr(result, *pos))

View File

@ -7,6 +7,10 @@ use crate::token::Position;
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT; use crate::parser::FLOAT;
#[cfg(not(feature = "no_float"))]
#[cfg(feature = "no_std")]
use num_traits::*;
use num_traits::{ use num_traits::{
identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl,
CheckedShr, CheckedSub, CheckedShr, CheckedSub,

View File

@ -6,6 +6,10 @@ use crate::token::Position;
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT; use crate::parser::FLOAT;
#[cfg(not(feature = "no_float"))]
#[cfg(feature = "no_std")]
use num_traits::*;
use crate::stdlib::{boxed::Box, format, i32, i64}; use crate::stdlib::{boxed::Box, format, i32, i64};
#[cfg(feature = "only_i32")] #[cfg(feature = "only_i32")]

View File

@ -2,7 +2,7 @@
use crate::any::{Dynamic, Union}; use crate::any::{Dynamic, Union};
use crate::calc_fn_hash; use crate::calc_fn_hash;
use crate::engine::{make_getter, make_setter, Engine, KEYWORD_THIS}; use crate::engine::{make_getter, make_setter, Engine, FN_ANONYMOUS, KEYWORD_THIS};
use crate::error::{LexError, ParseError, ParseErrorType}; use crate::error::{LexError, ParseError, ParseErrorType};
use crate::module::{Module, ModuleRef}; use crate::module::{Module, ModuleRef};
use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::optimize::{optimize_into_ast, OptimizationLevel};
@ -25,7 +25,7 @@ use crate::stdlib::{
char, char,
collections::HashMap, collections::HashMap,
fmt, format, fmt, format,
hash::Hash, hash::{Hash, Hasher},
iter::empty, iter::empty,
mem, mem,
num::NonZeroUsize, num::NonZeroUsize,
@ -35,6 +35,12 @@ use crate::stdlib::{
vec::Vec, vec::Vec,
}; };
#[cfg(not(feature = "no_std"))]
use crate::stdlib::collections::hash_map::DefaultHasher;
#[cfg(feature = "no_std")]
use ahash::AHasher;
/// The system integer type. /// The system integer type.
/// ///
/// If the `only_i32` feature is enabled, this will be `i32` instead. /// If the `only_i32` feature is enabled, this will be `i32` instead.
@ -598,21 +604,35 @@ impl Hash for CustomExpr {
} }
} }
#[cfg(not(feature = "no_float"))]
#[derive(Debug, PartialEq, PartialOrd, Clone)]
pub struct FloatWrapper(pub FLOAT, pub Position);
#[cfg(not(feature = "no_float"))]
impl Hash for FloatWrapper {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write(&self.0.to_le_bytes());
self.1.hash(state);
}
}
/// An expression. /// An expression.
/// ///
/// Each variant is at most one pointer in size (for speed), /// Each variant is at most one pointer in size (for speed),
/// with everything being allocated together in one single tuple. /// with everything being allocated together in one single tuple.
#[derive(Debug, Clone)] #[derive(Debug, Clone, Hash)]
pub enum Expr { pub enum Expr {
/// Integer constant. /// Integer constant.
IntegerConstant(Box<(INT, Position)>), IntegerConstant(Box<(INT, Position)>),
/// Floating-point constant. /// Floating-point constant.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
FloatConstant(Box<(FLOAT, Position)>), FloatConstant(Box<FloatWrapper>),
/// Character constant. /// Character constant.
CharConstant(Box<(char, Position)>), CharConstant(Box<(char, Position)>),
/// String constant. /// String constant.
StringConstant(Box<(ImmutableString, Position)>), StringConstant(Box<(ImmutableString, Position)>),
/// FnPtr constant.
FnPointer(Box<(ImmutableString, Position)>),
/// Variable access - ((variable name, position), optional modules, hash, optional index) /// Variable access - ((variable name, position), optional modules, hash, optional index)
Variable( Variable(
Box<( Box<(
@ -623,7 +643,7 @@ pub enum Expr {
)>, )>,
), ),
/// Property access. /// Property access.
Property(Box<((String, String, String), Position)>), Property(Box<((ImmutableString, String, String), Position)>),
/// { stmt } /// { stmt }
Stmt(Box<(Stmt, Position)>), Stmt(Box<(Stmt, Position)>),
/// Wrapped expression - should not be optimized away. /// Wrapped expression - should not be optimized away.
@ -637,7 +657,7 @@ pub enum Expr {
Option<Box<ModuleRef>>, Option<Box<ModuleRef>>,
u64, u64,
StaticVec<Expr>, StaticVec<Expr>,
Option<Dynamic>, Option<bool>,
)>, )>,
), ),
/// expr op= expr /// expr op= expr
@ -673,18 +693,6 @@ impl Default for Expr {
} }
} }
impl Hash for Expr {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
Self::FloatConstant(x) => {
state.write(&x.0.to_le_bytes());
x.1.hash(state);
}
_ => self.hash(state),
}
}
}
impl Expr { impl Expr {
/// Get the `Dynamic` value of a constant expression. /// Get the `Dynamic` value of a constant expression.
/// ///
@ -758,6 +766,7 @@ impl Expr {
Self::IntegerConstant(x) => x.1, Self::IntegerConstant(x) => x.1,
Self::CharConstant(x) => x.1, Self::CharConstant(x) => x.1,
Self::StringConstant(x) => x.1, Self::StringConstant(x) => x.1,
Self::FnPointer(x) => x.1,
Self::Array(x) => x.1, Self::Array(x) => x.1,
Self::Map(x) => x.1, Self::Map(x) => x.1,
Self::Property(x) => x.1, Self::Property(x) => x.1,
@ -791,6 +800,7 @@ impl Expr {
Self::IntegerConstant(x) => x.1 = new_pos, Self::IntegerConstant(x) => x.1 = new_pos,
Self::CharConstant(x) => x.1 = new_pos, Self::CharConstant(x) => x.1 = new_pos,
Self::StringConstant(x) => x.1 = new_pos, Self::StringConstant(x) => x.1 = new_pos,
Self::FnPointer(x) => x.1 = new_pos,
Self::Array(x) => x.1 = new_pos, Self::Array(x) => x.1 = new_pos,
Self::Map(x) => x.1 = new_pos, Self::Map(x) => x.1 = new_pos,
Self::Variable(x) => (x.0).1 = new_pos, Self::Variable(x) => (x.0).1 = new_pos,
@ -847,6 +857,7 @@ impl Expr {
Self::IntegerConstant(_) Self::IntegerConstant(_)
| Self::CharConstant(_) | Self::CharConstant(_)
| Self::StringConstant(_) | Self::StringConstant(_)
| Self::FnPointer(_)
| Self::True(_) | Self::True(_)
| Self::False(_) | Self::False(_)
| Self::Unit(_) => true, | Self::Unit(_) => true,
@ -878,6 +889,7 @@ impl Expr {
Self::IntegerConstant(_) Self::IntegerConstant(_)
| Self::CharConstant(_) | Self::CharConstant(_)
| Self::FnPointer(_)
| Self::In(_) | Self::In(_)
| Self::And(_) | Self::And(_)
| Self::Or(_) | Self::Or(_)
@ -925,7 +937,7 @@ impl Expr {
let (name, pos) = x.0; let (name, pos) = x.0;
let getter = make_getter(&name); let getter = make_getter(&name);
let setter = make_setter(&name); let setter = make_setter(&name);
Self::Property(Box::new(((name.clone(), getter, setter), pos))) Self::Property(Box::new(((name.into(), getter, setter), pos)))
} }
_ => self, _ => self,
} }
@ -1476,7 +1488,7 @@ fn parse_primary(
let mut root_expr = match token { let mut root_expr = match token {
Token::IntegerConstant(x) => Expr::IntegerConstant(Box::new((x, settings.pos))), Token::IntegerConstant(x) => Expr::IntegerConstant(Box::new((x, settings.pos))),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Token::FloatConstant(x) => Expr::FloatConstant(Box::new((x, settings.pos))), Token::FloatConstant(x) => Expr::FloatConstant(Box::new(FloatWrapper(x, settings.pos))),
Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))), Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))),
Token::StringConstant(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))), Token::StringConstant(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))),
Token::Identifier(s) => { Token::Identifier(s) => {
@ -1616,7 +1628,10 @@ fn parse_unary(
.map(|i| Expr::IntegerConstant(Box::new((i, pos)))) .map(|i| Expr::IntegerConstant(Box::new((i, pos))))
.or_else(|| { .or_else(|| {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
return Some(Expr::FloatConstant(Box::new((-(x.0 as FLOAT), pos)))); return Some(Expr::FloatConstant(Box::new(FloatWrapper(
-(x.0 as FLOAT),
pos,
))));
#[cfg(feature = "no_float")] #[cfg(feature = "no_float")]
return None; return None;
}) })
@ -1625,7 +1640,9 @@ fn parse_unary(
// Negative float // Negative float
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Expr::FloatConstant(x) => Ok(Expr::FloatConstant(Box::new((-x.0, x.1)))), Expr::FloatConstant(x) => {
Ok(Expr::FloatConstant(Box::new(FloatWrapper(-x.0, x.1))))
}
// Call negative function // Call negative function
expr => { expr => {
@ -1664,9 +1681,38 @@ fn parse_unary(
None, None,
hash, hash,
args, args,
Some(false.into()), // NOT operator, when operating on invalid operand, defaults to false Some(false), // NOT operator, when operating on invalid operand, defaults to false
)))) ))))
} }
// | ...
#[cfg(not(feature = "no_function"))]
Token::Pipe | Token::Or => {
let mut state = ParseState::new(
state.engine,
state.max_function_expr_depth,
state.max_function_expr_depth,
);
let settings = ParseSettings {
allow_if_expr: true,
allow_stmt_expr: true,
allow_anonymous_fn: true,
is_global: false,
is_function_scope: true,
is_breakable: false,
level: 0,
pos: *token_pos,
};
let (expr, func) = parse_anon_fn(input, &mut state, lib, settings)?;
// Qualifiers (none) + function name + number of arguments.
let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty());
lib.insert(hash, func);
Ok(expr)
}
// <EOF> // <EOF>
Token::EOF => Err(PERR::UnexpectedEOF.into_err(settings.pos)), Token::EOF => Err(PERR::UnexpectedEOF.into_err(settings.pos)),
// All other tokens // All other tokens
@ -1788,7 +1834,7 @@ fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, ParseEr
let getter = make_getter(&name); let getter = make_getter(&name);
let setter = make_setter(&name); let setter = make_setter(&name);
let rhs = Expr::Property(Box::new(((name, getter, setter), pos))); let rhs = Expr::Property(Box::new(((name.into(), getter, setter), pos)));
Expr::Dot(Box::new((lhs, rhs, op_pos))) Expr::Dot(Box::new((lhs, rhs, op_pos)))
} }
@ -2018,7 +2064,7 @@ fn parse_binary_op(
settings.pos = pos; settings.pos = pos;
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
let cmp_def = Some(false.into()); let cmp_def = Some(false);
let op = op_token.syntax(); let op = op_token.syntax();
let hash = calc_fn_hash(empty(), &op, 2, empty()); let hash = calc_fn_hash(empty(), &op, 2, empty());
let op = (op, true, pos); let op = (op, true, pos);
@ -2041,7 +2087,7 @@ fn parse_binary_op(
| Token::XOr => Expr::FnCall(Box::new((op, None, hash, args, None))), | Token::XOr => Expr::FnCall(Box::new((op, None, hash, args, None))),
// '!=' defaults to true when passed invalid operands // '!=' defaults to true when passed invalid operands
Token::NotEqualsTo => Expr::FnCall(Box::new((op, None, hash, args, Some(true.into())))), Token::NotEqualsTo => Expr::FnCall(Box::new((op, None, hash, args, Some(true)))),
// Comparison operators default to false when passed invalid operands // Comparison operators default to false when passed invalid operands
Token::EqualsTo Token::EqualsTo
@ -2151,9 +2197,6 @@ fn parse_expr(
exprs.push(Expr::Stmt(Box::new((stmt, pos)))) exprs.push(Expr::Stmt(Box::new((stmt, pos))))
} }
s => match input.peek().unwrap() { s => match input.peek().unwrap() {
(Token::Custom(custom), _) if custom == s => {
input.next().unwrap();
}
(t, _) if t.syntax().as_ref() == s => { (t, _) if t.syntax().as_ref() == s => {
input.next().unwrap(); input.next().unwrap();
} }
@ -2765,32 +2808,28 @@ fn parse_fn(
let mut params = Vec::new(); let mut params = Vec::new();
if !match_token(input, Token::RightParen)? { if !match_token(input, Token::RightParen)? {
let end_err = format!("to close the parameters list of function '{}'", name);
let sep_err = format!("to separate the parameters of function '{}'", name); let sep_err = format!("to separate the parameters of function '{}'", name);
loop { loop {
match input.peek().unwrap() { match input.next().unwrap() {
(Token::RightParen, _) => (), (Token::RightParen, _) => break,
_ => match input.next().unwrap() { (Token::Identifier(s), pos) => {
(Token::Identifier(s), pos) => { state.stack.push((s.clone(), ScopeEntryType::Normal));
state.stack.push((s.clone(), ScopeEntryType::Normal)); params.push((s, pos))
params.push((s, pos)) }
} (Token::LexError(err), pos) => return Err(err.into_err(pos)),
(Token::LexError(err), pos) => return Err(err.into_err(pos)), (_, pos) => {
(_, pos) => { return Err(PERR::MissingToken(
return Err( Token::RightParen.into(),
PERR::MissingToken(Token::RightParen.into(), end_err).into_err(pos) format!("to close the parameters list of function '{}'", name),
) )
} .into_err(pos))
}, }
} }
match input.next().unwrap() { match input.next().unwrap() {
(Token::RightParen, _) => break, (Token::RightParen, _) => break,
(Token::Comma, _) => (), (Token::Comma, _) => (),
(Token::Identifier(_), pos) => {
return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos))
}
(Token::LexError(err), pos) => return Err(err.into_err(pos)), (Token::LexError(err), pos) => return Err(err.into_err(pos)),
(_, pos) => { (_, pos) => {
return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos)) return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos))
@ -2834,6 +2873,101 @@ fn parse_fn(
}) })
} }
/// Parse an anonymous function definition.
#[cfg(not(feature = "no_function"))]
fn parse_anon_fn(
input: &mut TokenStream,
state: &mut ParseState,
lib: &mut FunctionsLib,
mut settings: ParseSettings,
) -> Result<(Expr, ScriptFnDef), ParseError> {
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
let mut params = Vec::new();
if input.next().unwrap().0 != Token::Or {
if !match_token(input, Token::Pipe)? {
loop {
match input.next().unwrap() {
(Token::Pipe, _) => break,
(Token::Identifier(s), pos) => {
state.stack.push((s.clone(), ScopeEntryType::Normal));
params.push((s, pos))
}
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
(_, pos) => {
return Err(PERR::MissingToken(
Token::Pipe.into(),
"to close the parameters list of anonymous function".into(),
)
.into_err(pos))
}
}
match input.next().unwrap() {
(Token::Pipe, _) => break,
(Token::Comma, _) => (),
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
(_, pos) => {
return Err(PERR::MissingToken(
Token::Comma.into(),
"to separate the parameters of anonymous function".into(),
)
.into_err(pos))
}
}
}
}
}
// Check for duplicating parameters
params
.iter()
.enumerate()
.try_for_each(|(i, (p1, _))| {
params
.iter()
.skip(i + 1)
.find(|(p2, _)| p2 == p1)
.map_or_else(|| Ok(()), |(p2, pos)| Err((p2, *pos)))
})
.map_err(|(p, pos)| PERR::FnDuplicatedParam("".to_string(), p.to_string()).into_err(pos))?;
// Parse function body
settings.is_breakable = false;
let pos = input.peek().unwrap().1;
let body = parse_stmt(input, state, lib, settings.level_up())
.map(|stmt| stmt.unwrap_or_else(|| Stmt::Noop(pos)))?;
let params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect();
// Calculate hash
#[cfg(feature = "no_std")]
let mut s: AHasher = Default::default();
#[cfg(not(feature = "no_std"))]
let mut s = DefaultHasher::new();
s.write_usize(params.len());
params.iter().for_each(|a| a.hash(&mut s));
body.hash(&mut s);
let hash = s.finish();
// Create unique function name
let fn_name = format!("{}{}", FN_ANONYMOUS, hash);
let script = ScriptFnDef {
name: fn_name.clone(),
access: FnAccess::Public,
params,
body,
pos: settings.pos,
};
let expr = Expr::FnPointer(Box::new((fn_name.into(), settings.pos)));
Ok((expr, script))
}
impl Engine { impl Engine {
pub(crate) fn parse_global_expr( pub(crate) fn parse_global_expr(
&self, &self,
@ -2954,7 +3088,7 @@ impl Engine {
pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option<Expr> { pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option<Expr> {
match value.0 { match value.0 {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(value) => Some(Expr::FloatConstant(Box::new((value, pos)))), Union::Float(value) => Some(Expr::FloatConstant(Box::new(FloatWrapper(value, pos)))),
Union::Unit(_) => Some(Expr::Unit(pos)), Union::Unit(_) => Some(Expr::Unit(pos)),
Union::Int(value) => Some(Expr::IntegerConstant(Box::new((value, pos)))), Union::Int(value) => Some(Expr::IntegerConstant(Box::new((value, pos)))),

View File

@ -2,7 +2,7 @@ use crate::engine::Engine;
use crate::module::ModuleResolver; use crate::module::ModuleResolver;
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
use crate::packages::PackageLibrary; use crate::packages::PackageLibrary;
use crate::token::is_valid_identifier; use crate::token::{is_valid_identifier, Token};
use crate::stdlib::{boxed::Box, format, string::String}; use crate::stdlib::{boxed::Box, format, string::String};
@ -183,8 +183,7 @@ impl Engine {
/// engine.disable_symbol("if"); // disable the 'if' keyword /// engine.disable_symbol("if"); // disable the 'if' keyword
/// ///
/// engine.compile("let x = if true { 42 } else { 0 };")?; /// engine.compile("let x = if true { 42 } else { 0 };")?;
/// // ^ 'if' is parsed as a variable name /// // ^ 'if' is rejected as a reserved keyword
/// // ^ missing ';' after statement end
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
@ -252,6 +251,24 @@ impl Engine {
return Err(format!("not a valid identifier: '{}'", keyword).into()); return Err(format!("not a valid identifier: '{}'", keyword).into());
} }
match Token::lookup_from_syntax(keyword) {
// Standard identifiers, reserved keywords and custom keywords are OK
None | Some(Token::Reserved(_)) | Some(Token::Custom(_)) => (),
// Disabled keywords are also OK
Some(token)
if !self
.disabled_symbols
.as_ref()
.map(|d| d.contains(token.syntax().as_ref()))
.unwrap_or(false) =>
{
()
}
// Active standard keywords cannot be made custom
Some(_) => return Err(format!("'{}' is a reserved keyword", keyword).into()),
}
// Add to custom keywords
if self.custom_keywords.is_none() { if self.custom_keywords.is_none() {
self.custom_keywords = Some(Default::default()); self.custom_keywords = Some(Default::default());
} }

View File

@ -88,11 +88,16 @@ impl Engine {
MARKER_EXPR | MARKER_BLOCK | MARKER_IDENT if !segments.is_empty() => s.to_string(), MARKER_EXPR | MARKER_BLOCK | MARKER_IDENT if !segments.is_empty() => s.to_string(),
// Standard symbols not in first position // Standard symbols not in first position
s if !segments.is_empty() && Token::lookup_from_syntax(s).is_some() => { s if !segments.is_empty() && Token::lookup_from_syntax(s).is_some() => {
// Make it a custom keyword/operator if it is a disabled standard keyword/operator
// or a reserved keyword/operator.
if self if self
.disabled_symbols .disabled_symbols
.as_ref() .as_ref()
.map(|d| d.contains(s)) .map(|d| d.contains(s))
.unwrap_or(false) .unwrap_or(false)
|| Token::lookup_from_syntax(s)
.map(|token| token.is_reserved())
.unwrap_or(false)
{ {
// If symbol is disabled, make it a custom keyword // If symbol is disabled, make it a custom keyword
if self.custom_keywords.is_none() { if self.custom_keywords.is_none() {

View File

@ -1334,73 +1334,81 @@ impl<'a> Iterator for TokenIterator<'a, '_> {
self.engine.disabled_symbols.as_ref(), self.engine.disabled_symbols.as_ref(),
self.engine.custom_keywords.as_ref(), self.engine.custom_keywords.as_ref(),
) { ) {
// {EOF}
(None, _, _) => None, (None, _, _) => None,
(Some((Token::Reserved(s), pos)), None, None) => return Some((match s.as_str() { // Reserved keyword/symbol
"===" => Token::LexError(Box::new(LERR::ImproperSymbol( (Some((Token::Reserved(s), pos)), disabled, custom) => Some((match
"'===' is not a valid operator. This is not JavaScript! Should it be '=='?" (s.as_str(), custom.map(|c| c.contains_key(&s)).unwrap_or(false))
.to_string(), {
("===", false) => Token::LexError(Box::new(LERR::ImproperSymbol(
"'===' is not a valid operator. This is not JavaScript! Should it be '=='?".to_string(),
))), ))),
"!==" => Token::LexError(Box::new(LERR::ImproperSymbol( ("!==", false) => Token::LexError(Box::new(LERR::ImproperSymbol(
"'!==' is not a valid operator. This is not JavaScript! Should it be '!='?" "'!==' is not a valid operator. This is not JavaScript! Should it be '!='?".to_string(),
.to_string(),
))), ))),
"->" => Token::LexError(Box::new(LERR::ImproperSymbol( ("->", false) => Token::LexError(Box::new(LERR::ImproperSymbol(
"'->' is not a valid symbol. This is not C or C++!".to_string(), "'->' is not a valid symbol. This is not C or C++!".to_string()))),
))), ("<-", false) => Token::LexError(Box::new(LERR::ImproperSymbol(
"<-" => Token::LexError(Box::new(LERR::ImproperSymbol(
"'<-' is not a valid symbol. This is not Go! Should it be '<='?".to_string(), "'<-' is not a valid symbol. This is not Go! Should it be '<='?".to_string(),
))), ))),
"=>" => Token::LexError(Box::new(LERR::ImproperSymbol( ("=>", false) => Token::LexError(Box::new(LERR::ImproperSymbol(
"'=>' is not a valid symbol. This is not Rust! Should it be '>='?" "'=>' is not a valid symbol. This is not Rust! Should it be '>='?".to_string(),
.to_string(),
))), ))),
":=" => Token::LexError(Box::new(LERR::ImproperSymbol( (":=", false) => Token::LexError(Box::new(LERR::ImproperSymbol(
"':=' is not a valid assignment operator. This is not Go! Should it be simply '='?" "':=' is not a valid assignment operator. This is not Go! Should it be simply '='?".to_string(),
.to_string(),
))), ))),
"::<" => Token::LexError(Box::new(LERR::ImproperSymbol( ("::<", false) => Token::LexError(Box::new(LERR::ImproperSymbol(
"'::<>' is not a valid symbol. This is not Rust! Should it be '::'?" "'::<>' is not a valid symbol. This is not Rust! Should it be '::'?".to_string(),
.to_string(),
))), ))),
"(*" | "*)" => Token::LexError(Box::new(LERR::ImproperSymbol( ("(*", false) | ("*)", false) => Token::LexError(Box::new(LERR::ImproperSymbol(
"'(* .. *)' is not a valid comment format. This is not Pascal! Should it be '/* .. */'?" "'(* .. *)' is not a valid comment format. This is not Pascal! Should it be '/* .. */'?".to_string(),
.to_string(),
))), ))),
"#" => Token::LexError(Box::new(LERR::ImproperSymbol( ("#", false) => Token::LexError(Box::new(LERR::ImproperSymbol(
"'#' is not a valid symbol. Should it be '#{'?" "'#' is not a valid symbol. Should it be '#{'?".to_string(),
.to_string(),
))), ))),
token if !is_valid_identifier(token.chars()) => Token::LexError(Box::new(LERR::ImproperSymbol( // Reserved keyword/operator that is custom.
format!("'{}' is a reserved symbol.", token) (_, true) => Token::Custom(s),
// Reserved operator that is not custom.
(token, false) if !is_valid_identifier(token.chars()) => Token::LexError(Box::new(LERR::ImproperSymbol(
format!("'{}' is a reserved symbol", token)
))), ))),
_ => Token::Reserved(s) // Reserved keyword that is not custom and disabled.
(token, false) if disabled.map(|d| d.contains(token)).unwrap_or(false) => Token::LexError(Box::new(LERR::ImproperSymbol(
format!("reserved symbol '{}' is disabled", token)
))),
// Reserved keyword/operator that is not custom.
(_, false) => Token::Reserved(s),
}, pos)), }, pos)),
(r @ Some(_), None, None) => r, // Custom keyword
(Some((Token::Identifier(s), pos)), _, Some(custom)) if custom.contains_key(&s) => { (Some((Token::Identifier(s), pos)), _, Some(custom)) if custom.contains_key(&s) => {
// Convert custom keywords
Some((Token::Custom(s), pos)) Some((Token::Custom(s), pos))
} }
(Some((token, pos)), _, Some(custom)) // Custom standard keyword - must be disabled
if (token.is_keyword() || token.is_operator() || token.is_reserved()) (Some((token, pos)), Some(disabled), Some(custom))
&& custom.contains_key(token.syntax().as_ref()) => if token.is_keyword() && custom.contains_key(token.syntax().as_ref()) =>
{ {
// Convert into custom keywords if disabled.contains(token.syntax().as_ref()) {
Some((Token::Custom(token.syntax().into()), pos)) // Disabled standard keyword
Some((Token::Custom(token.syntax().into()), pos))
} else {
// Active standard keyword - should never be a custom keyword!
unreachable!()
}
} }
// Disabled operator
(Some((token, pos)), Some(disabled), _) (Some((token, pos)), Some(disabled), _)
if token.is_operator() && disabled.contains(token.syntax().as_ref()) => if token.is_operator() && disabled.contains(token.syntax().as_ref()) =>
{ {
// Convert disallowed operators into lex errors
Some(( Some((
Token::LexError(Box::new(LexError::UnexpectedInput(token.syntax().into()))), Token::LexError(Box::new(LexError::UnexpectedInput(token.syntax().into()))),
pos, pos,
)) ))
} }
// Disabled standard keyword
(Some((token, pos)), Some(disabled), _) (Some((token, pos)), Some(disabled), _)
if token.is_keyword() && disabled.contains(token.syntax().as_ref()) => if token.is_keyword() && disabled.contains(token.syntax().as_ref()) =>
{ {
// Convert disallowed keywords into identifiers Some((Token::Reserved(token.syntax().into()), pos))
Some((Token::Identifier(token.syntax().into()), pos))
} }
(r, _, _) => r, (r, _, _) => r,
} }

View File

@ -1,7 +1,6 @@
#![cfg(not(feature = "no_function"))] #![cfg(not(feature = "no_function"))]
use rhai::{ use rhai::{
Dynamic, Engine, EvalAltResult, FnPtr, Func, ImmutableString, Module, ParseError, Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseError, ParseErrorType, Scope, INT,
ParseErrorType, Scope, INT,
}; };
#[test] #[test]

View File

@ -1,6 +1,6 @@
#![cfg(not(feature = "no_module"))] #![cfg(not(feature = "no_module"))]
use rhai::{ use rhai::{
module_resolvers::StaticModuleResolver, Engine, EvalAltResult, Module, ParseError, module_resolvers::StaticModuleResolver, Dynamic, Engine, EvalAltResult, Module, ParseError,
ParseErrorType, Scope, INT, ParseErrorType, Scope, INT,
}; };
@ -26,6 +26,7 @@ fn test_module_sub_module() -> Result<(), Box<EvalAltResult>> {
sub_module.set_sub_module("universe", sub_module2); sub_module.set_sub_module("universe", sub_module2);
module.set_sub_module("life", sub_module); module.set_sub_module("life", sub_module);
module.set_var("MYSTIC_NUMBER", Dynamic::from(42 as INT));
assert!(module.contains_sub_module("life")); assert!(module.contains_sub_module("life"));
let m = module.get_sub_module("life").unwrap(); let m = module.get_sub_module("life").unwrap();
@ -44,18 +45,16 @@ fn test_module_sub_module() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.set_module_resolver(Some(resolver)); engine.set_module_resolver(Some(resolver));
let mut scope = Scope::new();
assert_eq!( assert_eq!(
engine.eval_with_scope::<INT>( engine.eval::<INT>(r#"import "question" as q; q::MYSTIC_NUMBER"#)?,
&mut scope,
r#"import "question" as q; q::life::universe::answer + 1"#
)?,
42 42
); );
assert_eq!( assert_eq!(
engine.eval_with_scope::<INT>( engine.eval::<INT>(r#"import "question" as q; q::life::universe::answer + 1"#)?,
&mut scope, 42
);
assert_eq!(
engine.eval::<INT>(
r#"import "question" as q; q::life::universe::inc(q::life::universe::answer)"# r#"import "question" as q; q::life::universe::inc(q::life::universe::answer)"#
)?, )?,
42 42
@ -221,36 +220,30 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
resolver2.insert("testing", module); resolver2.insert("testing", module);
engine.set_module_resolver(Some(resolver2)); engine.set_module_resolver(Some(resolver2));
let mut scope = Scope::new();
assert_eq!( assert_eq!(
engine.eval_with_scope::<INT>(&mut scope, r#"import "testing" as ttt; ttt::abc"#)?, engine.eval::<INT>(r#"import "testing" as ttt; ttt::abc"#)?,
123 123
); );
assert_eq!( assert_eq!(
engine.eval_with_scope::<INT>(&mut scope, r#"import "testing" as ttt; ttt::foo"#)?, engine.eval::<INT>(r#"import "testing" as ttt; ttt::foo"#)?,
42 42
); );
assert!(engine assert!(engine.eval::<bool>(r#"import "testing" as ttt; ttt::extra::foo"#)?);
.eval_with_scope::<bool>(&mut scope, r#"import "testing" as ttt; ttt::extra::foo"#)?);
assert_eq!( assert_eq!(
engine.eval_with_scope::<String>(&mut scope, r#"import "testing" as ttt; ttt::hello"#)?, engine.eval::<String>(r#"import "testing" as ttt; ttt::hello"#)?,
"hello, 42 worlds!" "hello, 42 worlds!"
); );
assert_eq!( assert_eq!(
engine.eval_with_scope::<INT>(&mut scope, r#"import "testing" as ttt; ttt::calc(999)"#)?, engine.eval::<INT>(r#"import "testing" as ttt; ttt::calc(999)"#)?,
1000 1000
); );
assert_eq!( assert_eq!(
engine.eval_with_scope::<INT>( engine.eval::<INT>(r#"import "testing" as ttt; ttt::add_len(ttt::foo, ttt::hello)"#)?,
&mut scope,
r#"import "testing" as ttt; ttt::add_len(ttt::foo, ttt::hello)"#
)?,
59 59
); );
assert!(matches!( assert!(matches!(
*engine *engine
.eval_with_scope::<()>(&mut scope, r#"import "testing" as ttt; ttt::hidden()"#) .consume(r#"import "testing" as ttt; ttt::hidden()"#)
.expect_err("should error"), .expect_err("should error"),
EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "ttt::hidden" EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "ttt::hidden"
)); ));

View File

@ -1,16 +1,25 @@
#![cfg(feature = "internals")] #![cfg(feature = "internals")]
use rhai::{ use rhai::{
Dynamic, Engine, EvalAltResult, EvalState, Expression, Imports, LexError, Module, Scope, INT, Dynamic, Engine, EvalAltResult, EvalState, Expression, Imports, LexError, Module, ParseError,
ParseErrorType, Scope, INT,
}; };
#[test] #[test]
fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> { fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.consume("while false {}")?;
// Disable 'while' and make sure it still works with custom syntax // Disable 'while' and make sure it still works with custom syntax
engine.disable_symbol("while"); engine.disable_symbol("while");
engine.consume("while false {}").expect_err("should error"); assert!(matches!(
engine.consume("let while = 0")?; *engine.compile("while false {}").expect_err("should error").0,
ParseErrorType::Reserved(err) if err == "while"
));
assert!(matches!(
*engine.compile("let while = 0").expect_err("should error").0,
ParseErrorType::Reserved(err) if err == "while"
));
engine engine
.register_custom_syntax( .register_custom_syntax(

View File

@ -7,8 +7,11 @@ fn test_tokens_disabled() {
engine.disable_symbol("if"); // disable the 'if' keyword engine.disable_symbol("if"); // disable the 'if' keyword
assert!(matches!( assert!(matches!(
*engine.compile("let x = if true { 42 } else { 0 };").expect_err("should error").0, *engine
ParseErrorType::MissingToken(ref token, _) if token == ";" .compile("let x = if true { 42 } else { 0 };")
.expect_err("should error")
.0,
ParseErrorType::Reserved(err) if err == "if"
)); ));
engine.disable_symbol("+="); // disable the '+=' operator engine.disable_symbol("+="); // disable the '+=' operator