diff --git a/Cargo.toml b/Cargo.toml
index fe72ee21..bf4e0dc2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -46,10 +46,12 @@ codegen-units = 1
[dependencies.libm]
version = "0.2.1"
+default_features = false
optional = true
[dependencies.core-error]
version = "0.0.0"
+default_features = false
features = ["alloc"]
optional = true
@@ -66,9 +68,9 @@ features = ["compile-time-rng"]
optional = true
[dependencies.serde]
-package = "serde"
version = "1.0.111"
-features = ["derive"]
+default_features = false
+features = ["derive", "alloc"]
optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies]
diff --git a/RELEASES.md b/RELEASES.md
index 9a48b709..b09e10ce 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -4,12 +4,17 @@ Rhai Release Notes
Version 0.18.0
==============
+This version adds:
+
+* Anonymous functions (in closure syntax). Simplifies creation of ad hoc functions.
+
New features
------------
* `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.
* `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
diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md
index e344c306..d86aae6a 100644
--- a/doc/src/SUMMARY.md
+++ b/doc/src/SUMMARY.md
@@ -75,6 +75,7 @@ The Rhai Scripting Language
2. [Overloading](language/overload.md)
3. [Namespaces](language/fn-namespaces.md)
4. [Function Pointers](language/fn-ptr.md)
+ 5. [Anonymous Functions](language/fn-anon.md)
15. [Print and Debug](language/print-debug.md)
16. [Modules](language/modules/index.md)
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)
diff --git a/doc/src/engine/custom-syntax.md b/doc/src/engine/custom-syntax.md
index e0529841..0bd9fb30 100644
--- a/doc/src/engine/custom-syntax.md
+++ b/doc/src/engine/custom-syntax.md
@@ -68,11 +68,13 @@ These symbol types can be used:
### The First Symbol Must be a Keyword
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
diff --git a/doc/src/engine/disable.md b/doc/src/engine/disable.md
index 3810fa5e..28e35f70 100644
--- a/doc/src/engine/disable.md
+++ b/doc/src/engine/disable.md
@@ -20,8 +20,7 @@ engine
// The following all return parse errors.
engine.compile("let x = if true { 42 } else { 0 };")?;
-// ^ missing ';' after statement end
-// ^ 'if' is parsed as a variable name
+// ^ 'if' is rejected as a reserved keyword
engine.compile("let x = 40 + 2; x += 1;")?;
// ^ '+=' is not recognized as an operator
diff --git a/doc/src/language/fn-anon.md b/doc/src/language/fn-anon.md
new file mode 100644
index 00000000..21ceb7a3
--- /dev/null
+++ b/doc/src/language/fn-anon.md
@@ -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.
diff --git a/doc/src/language/fn-ptr.md b/doc/src/language/fn-ptr.md
index 57f2f24f..833cfaa2 100644
--- a/doc/src/language/fn-ptr.md
+++ b/doc/src/language/fn-ptr.md
@@ -163,4 +163,4 @@ x == 42;
Beware that this only works for _method-call_ style. Normal function-call style cannot bind
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`].
diff --git a/doc/src/language/object-maps-oop.md b/doc/src/language/object-maps-oop.md
index 4727871b..c93f6847 100644
--- a/doc/src/language/object-maps-oop.md
+++ b/doc/src/language/object-maps-oop.md
@@ -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.
```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 = #{
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:
-fn do_action(map, x) { print(map.data + x); }
+obj.call(obj.action, 2); // The above de-sugars to this
-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
```
diff --git a/doc/src/language/oop.md b/doc/src/language/oop.md
index 120b42d0..e34a8ab2 100644
--- a/doc/src/language/oop.md
+++ b/doc/src/language/oop.md
@@ -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 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
--------
```rust
// Define the object
-let obj = #{
- data: 0,
- increment: Fn("add"), // when called, 'this' binds to 'obj'
- update: Fn("update"), // when called, 'this' binds to 'obj'
- action: Fn("action") // 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'
+let obj =
+ #{
+ data: 0,
+ increment: |x| this.data += x, // 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'
+ };
// Use the object
obj.increment(1);
diff --git a/doc/src/links.md b/doc/src/links.md
index 82ceab3b..a26a34e8 100644
--- a/doc/src/links.md
+++ b/doc/src/links.md
@@ -78,6 +78,8 @@
[function pointers]: {{rootUrl}}/language/fn-ptr.md
[function namespace]: {{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
diff --git a/doc/src/start/builds/minimal.md b/doc/src/start/builds/minimal.md
index d1da0f01..46b85007 100644
--- a/doc/src/start/builds/minimal.md
+++ b/doc/src/start/builds/minimal.md
@@ -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
-all code is compiled in as what a script requires cannot be predicted. If a language feature is not needed,
-omitting them via special features is a prudent strategy to optimize the build for size.
+all code is compiled into the final binary since what a script requires cannot be predicted.
+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
([`no_float`]), checked arithmetic/script resource limits ([`unchecked`]) and finally object maps and custom types ([`no_object`]).
diff --git a/doc/src/start/builds/no-std.md b/doc/src/start/builds/no-std.md
index 22b9b025..46a8e986 100644
--- a/doc/src/start/builds/no-std.md
+++ b/doc/src/start/builds/no-std.md
@@ -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
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.
diff --git a/doc/src/start/examples/rust.md b/doc/src/start/examples/rust.md
index b7b6a5a5..3784778d 100644
--- a/doc/src/start/examples/rust.md
+++ b/doc/src/start/examples/rust.md
@@ -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. |
| [`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. |
-| [`no_std`](https://github.com/jonathandturner/rhai/tree/master/examples/no_std.rs) | Example to test out `no-std` builds.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`]. |
| [`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).
The [`serde`] feature is required to run. |
diff --git a/examples/no_std.rs b/examples/no_std.rs
deleted file mode 100644
index a6902f72..00000000
--- a/examples/no_std.rs
+++ /dev/null
@@ -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> {
- let engine = Engine::new();
-
- let result = engine.eval::("40 + 2")?;
-
- #[cfg(not(feature = "no_std"))]
- println!("Answer: {}", result);
-
- #[cfg(feature = "no_std")]
- assert_eq!(result, 42);
-
- Ok(())
-}
diff --git a/src/any.rs b/src/any.rs
index 5182da08..8ff20298 100644
--- a/src/any.rs
+++ b/src/any.rs
@@ -243,12 +243,15 @@ impl fmt::Display for Dynamic {
#[cfg(not(feature = "no_index"))]
Union::Array(value) => fmt::Debug::fmt(value, f),
#[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),
#[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::() => write!(f, ""),
- 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"))]
Union::Array(value) => fmt::Debug::fmt(value, f),
#[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),
#[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::() => write!(f, ""),
- Union::Variant(_) => write!(f, ""),
+ Union::Variant(value) => write!(f, "{}", (*value).type_name()),
}
}
}
diff --git a/src/engine.rs b/src/engine.rs
index 0b7ac58e..a6b87770 100644
--- a/src/engine.rs
+++ b/src/engine.rs
@@ -87,6 +87,7 @@ pub const FN_GET: &str = "get$";
pub const FN_SET: &str = "set$";
pub const FN_IDX_GET: &str = "index$get$";
pub const FN_IDX_SET: &str = "index$set$";
+pub const FN_ANONYMOUS: &str = "anon$";
#[cfg(feature = "internals")]
pub const MARKER_EXPR: &str = "$expr$";
@@ -187,7 +188,7 @@ impl 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> {
match self {
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> {
#[cfg(not(feature = "no_object"))]
if fn_name.starts_with(FN_GET) {
- Some(&fn_name[FN_GET.len()..])
- } else {
- None
+ return Some(&fn_name[FN_GET.len()..]);
}
- #[cfg(feature = "no_object")]
None
}
@@ -436,12 +434,9 @@ pub fn make_setter(id: &str) -> String {
fn extract_prop_from_setter(fn_name: &str) -> Option<&str> {
#[cfg(not(feature = "no_object"))]
if fn_name.starts_with(FN_SET) {
- Some(&fn_name[FN_SET.len()..])
- } else {
- None
+ return Some(&fn_name[FN_SET.len()..]);
}
- #[cfg(feature = "no_object")]
None
}
@@ -453,7 +448,7 @@ fn default_print(s: &str) {
}
/// 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>(
mods: &'s Imports,
state: &mut State,
@@ -486,7 +481,7 @@ fn search_imports<'s>(
}
/// 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>(
mods: &'s mut Imports,
state: &mut State,
@@ -636,7 +631,7 @@ impl Engine {
}
/// 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
///
@@ -654,7 +649,7 @@ impl Engine {
args: &mut FnCallArgs,
is_ref: bool,
is_method: bool,
- def_val: Option<&Dynamic>,
+ def_val: Option,
level: usize,
) -> Result<(Dynamic, bool), Box> {
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`.
fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCallArgs<'a>) {
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)
if let Some(val) = def_val {
- return Ok((val.clone(), false));
+ return Ok((val.into(), false));
}
// Getter function not found?
@@ -877,7 +872,7 @@ impl Engine {
}
/// 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
///
@@ -959,7 +954,7 @@ impl Engine {
}
/// 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
///
@@ -976,7 +971,7 @@ impl Engine {
args: &mut FnCallArgs,
is_ref: bool,
is_method: bool,
- def_val: Option<&Dynamic>,
+ def_val: Option,
level: usize,
) -> Result<(Dynamic, bool), Box> {
// 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'.
- /// Position in `EvalAltResult` is None and must be set afterwards.
+ /// Position in `EvalAltResult` is `None` and must be set afterwards.
fn eval_script_expr(
&self,
scope: &mut Scope,
@@ -1081,7 +1076,6 @@ impl Engine {
let is_ref = target.is_ref();
let is_value = target.is_value();
- let def_val = def_val.as_ref();
// Get a reference to the mutation target Dynamic
let obj = target.as_mut();
@@ -1100,7 +1094,7 @@ impl Engine {
// Map it to name(args) in function-call style
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 call on object
@@ -1120,7 +1114,7 @@ impl Engine {
// Map it to name(args) in function-call style
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 {
let redirected: Option;
@@ -1146,7 +1140,7 @@ impl Engine {
let args = arg_values.as_mut();
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))?;
@@ -1161,7 +1155,7 @@ impl Engine {
}
/// 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(
&self,
state: &mut State,
@@ -1210,14 +1204,14 @@ impl Engine {
}
// xxx[rhs] = new_val
_ if new_val.is_some() => {
+ let mut new_val = new_val.unwrap();
let mut idx_val2 = idx_val.clone();
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
// Try to call an index setter
Ok(obj_ptr) if obj_ptr.is_value() => {
- let args =
- &mut [target.as_mut(), &mut idx_val2, &mut new_val.unwrap()];
+ let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val];
self.exec_fn_call(
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
Ok(ref mut obj_ptr) => {
obj_ptr
- .set_value(new_val.unwrap())
+ .set_value(new_val)
.map_err(|err| err.new_position(rhs.position()))?;
}
Err(err) => match *err {
// No index getter - try to call an index setter
EvalAltResult::ErrorIndexingType(_, _) => {
- let args = &mut [
- target.as_mut(),
- &mut idx_val2,
- &mut new_val.unwrap(),
- ];
+ let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val];
self.exec_fn_call(
state, lib, FN_IDX_SET, true, 0, args, is_ref, true, None,
@@ -1694,13 +1684,12 @@ impl Engine {
#[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(mut rhs_value)) => {
let op = "==";
- let def_value = false.into();
let mut scope = Scope::new();
// Call the `==` operator to compare each value
for value in rhs_value.iter_mut() {
+ let def_value = Some(false);
let args = &mut [&mut lhs_value.clone(), value];
- let def_value = Some(&def_value);
let hashes = (
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
@@ -1782,6 +1771,7 @@ impl Engine {
Expr::FloatConstant(x) => Ok(x.0.into()),
Expr::StringConstant(x) => Ok(x.0.to_string().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 => {
if let Some(ref val) = this_ptr {
Ok((*val).clone())
@@ -1941,7 +1931,6 @@ impl Engine {
// Normal function call
Expr::FnCall(x) if x.1.is_none() => {
let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref();
- let def_val = def_val.as_ref();
// Handle Fn()
if name == KEYWORD_FN_PTR && args_expr.len() == 1 {
@@ -2072,7 +2061,7 @@ impl Engine {
let args = args.as_mut();
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_err(|err| err.new_position(*pos))
@@ -2167,7 +2156,7 @@ impl Engine {
.map_err(|err| err.new_position(*pos)),
Err(err) => match *err {
EvalAltResult::ErrorFunctionNotFound(_, _) if def_val.is_some() => {
- Ok(def_val.clone().unwrap())
+ Ok(def_val.unwrap().into())
}
EvalAltResult::ErrorFunctionNotFound(_, _) => {
Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
@@ -2634,7 +2623,7 @@ impl Engine {
}
/// 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> {
state.operations += 1;
diff --git a/src/error.rs b/src/error.rs
index 1f4b08bf..86c49091 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -206,6 +206,9 @@ impl fmt::Display for ParseErrorType {
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) => {
write!(f, "Expecting body statement block for function '{}'", s)
}
diff --git a/src/module.rs b/src/module.rs
index 5a6a604c..7216c248 100644
--- a/src/module.rs
+++ b/src/module.rs
@@ -30,6 +30,7 @@ use crate::stdlib::{
};
#[cfg(not(feature = "no_std"))]
+#[cfg(feature = "sync")]
use crate::stdlib::sync::RwLock;
/// 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,
/// 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);
impl fmt::Debug for ModuleRef {
diff --git a/src/optimize.rs b/src/optimize.rs
index 3da46ffa..945040ea 100644
--- a/src/optimize.rs
+++ b/src/optimize.rs
@@ -1,9 +1,12 @@
use crate::any::Dynamic;
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::parser::{map_dynamic_to_expr, Expr, ReturnType, ScriptFnDef, Stmt, AST};
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
+use crate::token::is_valid_identifier;
use crate::utils::StaticVec;
#[cfg(feature = "internals")]
@@ -397,7 +400,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
// All other items can be thrown away.
state.set_dirty();
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))
.unwrap_or_else(|| Expr::Unit(pos))
}
@@ -528,6 +531,19 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
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
Expr::FnCall(mut x)
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())
} else {
// 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))
diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs
index 7a548ff0..5e43c05b 100644
--- a/src/packages/arithmetic.rs
+++ b/src/packages/arithmetic.rs
@@ -7,6 +7,10 @@ use crate::token::Position;
#[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT;
+#[cfg(not(feature = "no_float"))]
+#[cfg(feature = "no_std")]
+use num_traits::*;
+
use num_traits::{
identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl,
CheckedShr, CheckedSub,
diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs
index e7ce43b7..aed7e5b7 100644
--- a/src/packages/math_basic.rs
+++ b/src/packages/math_basic.rs
@@ -6,6 +6,10 @@ use crate::token::Position;
#[cfg(not(feature = "no_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};
#[cfg(feature = "only_i32")]
diff --git a/src/parser.rs b/src/parser.rs
index e2f0c3f2..b3ed0756 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -2,7 +2,7 @@
use crate::any::{Dynamic, Union};
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::module::{Module, ModuleRef};
use crate::optimize::{optimize_into_ast, OptimizationLevel};
@@ -25,7 +25,7 @@ use crate::stdlib::{
char,
collections::HashMap,
fmt, format,
- hash::Hash,
+ hash::{Hash, Hasher},
iter::empty,
mem,
num::NonZeroUsize,
@@ -35,6 +35,12 @@ use crate::stdlib::{
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.
///
/// 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(&self, state: &mut H) {
+ state.write(&self.0.to_le_bytes());
+ self.1.hash(state);
+ }
+}
+
/// An expression.
///
/// Each variant is at most one pointer in size (for speed),
/// with everything being allocated together in one single tuple.
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Hash)]
pub enum Expr {
/// Integer constant.
IntegerConstant(Box<(INT, Position)>),
/// Floating-point constant.
#[cfg(not(feature = "no_float"))]
- FloatConstant(Box<(FLOAT, Position)>),
+ FloatConstant(Box),
/// Character constant.
CharConstant(Box<(char, Position)>),
/// String constant.
StringConstant(Box<(ImmutableString, Position)>),
+ /// FnPtr constant.
+ FnPointer(Box<(ImmutableString, Position)>),
/// Variable access - ((variable name, position), optional modules, hash, optional index)
Variable(
Box<(
@@ -623,7 +643,7 @@ pub enum Expr {
)>,
),
/// Property access.
- Property(Box<((String, String, String), Position)>),
+ Property(Box<((ImmutableString, String, String), Position)>),
/// { stmt }
Stmt(Box<(Stmt, Position)>),
/// Wrapped expression - should not be optimized away.
@@ -637,7 +657,7 @@ pub enum Expr {
Option>,
u64,
StaticVec,
- Option,
+ Option,
)>,
),
/// expr op= expr
@@ -673,18 +693,6 @@ impl Default for Expr {
}
}
-impl Hash for Expr {
- fn hash(&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 {
/// Get the `Dynamic` value of a constant expression.
///
@@ -758,6 +766,7 @@ impl Expr {
Self::IntegerConstant(x) => x.1,
Self::CharConstant(x) => x.1,
Self::StringConstant(x) => x.1,
+ Self::FnPointer(x) => x.1,
Self::Array(x) => x.1,
Self::Map(x) => x.1,
Self::Property(x) => x.1,
@@ -791,6 +800,7 @@ impl Expr {
Self::IntegerConstant(x) => x.1 = new_pos,
Self::CharConstant(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::Map(x) => x.1 = new_pos,
Self::Variable(x) => (x.0).1 = new_pos,
@@ -847,6 +857,7 @@ impl Expr {
Self::IntegerConstant(_)
| Self::CharConstant(_)
| Self::StringConstant(_)
+ | Self::FnPointer(_)
| Self::True(_)
| Self::False(_)
| Self::Unit(_) => true,
@@ -878,6 +889,7 @@ impl Expr {
Self::IntegerConstant(_)
| Self::CharConstant(_)
+ | Self::FnPointer(_)
| Self::In(_)
| Self::And(_)
| Self::Or(_)
@@ -925,7 +937,7 @@ impl Expr {
let (name, pos) = x.0;
let getter = make_getter(&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,
}
@@ -1476,7 +1488,7 @@ fn parse_primary(
let mut root_expr = match token {
Token::IntegerConstant(x) => Expr::IntegerConstant(Box::new((x, settings.pos))),
#[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::StringConstant(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))),
Token::Identifier(s) => {
@@ -1616,7 +1628,10 @@ fn parse_unary(
.map(|i| Expr::IntegerConstant(Box::new((i, pos))))
.or_else(|| {
#[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")]
return None;
})
@@ -1625,7 +1640,9 @@ fn parse_unary(
// Negative 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
expr => {
@@ -1664,9 +1681,38 @@ fn parse_unary(
None,
hash,
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)
+ }
//
Token::EOF => Err(PERR::UnexpectedEOF.into_err(settings.pos)),
// All other tokens
@@ -1788,7 +1834,7 @@ fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result Expr::FnCall(Box::new((op, None, hash, args, None))),
// '!=' 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
Token::EqualsTo
@@ -2151,9 +2197,6 @@ fn parse_expr(
exprs.push(Expr::Stmt(Box::new((stmt, pos))))
}
s => match input.peek().unwrap() {
- (Token::Custom(custom), _) if custom == s => {
- input.next().unwrap();
- }
(t, _) if t.syntax().as_ref() == s => {
input.next().unwrap();
}
@@ -2765,32 +2808,28 @@ fn parse_fn(
let mut params = Vec::new();
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);
loop {
- match input.peek().unwrap() {
- (Token::RightParen, _) => (),
- _ => match input.next().unwrap() {
- (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::RightParen.into(), end_err).into_err(pos)
- )
- }
- },
+ match input.next().unwrap() {
+ (Token::RightParen, _) => 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::RightParen.into(),
+ format!("to close the parameters list of function '{}'", name),
+ )
+ .into_err(pos))
+ }
}
match input.next().unwrap() {
(Token::RightParen, _) => break,
(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)),
(_, 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 {
pub(crate) fn parse_global_expr(
&self,
@@ -2954,7 +3088,7 @@ impl Engine {
pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option {
match value.0 {
#[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::Int(value) => Some(Expr::IntegerConstant(Box::new((value, pos)))),
diff --git a/src/settings.rs b/src/settings.rs
index f1967cdf..e7e4fd16 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -2,7 +2,7 @@ use crate::engine::Engine;
use crate::module::ModuleResolver;
use crate::optimize::OptimizationLevel;
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};
@@ -183,8 +183,7 @@ impl Engine {
/// engine.disable_symbol("if"); // disable the 'if' keyword
///
/// engine.compile("let x = if true { 42 } else { 0 };")?;
- /// // ^ 'if' is parsed as a variable name
- /// // ^ missing ';' after statement end
+ /// // ^ 'if' is rejected as a reserved keyword
/// # Ok(())
/// # }
/// ```
@@ -252,6 +251,24 @@ impl Engine {
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() {
self.custom_keywords = Some(Default::default());
}
diff --git a/src/syntax.rs b/src/syntax.rs
index 3b646d25..9bd5fad1 100644
--- a/src/syntax.rs
+++ b/src/syntax.rs
@@ -88,11 +88,16 @@ impl Engine {
MARKER_EXPR | MARKER_BLOCK | MARKER_IDENT if !segments.is_empty() => s.to_string(),
// Standard symbols not in first position
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
.disabled_symbols
.as_ref()
.map(|d| d.contains(s))
.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 self.custom_keywords.is_none() {
diff --git a/src/token.rs b/src/token.rs
index f3579317..dae43624 100644
--- a/src/token.rs
+++ b/src/token.rs
@@ -1334,73 +1334,81 @@ impl<'a> Iterator for TokenIterator<'a, '_> {
self.engine.disabled_symbols.as_ref(),
self.engine.custom_keywords.as_ref(),
) {
+ // {EOF}
(None, _, _) => None,
- (Some((Token::Reserved(s), pos)), None, None) => return Some((match s.as_str() {
- "===" => Token::LexError(Box::new(LERR::ImproperSymbol(
- "'===' is not a valid operator. This is not JavaScript! Should it be '=='?"
- .to_string(),
+ // Reserved keyword/symbol
+ (Some((Token::Reserved(s), pos)), disabled, custom) => Some((match
+ (s.as_str(), custom.map(|c| c.contains_key(&s)).unwrap_or(false))
+ {
+ ("===", 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(
- "'!==' is not a valid operator. This is not JavaScript! Should it be '!='?"
- .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(
- "'->' is not a valid symbol. This is not C or C++!".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()))),
+ ("<-", false) => Token::LexError(Box::new(LERR::ImproperSymbol(
"'<-' is not a valid symbol. This is not Go! Should it be '<='?".to_string(),
))),
- "=>" => Token::LexError(Box::new(LERR::ImproperSymbol(
- "'=>' is not a valid symbol. This is not Rust! Should it be '>='?"
- .to_string(),
+ ("=>", false) => Token::LexError(Box::new(LERR::ImproperSymbol(
+ "'=>' is not a valid symbol. This is not Rust! Should it be '>='?".to_string(),
))),
- ":=" => Token::LexError(Box::new(LERR::ImproperSymbol(
- "':=' is not a valid assignment operator. This is not Go! Should it be simply '='?"
- .to_string(),
+ (":=", false) => Token::LexError(Box::new(LERR::ImproperSymbol(
+ "':=' is not a valid assignment operator. This is not Go! Should it be simply '='?".to_string(),
))),
- "::<" => Token::LexError(Box::new(LERR::ImproperSymbol(
- "'::<>' is not a valid symbol. This is not Rust! Should it be '::'?"
- .to_string(),
+ ("::<", false) => Token::LexError(Box::new(LERR::ImproperSymbol(
+ "'::<>' is not a valid symbol. This is not Rust! Should it be '::'?".to_string(),
))),
- "(*" | "*)" => Token::LexError(Box::new(LERR::ImproperSymbol(
- "'(* .. *)' is not a valid comment format. This is not Pascal! Should it be '/* .. */'?"
- .to_string(),
+ ("(*", false) | ("*)", false) => Token::LexError(Box::new(LERR::ImproperSymbol(
+ "'(* .. *)' is not a valid comment format. This is not Pascal! Should it be '/* .. */'?".to_string(),
))),
- "#" => Token::LexError(Box::new(LERR::ImproperSymbol(
- "'#' is not a valid symbol. Should it be '#{'?"
- .to_string(),
+ ("#", false) => Token::LexError(Box::new(LERR::ImproperSymbol(
+ "'#' is not a valid symbol. Should it be '#{'?".to_string(),
))),
- token if !is_valid_identifier(token.chars()) => Token::LexError(Box::new(LERR::ImproperSymbol(
- format!("'{}' is a reserved symbol.", token)
+ // Reserved keyword/operator that is custom.
+ (_, 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)),
- (r @ Some(_), None, None) => r,
+ // Custom keyword
(Some((Token::Identifier(s), pos)), _, Some(custom)) if custom.contains_key(&s) => {
- // Convert custom keywords
Some((Token::Custom(s), pos))
}
- (Some((token, pos)), _, Some(custom))
- if (token.is_keyword() || token.is_operator() || token.is_reserved())
- && custom.contains_key(token.syntax().as_ref()) =>
+ // Custom standard keyword - must be disabled
+ (Some((token, pos)), Some(disabled), Some(custom))
+ if token.is_keyword() && custom.contains_key(token.syntax().as_ref()) =>
{
- // Convert into custom keywords
- Some((Token::Custom(token.syntax().into()), pos))
+ if disabled.contains(token.syntax().as_ref()) {
+ // 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), _)
if token.is_operator() && disabled.contains(token.syntax().as_ref()) =>
{
- // Convert disallowed operators into lex errors
Some((
Token::LexError(Box::new(LexError::UnexpectedInput(token.syntax().into()))),
pos,
))
}
+ // Disabled standard keyword
(Some((token, pos)), Some(disabled), _)
if token.is_keyword() && disabled.contains(token.syntax().as_ref()) =>
{
- // Convert disallowed keywords into identifiers
- Some((Token::Identifier(token.syntax().into()), pos))
+ Some((Token::Reserved(token.syntax().into()), pos))
}
(r, _, _) => r,
}
diff --git a/tests/call_fn.rs b/tests/call_fn.rs
index a04bd614..97767df7 100644
--- a/tests/call_fn.rs
+++ b/tests/call_fn.rs
@@ -1,7 +1,6 @@
#![cfg(not(feature = "no_function"))]
use rhai::{
- Dynamic, Engine, EvalAltResult, FnPtr, Func, ImmutableString, Module, ParseError,
- ParseErrorType, Scope, INT,
+ Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseError, ParseErrorType, Scope, INT,
};
#[test]
diff --git a/tests/modules.rs b/tests/modules.rs
index 3d0fc48d..b7393f14 100644
--- a/tests/modules.rs
+++ b/tests/modules.rs
@@ -1,6 +1,6 @@
#![cfg(not(feature = "no_module"))]
use rhai::{
- module_resolvers::StaticModuleResolver, Engine, EvalAltResult, Module, ParseError,
+ module_resolvers::StaticModuleResolver, Dynamic, Engine, EvalAltResult, Module, ParseError,
ParseErrorType, Scope, INT,
};
@@ -26,6 +26,7 @@ fn test_module_sub_module() -> Result<(), Box> {
sub_module.set_sub_module("universe", sub_module2);
module.set_sub_module("life", sub_module);
+ module.set_var("MYSTIC_NUMBER", Dynamic::from(42 as INT));
assert!(module.contains_sub_module("life"));
let m = module.get_sub_module("life").unwrap();
@@ -44,18 +45,16 @@ fn test_module_sub_module() -> Result<(), Box> {
let mut engine = Engine::new();
engine.set_module_resolver(Some(resolver));
- let mut scope = Scope::new();
-
assert_eq!(
- engine.eval_with_scope::(
- &mut scope,
- r#"import "question" as q; q::life::universe::answer + 1"#
- )?,
+ engine.eval::(r#"import "question" as q; q::MYSTIC_NUMBER"#)?,
42
);
assert_eq!(
- engine.eval_with_scope::(
- &mut scope,
+ engine.eval::(r#"import "question" as q; q::life::universe::answer + 1"#)?,
+ 42
+ );
+ assert_eq!(
+ engine.eval::(
r#"import "question" as q; q::life::universe::inc(q::life::universe::answer)"#
)?,
42
@@ -221,36 +220,30 @@ fn test_module_from_ast() -> Result<(), Box> {
resolver2.insert("testing", module);
engine.set_module_resolver(Some(resolver2));
- let mut scope = Scope::new();
-
assert_eq!(
- engine.eval_with_scope::(&mut scope, r#"import "testing" as ttt; ttt::abc"#)?,
+ engine.eval::(r#"import "testing" as ttt; ttt::abc"#)?,
123
);
assert_eq!(
- engine.eval_with_scope::(&mut scope, r#"import "testing" as ttt; ttt::foo"#)?,
+ engine.eval::(r#"import "testing" as ttt; ttt::foo"#)?,
42
);
- assert!(engine
- .eval_with_scope::(&mut scope, r#"import "testing" as ttt; ttt::extra::foo"#)?);
+ assert!(engine.eval::(r#"import "testing" as ttt; ttt::extra::foo"#)?);
assert_eq!(
- engine.eval_with_scope::(&mut scope, r#"import "testing" as ttt; ttt::hello"#)?,
+ engine.eval::(r#"import "testing" as ttt; ttt::hello"#)?,
"hello, 42 worlds!"
);
assert_eq!(
- engine.eval_with_scope::(&mut scope, r#"import "testing" as ttt; ttt::calc(999)"#)?,
+ engine.eval::(r#"import "testing" as ttt; ttt::calc(999)"#)?,
1000
);
assert_eq!(
- engine.eval_with_scope::(
- &mut scope,
- r#"import "testing" as ttt; ttt::add_len(ttt::foo, ttt::hello)"#
- )?,
+ engine.eval::(r#"import "testing" as ttt; ttt::add_len(ttt::foo, ttt::hello)"#)?,
59
);
assert!(matches!(
*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"),
EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "ttt::hidden"
));
diff --git a/tests/syntax.rs b/tests/syntax.rs
index 0fdf3c07..d4737b71 100644
--- a/tests/syntax.rs
+++ b/tests/syntax.rs
@@ -1,16 +1,25 @@
#![cfg(feature = "internals")]
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]
fn test_custom_syntax() -> Result<(), Box> {
let mut engine = Engine::new();
+ engine.consume("while false {}")?;
+
// Disable 'while' and make sure it still works with custom syntax
engine.disable_symbol("while");
- engine.consume("while false {}").expect_err("should error");
- engine.consume("let while = 0")?;
+ assert!(matches!(
+ *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
.register_custom_syntax(
diff --git a/tests/tokens.rs b/tests/tokens.rs
index f54afa84..523beab7 100644
--- a/tests/tokens.rs
+++ b/tests/tokens.rs
@@ -7,8 +7,11 @@ fn test_tokens_disabled() {
engine.disable_symbol("if"); // disable the 'if' keyword
assert!(matches!(
- *engine.compile("let x = if true { 42 } else { 0 };").expect_err("should error").0,
- ParseErrorType::MissingToken(ref token, _) if token == ";"
+ *engine
+ .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