Merge pull request #714 from schungx/master

Fix parsing bug.
This commit is contained in:
Stephen Chung 2023-04-20 14:38:17 +08:00 committed by GitHub
commit 160f72b4cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 408 additions and 373 deletions

View File

@ -11,6 +11,7 @@ Buf fixes
* `is_shared` is a reserved keyword and is now handled properly (e.g. it cannot be the target of a function pointer). * `is_shared` is a reserved keyword and is now handled properly (e.g. it cannot be the target of a function pointer).
* Re-optimizing an AST via `optimize_ast` with constants now works correctly for closures. Previously the hidden `Share` nodes are not removed and causes variable-not-found errors during runtime if the constants are not available in the scope. * Re-optimizing an AST via `optimize_ast` with constants now works correctly for closures. Previously the hidden `Share` nodes are not removed and causes variable-not-found errors during runtime if the constants are not available in the scope.
* Expressions such as `(v[0].func()).prop` now parse correctly.
New features New features
------------ ------------
@ -18,6 +19,11 @@ New features
* It is now possible to require a specific _type_ to the `this` pointer for a particular script-defined function so that it is called only when the `this` pointer contains the specified type. * It is now possible to require a specific _type_ to the `this` pointer for a particular script-defined function so that it is called only when the `this` pointer contains the specified type.
* `is_def_fn` is extended to support checking for typed methods, with syntax `is_def_fn(this_type, fn_name, arity)` * `is_def_fn` is extended to support checking for typed methods, with syntax `is_def_fn(this_type, fn_name, arity)`
Enhancements
------------
* `Engine::is_symbol_disabled` is added to test whether a particular keyword/symbol is disabled.
Version 1.13.0 Version 1.13.0
============== ==============

View File

@ -11,21 +11,23 @@ Root Sources
| `tokenizer.rs` | Script tokenizer/lexer | | `tokenizer.rs` | Script tokenizer/lexer |
| `parser.rs` | Script parser | | `parser.rs` | Script parser |
| `optimizer.rs` | Script optimizer | | `optimizer.rs` | Script optimizer |
| `defer.rs` | Utilities for deferred clean-up of resources |
| `reify.rs` | Utilities for making generic types concrete | | `reify.rs` | Utilities for making generic types concrete |
| `tests.rs` | Unit tests (not integration tests, which are in the `rhai/tests` sub-directory) | | `tests.rs` | Unit tests (not integration tests, which are in the main `tests` sub-directory) |
Sub-Directories Sub-Directories
--------------- ---------------
| Sub-directory | Description | | Sub-directory | Description |
| ------------- | ----------------------------------------------------- | | ------------- | ------------------------------------------------------------------ |
| `types` | Common data types (e.g. `Dynamic`, errors) | | `config` | Configuration |
| `api` | Public API for the scripting engine | | `types` | Common data types (e.g. `Dynamic`, errors) |
| `ast` | AST definition | | `api` | Public API for the scripting engine |
| `module` | Support for modules | | `ast` | AST definition |
| `packages` | Pre-defined packages | | `module` | Support for modules |
| `func` | Support for function calls | | `packages` | Pre-defined packages |
| `eval` | Evaluation engine | | `func` | Registering and calling functions (native Rust and script-defined) |
| `serde` | Support for [`serde`](https://crates.io/crates/serde) | | `eval` | AST evaluation |
| `bin` | Pre-built CLI binaries (e.g. `rhai-run`, `rhai-repl`) | | `serde` | Support for [`serde`](https://crates.io/crates/serde) and metadata |
| `bin` | Pre-built CLI binaries |

View File

@ -259,15 +259,9 @@ impl Engine {
// Keyword/symbol not in first position // Keyword/symbol not in first position
_ if !segments.is_empty() && token.is_some() => { _ if !segments.is_empty() && token.is_some() => {
// Make it a custom keyword/symbol if it is disabled or reserved // Make it a custom keyword/symbol if it is disabled or reserved
if (self if (self.is_symbol_disabled(s)
.disabled_symbols
.as_ref()
.map_or(false, |m| m.contains(s))
|| token.as_ref().map_or(false, Token::is_reserved)) || token.as_ref().map_or(false, Token::is_reserved))
&& !self && !self.is_custom_keyword(s)
.custom_keywords
.as_ref()
.map_or(false, |m| m.contains_key(s))
{ {
self.custom_keywords self.custom_keywords
.get_or_insert_with(Default::default) .get_or_insert_with(Default::default)
@ -279,10 +273,7 @@ impl Engine {
// Standard keyword in first position but not disabled // Standard keyword in first position but not disabled
_ if segments.is_empty() _ if segments.is_empty()
&& token.as_ref().map_or(false, Token::is_standard_keyword) && token.as_ref().map_or(false, Token::is_standard_keyword)
&& !self && !self.is_symbol_disabled(s) =>
.disabled_symbols
.as_ref()
.map_or(false, |m| m.contains(s)) =>
{ {
return Err(LexError::ImproperSymbol( return Err(LexError::ImproperSymbol(
s.to_string(), s.to_string(),
@ -299,15 +290,9 @@ impl Engine {
&& (is_valid_identifier(s) || is_reserved_keyword_or_symbol(s).0) => && (is_valid_identifier(s) || is_reserved_keyword_or_symbol(s).0) =>
{ {
// Make it a custom keyword/symbol if it is disabled or reserved // Make it a custom keyword/symbol if it is disabled or reserved
if self if self.is_symbol_disabled(s)
.disabled_symbols
.as_ref()
.map_or(false, |m| m.contains(s))
|| (token.as_ref().map_or(false, Token::is_reserved) || (token.as_ref().map_or(false, Token::is_reserved)
&& !self && !self.is_custom_keyword(s))
.custom_keywords
.as_ref()
.map_or(false, |m| m.contains_key(s)))
{ {
self.custom_keywords self.custom_keywords
.get_or_insert_with(Default::default) .get_or_insert_with(Default::default)

View File

@ -457,12 +457,7 @@ impl Module {
!f.metadata.name.contains('$') && !is_valid_function_name(&f.metadata.name); !f.metadata.name.contains('$') && !is_valid_function_name(&f.metadata.name);
#[cfg(not(feature = "no_custom_syntax"))] #[cfg(not(feature = "no_custom_syntax"))]
let operator = operator let operator = operator || def.engine.is_custom_keyword(f.metadata.name.as_str());
|| def
.engine
.custom_keywords
.as_ref()
.map_or(false, |m| m.contains_key(f.metadata.name.as_str()));
f.write_definition(writer, def, operator)?; f.write_definition(writer, def, operator)?;
} }

View File

@ -109,7 +109,7 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[inline(always)] #[inline]
pub fn disable_symbol(&mut self, symbol: impl Into<Identifier>) -> &mut Self { pub fn disable_symbol(&mut self, symbol: impl Into<Identifier>) -> &mut Self {
self.disabled_symbols self.disabled_symbols
.get_or_insert_with(Default::default) .get_or_insert_with(Default::default)
@ -117,6 +117,26 @@ impl Engine {
self self
} }
/// Is a particular keyword or operator disabled?
///
/// # Examples
///
/// ```rust
/// use rhai::Engine;
///
/// let mut engine = Engine::new();
///
/// engine.disable_symbol("if"); // disable the 'if' keyword
///
/// assert!(engine.is_symbol_disabled("if"));
/// ```
#[inline]
pub fn is_symbol_disabled(&self, symbol: &str) -> bool {
self.disabled_symbols
.as_ref()
.map_or(false, |m| m.contains(symbol))
}
/// Register a custom operator with a precedence into the language. /// Register a custom operator with a precedence into the language.
/// ///
/// Not available under `no_custom_syntax`. /// Not available under `no_custom_syntax`.
@ -168,32 +188,21 @@ impl Engine {
Some(Token::Custom(..)) => (), Some(Token::Custom(..)) => (),
// Active standard keywords cannot be made custom // Active standard keywords cannot be made custom
// Disabled keywords are OK // Disabled keywords are OK
Some(token) if token.is_standard_keyword() => { Some(token)
if !self if token.is_standard_keyword()
.disabled_symbols && !self.is_symbol_disabled(token.literal_syntax()) =>
.as_ref() {
.map_or(false, |m| m.contains(token.literal_syntax())) return Err(format!("'{keyword}' is a reserved keyword"))
{
return Err(format!("'{keyword}' is a reserved keyword"));
}
}
// Active standard symbols cannot be made custom
Some(token) if token.is_standard_symbol() => {
if !self
.disabled_symbols
.as_ref()
.map_or(false, |m| m.contains(token.literal_syntax()))
{
return Err(format!("'{keyword}' is a reserved operator"));
}
} }
// Active standard symbols cannot be made custom // Active standard symbols cannot be made custom
Some(token) Some(token)
if !self if token.is_standard_symbol()
.disabled_symbols && !self.is_symbol_disabled(token.literal_syntax()) =>
.as_ref()
.map_or(false, |m| m.contains(token.literal_syntax())) =>
{ {
return Err(format!("'{keyword}' is a reserved operator"))
}
// Active standard symbols cannot be made custom
Some(token) if !self.is_symbol_disabled(token.literal_syntax()) => {
return Err(format!("'{keyword}' is a reserved symbol")) return Err(format!("'{keyword}' is a reserved symbol"))
} }
// Disabled symbols are OK // Disabled symbols are OK
@ -207,6 +216,16 @@ impl Engine {
Ok(self) Ok(self)
} }
/// Is a keyword registered as a custom keyword?
///
/// Not available under `no_custom_syntax`.
#[cfg(not(feature = "no_custom_syntax"))]
#[inline]
pub(crate) fn is_custom_keyword(&self, keyword: &str) -> bool {
self.custom_keywords
.as_ref()
.map_or(false, |m| m.contains_key(keyword))
}
/// Get the default value of the custom state for each evaluation run. /// Get the default value of the custom state for each evaluation run.
#[inline(always)] #[inline(always)]

View File

@ -90,15 +90,15 @@ impl Engine {
let param_type_names: Option<&[&str]> = None; let param_type_names: Option<&[&str]> = None;
let fn_name = name.as_ref(); let fn_name = name.as_ref();
let no_const = false; let is_pure = true;
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
let no_const = no_const || (F::num_params() == 3 && fn_name == crate::engine::FN_IDX_SET); let is_pure = is_pure && (F::num_params() != 3 || fn_name != crate::engine::FN_IDX_SET);
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
let no_const = let is_pure =
no_const || (F::num_params() == 2 && fn_name.starts_with(crate::engine::FN_SET)); is_pure && (F::num_params() != 2 || !fn_name.starts_with(crate::engine::FN_SET));
let func = func.into_callable_function(fn_name.into(), no_const); let func = func.into_callable_function(fn_name.into(), is_pure);
self.global_namespace_mut().set_fn( self.global_namespace_mut().set_fn(
name, name,

View File

@ -721,7 +721,7 @@ pub enum Stmt {
/// This variant does not map to any language structure. It is currently only used only to /// This variant does not map to any language structure. It is currently only used only to
/// convert normal variables into shared variables when they are _captured_ by a closure. /// convert normal variables into shared variables when they are _captured_ by a closure.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Share(Box<crate::FnArgsVec<(crate::ImmutableString, Option<NonZeroUsize>, Position)>>), Share(Box<crate::FnArgsVec<(crate::ast::Ident, Option<NonZeroUsize>)>>),
} }
impl Default for Stmt { impl Default for Stmt {
@ -816,7 +816,7 @@ impl Stmt {
Self::Export(.., pos) => *pos, Self::Export(.., pos) => *pos,
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Self::Share(x) => x[0].2, Self::Share(x) => x[0].0.pos,
} }
} }
/// Override the [position][Position] of this statement. /// Override the [position][Position] of this statement.
@ -848,7 +848,7 @@ impl Stmt {
Self::Export(.., pos) => *pos = new_pos, Self::Export(.., pos) => *pos = new_pos,
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Self::Share(x) => x.iter_mut().for_each(|(_, _, pos)| *pos = new_pos), Self::Share(x) => x.iter_mut().for_each(|(x, _)| x.pos = new_pos),
} }
self self

View File

@ -927,19 +927,19 @@ impl Engine {
// Share statement // Share statement
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Stmt::Share(x) => { Stmt::Share(x) => {
for (name, index, pos) in &**x { for (var, index) in &**x {
if let Some(index) = index let index = index
.map(|n| scope.len() - n.get()) .map(|n| scope.len() - n.get())
.or_else(|| scope.search(name)) .or_else(|| scope.search(&var.name))
{ .ok_or_else(|| {
let val = scope.get_mut_by_index(index); Box::new(ERR::ErrorVariableNotFound(var.name.to_string(), var.pos))
})?;
if !val.is_shared() { let val = scope.get_mut_by_index(index);
// Replace the variable with a shared value.
*val = std::mem::take(val).into_shared(); if !val.is_shared() {
} // Replace the variable with a shared value.
} else { *val = std::mem::take(val).into_shared();
return Err(ERR::ErrorVariableNotFound(name.to_string(), *pos).into());
} }
} }

View File

@ -276,6 +276,7 @@ impl Engine {
func: CallableFunction::Method { func: CallableFunction::Method {
func: Shared::new(f), func: Shared::new(f),
has_context, has_context,
is_pure: false,
}, },
source: None, source: None,
}) })
@ -285,6 +286,7 @@ impl Engine {
func: CallableFunction::Method { func: CallableFunction::Method {
func: Shared::new(f), func: Shared::new(f),
has_context, has_context,
is_pure: true,
}, },
source: None, source: None,
}), }),
@ -372,8 +374,8 @@ impl Engine {
let backup = &mut ArgBackup::new(); let backup = &mut ArgBackup::new();
// Calling pure function but the first argument is a reference? // Calling non-method function but the first argument is a reference?
let swap = is_ref_mut && func.is_pure() && !args.is_empty(); let swap = is_ref_mut && !func.is_method() && !args.is_empty();
if swap { if swap {
// Clone the first argument // Clone the first argument
@ -400,12 +402,11 @@ impl Engine {
.has_context() .has_context()
.then(|| (self, name, src, &*global, pos).into()); .then(|| (self, name, src, &*global, pos).into());
let mut _result = if let Some(f) = func.get_plugin_fn() { let mut _result = if !func.is_pure() && !args.is_empty() && args[0].is_read_only() {
if !f.is_pure() && !args.is_empty() && args[0].is_read_only() { // If function is not pure, there must be at least one argument
Err(ERR::ErrorNonPureMethodCallOnConstant(name.to_string(), pos).into()) Err(ERR::ErrorNonPureMethodCallOnConstant(name.to_string(), pos).into())
} else { } else if let Some(f) = func.get_plugin_fn() {
f.call(context, args) f.call(context, args)
}
} else if let Some(f) = func.get_native_fn() { } else if let Some(f) = func.get_native_fn() {
f(context, args) f(context, args)
} else { } else {
@ -603,8 +604,11 @@ impl Engine {
}; };
if error { if error {
let sig = self.gen_fn_call_signature(fn_name, args); return Err(ERR::ErrorFunctionNotFound(
return Err(ERR::ErrorFunctionNotFound(sig, pos).into()); self.gen_fn_call_signature(fn_name, args),
pos,
)
.into());
} }
} }
@ -814,12 +818,16 @@ impl Engine {
// Handle obj.call(fn_ptr, ...) // Handle obj.call(fn_ptr, ...)
KEYWORD_FN_PTR_CALL => { KEYWORD_FN_PTR_CALL => {
if call_args.is_empty() { if call_args.is_empty() {
let typ = self.map_type_name(target.type_name()); return Err(self.make_type_mismatch_err::<FnPtr>(
return Err(self.make_type_mismatch_err::<FnPtr>(typ, fn_call_pos)); self.map_type_name(target.type_name()),
fn_call_pos,
));
} }
if !call_args[0].is_fnptr() { if !call_args[0].is_fnptr() {
let typ = self.map_type_name(call_args[0].type_name()); return Err(self.make_type_mismatch_err::<FnPtr>(
return Err(self.make_type_mismatch_err::<FnPtr>(typ, first_arg_pos)); self.map_type_name(call_args[0].type_name()),
first_arg_pos,
));
} }
// FnPtr call on object // FnPtr call on object
@ -903,8 +911,10 @@ impl Engine {
} }
KEYWORD_FN_PTR_CURRY => { KEYWORD_FN_PTR_CURRY => {
if !target.is_fnptr() { if !target.is_fnptr() {
let typ = self.map_type_name(target.type_name()); return Err(self.make_type_mismatch_err::<FnPtr>(
return Err(self.make_type_mismatch_err::<FnPtr>(typ, fn_call_pos)); self.map_type_name(target.type_name()),
fn_call_pos,
));
} }
let mut fn_ptr = target.read_lock::<FnPtr>().expect("`FnPtr`").clone(); let mut fn_ptr = target.read_lock::<FnPtr>().expect("`FnPtr`").clone();
@ -1035,8 +1045,10 @@ impl Engine {
self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), arg)?; self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), arg)?;
if !arg_value.is_fnptr() { if !arg_value.is_fnptr() {
let typ = self.map_type_name(arg_value.type_name()); return Err(self.make_type_mismatch_err::<FnPtr>(
return Err(self.make_type_mismatch_err::<FnPtr>(typ, arg_pos)); self.map_type_name(arg_value.type_name()),
arg_pos,
));
} }
let fn_ptr = arg_value.cast::<FnPtr>(); let fn_ptr = arg_value.cast::<FnPtr>();
@ -1121,8 +1133,10 @@ impl Engine {
self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), first)?; self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), first)?;
if !arg_value.is_fnptr() { if !arg_value.is_fnptr() {
let typ = self.map_type_name(arg_value.type_name()); return Err(self.make_type_mismatch_err::<FnPtr>(
return Err(self.make_type_mismatch_err::<FnPtr>(typ, arg_pos)); self.map_type_name(arg_value.type_name()),
arg_pos,
));
} }
let mut fn_ptr = arg_value.cast::<FnPtr>(); let mut fn_ptr = arg_value.cast::<FnPtr>();
@ -1493,17 +1507,18 @@ impl Engine {
self.call_script_fn(global, caches, scope, None, environ, f, args, true, pos) self.call_script_fn(global, caches, scope, None, environ, f, args, true, pos)
} }
Some(f) if !f.is_pure() && args[0].is_read_only() => {
// If function is not pure, there must be at least one argument
Err(ERR::ErrorNonPureMethodCallOnConstant(fn_name.to_string(), pos).into())
}
Some(f) if f.is_plugin_fn() => { Some(f) if f.is_plugin_fn() => {
let f = f.get_plugin_fn().expect("plugin function"); let f = f.get_plugin_fn().expect("plugin function");
let context = f let context = f
.has_context() .has_context()
.then(|| (self, fn_name, module.id(), &*global, pos).into()); .then(|| (self, fn_name, module.id(), &*global, pos).into());
if !f.is_pure() && !args.is_empty() && args[0].is_read_only() { f.call(context, args)
Err(ERR::ErrorNonPureMethodCallOnConstant(fn_name.to_string(), pos).into()) .and_then(|r| self.check_data_size(r, pos))
} else {
f.call(context, args)
.and_then(|r| self.check_data_size(r, pos))
}
} }
Some(f) if f.is_native() => { Some(f) if f.is_native() => {

View File

@ -39,6 +39,8 @@ pub enum CallableFunction {
func: Shared<FnAny>, func: Shared<FnAny>,
/// Does the function take a [`NativeCallContext`][crate::NativeCallContext] parameter? /// Does the function take a [`NativeCallContext`][crate::NativeCallContext] parameter?
has_context: bool, has_context: bool,
/// This is a dummy field and is not used.
is_pure: bool,
}, },
/// A native Rust object method with the first argument passed by reference, /// A native Rust object method with the first argument passed by reference,
/// and the rest passed by value. /// and the rest passed by value.
@ -47,6 +49,8 @@ pub enum CallableFunction {
func: Shared<FnAny>, func: Shared<FnAny>,
/// Does the function take a [`NativeCallContext`][crate::NativeCallContext] parameter? /// Does the function take a [`NativeCallContext`][crate::NativeCallContext] parameter?
has_context: bool, has_context: bool,
/// Allow operating on constants?
is_pure: bool,
}, },
/// An iterator function. /// An iterator function.
Iterator { Iterator {
@ -105,9 +109,10 @@ impl CallableFunction {
pub fn is_pure(&self) -> bool { pub fn is_pure(&self) -> bool {
match self { match self {
Self::Pure { .. } => true, Self::Pure { .. } => true,
Self::Method { .. } | Self::Iterator { .. } => false, Self::Method { is_pure, .. } => *is_pure,
Self::Iterator { .. } => true,
Self::Plugin { func, .. } => !func.is_method_call(), Self::Plugin { func, .. } => func.is_pure(),
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Self::Script { .. } => false, Self::Script { .. } => false,

View File

@ -84,7 +84,7 @@ pub trait RegisterNativeFunction<
{ {
/// Convert this function into a [`CallableFunction`]. /// Convert this function into a [`CallableFunction`].
#[must_use] #[must_use]
fn into_callable_function(self, name: Identifier, no_const: bool) -> CallableFunction; fn into_callable_function(self, name: Identifier, is_pure: bool) -> CallableFunction;
/// Get the type ID's of this function's parameters. /// Get the type ID's of this function's parameters.
#[must_use] #[must_use]
fn param_types() -> [TypeId; N]; fn param_types() -> [TypeId; N];
@ -127,19 +127,6 @@ pub trait RegisterNativeFunction<
} }
} }
macro_rules! check_constant {
($abi:ident, $n:expr, $fn_name:ident, $no_const:ident, $args:ident) => {
#[cfg(any(not(feature = "no_object"), not(feature = "no_index")))]
if stringify!($abi) == "Method" && $no_const && $args[0].is_read_only() {
return Err(crate::ERR::ErrorNonPureMethodCallOnConstant(
$fn_name.to_string(),
crate::Position::NONE,
)
.into());
}
};
}
macro_rules! def_register { macro_rules! def_register {
() => { () => {
def_register!(imp Pure : 0;); def_register!(imp Pure : 0;);
@ -160,11 +147,9 @@ macro_rules! def_register {
> RegisterNativeFunction<($($mark,)*), $n, false, RET, false> for FN { > RegisterNativeFunction<($($mark,)*), $n, false, RET, false> for FN {
#[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] } #[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] }
#[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction { #[inline(always)] fn into_callable_function(self, fn_name: Identifier, is_pure: bool) -> CallableFunction {
CallableFunction::$abi { func: Shared::new(move |_, args: &mut FnCallArgs| { CallableFunction::$abi { func: Shared::new(move |_, args: &mut FnCallArgs| {
// The arguments are assumed to be of the correct number and types! // The arguments are assumed to be of the correct number and types!
check_constant!($abi, $n, fn_name, no_const, args);
let mut drain = args.iter_mut(); let mut drain = args.iter_mut();
$(let mut $par = $clone(drain.next().unwrap()); )* $(let mut $par = $clone(drain.next().unwrap()); )*
@ -173,7 +158,7 @@ macro_rules! def_register {
// Map the result // Map the result
Ok(Dynamic::from(r)) Ok(Dynamic::from(r))
}), has_context: false } }), has_context: false, is_pure }
} }
} }
@ -184,13 +169,11 @@ macro_rules! def_register {
> RegisterNativeFunction<($($mark,)*), $n, true, RET, false> for FN { > RegisterNativeFunction<($($mark,)*), $n, true, RET, false> for FN {
#[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] } #[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] }
#[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction { #[inline(always)] fn into_callable_function(self, fn_name: Identifier, is_pure: bool) -> CallableFunction {
CallableFunction::$abi { func: Shared::new(move |ctx: Option<NativeCallContext>, args: &mut FnCallArgs| { CallableFunction::$abi { func: Shared::new(move |ctx: Option<NativeCallContext>, args: &mut FnCallArgs| {
let ctx = ctx.unwrap(); let ctx = ctx.unwrap();
// The arguments are assumed to be of the correct number and types! // The arguments are assumed to be of the correct number and types!
check_constant!($abi, $n, fn_name, no_const, args);
let mut drain = args.iter_mut(); let mut drain = args.iter_mut();
$(let mut $par = $clone(drain.next().unwrap()); )* $(let mut $par = $clone(drain.next().unwrap()); )*
@ -199,7 +182,7 @@ macro_rules! def_register {
// Map the result // Map the result
Ok(Dynamic::from(r)) Ok(Dynamic::from(r))
}), has_context: true } }), has_context: true, is_pure }
} }
} }
@ -211,17 +194,15 @@ macro_rules! def_register {
#[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] } #[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { type_name::<RhaiResultOf<RET>>() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { type_name::<RhaiResultOf<RET>>() }
#[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction { #[inline(always)] fn into_callable_function(self, fn_name: Identifier, is_pure: bool) -> CallableFunction {
CallableFunction::$abi { func: Shared::new(move |_, args: &mut FnCallArgs| { CallableFunction::$abi { func: Shared::new(move |_, args: &mut FnCallArgs| {
// The arguments are assumed to be of the correct number and types! // The arguments are assumed to be of the correct number and types!
check_constant!($abi, $n, fn_name, no_const, args);
let mut drain = args.iter_mut(); let mut drain = args.iter_mut();
$(let mut $par = $clone(drain.next().unwrap()); )* $(let mut $par = $clone(drain.next().unwrap()); )*
// Call the function with each argument value // Call the function with each argument value
self($($arg),*).map(Dynamic::from) self($($arg),*).map(Dynamic::from)
}), has_context: false } }), has_context: false, is_pure }
} }
} }
@ -233,19 +214,17 @@ macro_rules! def_register {
#[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] } #[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { type_name::<RhaiResultOf<RET>>() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { type_name::<RhaiResultOf<RET>>() }
#[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction { #[inline(always)] fn into_callable_function(self, fn_name: Identifier, is_pure: bool) -> CallableFunction {
CallableFunction::$abi { func: Shared::new(move |ctx: Option<NativeCallContext>, args: &mut FnCallArgs| { CallableFunction::$abi { func: Shared::new(move |ctx: Option<NativeCallContext>, args: &mut FnCallArgs| {
let ctx = ctx.unwrap(); let ctx = ctx.unwrap();
// The arguments are assumed to be of the correct number and types! // The arguments are assumed to be of the correct number and types!
check_constant!($abi, $n, fn_name, no_const, args);
let mut drain = args.iter_mut(); let mut drain = args.iter_mut();
$(let mut $par = $clone(drain.next().unwrap()); )* $(let mut $par = $clone(drain.next().unwrap()); )*
// Call the function with each argument value // Call the function with each argument value
self(ctx, $($arg),*).map(Dynamic::from) self(ctx, $($arg),*).map(Dynamic::from)
}), has_context: true } }), has_context: true, is_pure }
} }
} }

View File

@ -1266,6 +1266,7 @@ impl Module {
CallableFunction::Method { CallableFunction::Method {
func: Shared::new(f), func: Shared::new(f),
has_context: true, has_context: true,
is_pure: false,
}, },
) )
} }
@ -1303,15 +1304,15 @@ impl Module {
F: RegisterNativeFunction<A, N, C, T, true>, F: RegisterNativeFunction<A, N, C, T, true>,
{ {
let fn_name = name.into(); let fn_name = name.into();
let no_const = false; let is_pure = true;
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
let no_const = no_const || (F::num_params() == 3 && fn_name == crate::engine::FN_IDX_SET); let is_pure = is_pure && (F::num_params() != 3 || fn_name != crate::engine::FN_IDX_SET);
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
let no_const = let is_pure =
no_const || (F::num_params() == 2 && fn_name.starts_with(crate::engine::FN_SET)); is_pure && (F::num_params() != 2 || !fn_name.starts_with(crate::engine::FN_SET));
let func = func.into_callable_function(fn_name.clone(), no_const); let func = func.into_callable_function(fn_name.clone(), is_pure);
self.set_fn( self.set_fn(
fn_name, fn_name,
@ -1350,7 +1351,7 @@ impl Module {
F: RegisterNativeFunction<(Mut<A>,), 1, C, T, true> + SendSync + 'static, F: RegisterNativeFunction<(Mut<A>,), 1, C, T, true> + SendSync + 'static,
{ {
let fn_name = crate::engine::make_getter(name.as_ref()); let fn_name = crate::engine::make_getter(name.as_ref());
let func = func.into_callable_function(fn_name.clone(), false); let func = func.into_callable_function(fn_name.clone(), true);
self.set_fn( self.set_fn(
fn_name, fn_name,
@ -1394,7 +1395,7 @@ impl Module {
F: RegisterNativeFunction<(Mut<A>, T), 2, C, (), true> + SendSync + 'static, F: RegisterNativeFunction<(Mut<A>, T), 2, C, (), true> + SendSync + 'static,
{ {
let fn_name = crate::engine::make_setter(name.as_ref()); let fn_name = crate::engine::make_setter(name.as_ref());
let func = func.into_callable_function(fn_name.clone(), true); let func = func.into_callable_function(fn_name.clone(), false);
self.set_fn( self.set_fn(
fn_name, fn_name,
@ -1510,7 +1511,7 @@ impl Module {
FnAccess::Public, FnAccess::Public,
None, None,
F::param_types(), F::param_types(),
func.into_callable_function(crate::engine::FN_IDX_GET.into(), false), func.into_callable_function(crate::engine::FN_IDX_GET.into(), true),
) )
} }
@ -1571,7 +1572,7 @@ impl Module {
FnAccess::Public, FnAccess::Public,
None, None,
F::param_types(), F::param_types(),
func.into_callable_function(crate::engine::FN_IDX_SET.into(), true), func.into_callable_function(crate::engine::FN_IDX_SET.into(), false),
) )
} }
@ -1986,23 +1987,35 @@ impl Module {
} }
/// Get an iterator to the sub-modules in the [`Module`]. /// Get an iterator to the sub-modules in the [`Module`].
#[inline] #[inline(always)]
pub fn iter_sub_modules(&self) -> impl Iterator<Item = (&str, &SharedModule)> { pub fn iter_sub_modules(&self) -> impl Iterator<Item = (&str, &SharedModule)> {
self.iter_sub_modules_raw().map(|(k, m)| (k.as_str(), m))
}
/// Get an iterator to the sub-modules in the [`Module`].
#[inline]
pub(crate) fn iter_sub_modules_raw(
&self,
) -> impl Iterator<Item = (&Identifier, &SharedModule)> {
self.modules self.modules
.as_ref() .as_ref()
.into_iter() .into_iter()
.flatten() .flatten()
.map(|(k, m)| (k.as_str(), m)) .map(|(k, m)| (k, m))
} }
/// Get an iterator to the variables in the [`Module`]. /// Get an iterator to the variables in the [`Module`].
#[inline] #[inline(always)]
pub fn iter_var(&self) -> impl Iterator<Item = (&str, &Dynamic)> { pub fn iter_var(&self) -> impl Iterator<Item = (&str, &Dynamic)> {
self.iter_var_raw().map(|(k, v)| (k.as_str(), v))
}
/// Get an iterator to the variables in the [`Module`].
#[inline]
pub(crate) fn iter_var_raw(&self) -> impl Iterator<Item = (&Identifier, &Dynamic)> {
self.variables self.variables
.as_ref() .as_ref()
.into_iter() .into_iter()
.flatten() .flatten()
.map(|(k, v)| (k.as_str(), v)) .map(|(k, v)| (k, v))
} }
/// Get an iterator to the functions in the [`Module`]. /// Get an iterator to the functions in the [`Module`].

View File

@ -14,10 +14,9 @@ use crate::func::builtin::get_builtin_binary_op_fn;
use crate::func::hashing::get_hasher; use crate::func::hashing::get_hasher;
use crate::module::ModuleFlags; use crate::module::ModuleFlags;
use crate::tokenizer::Token; use crate::tokenizer::Token;
use crate::types::dynamic::AccessMode;
use crate::{ use crate::{
calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnPtr, Identifier, ImmutableString, Position, calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnPtr, ImmutableString, Position, Scope,
Scope, StaticVec, AST, StaticVec, AST,
}; };
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -54,8 +53,8 @@ impl Default for OptimizationLevel {
struct OptimizerState<'a> { struct OptimizerState<'a> {
/// Has the [`AST`] been changed during this pass? /// Has the [`AST`] been changed during this pass?
changed: bool, changed: bool,
/// Collection of constants to use for eager function evaluations. /// Collection of variables/constants to use for eager function evaluations.
variables: StaticVec<(Identifier, AccessMode, Option<Dynamic>)>, variables: StaticVec<(ImmutableString, Option<Dynamic>)>,
/// Activate constants propagation? /// Activate constants propagation?
propagate_constants: bool, propagate_constants: bool,
/// An [`Engine`] instance for eager function evaluation. /// An [`Engine`] instance for eager function evaluation.
@ -115,14 +114,11 @@ impl<'a> OptimizerState<'a> {
self.variables.truncate(len); self.variables.truncate(len);
} }
/// Add a new variable to the list. /// Add a new variable to the list.
///
/// `Some(value)` if constant, `None` or otherwise.
#[inline(always)] #[inline(always)]
pub fn push_var( pub fn push_var(&mut self, name: ImmutableString, value: Option<Dynamic>) {
&mut self, self.variables.push((name, value));
name: impl Into<Identifier>,
access: AccessMode,
value: Option<Dynamic>,
) {
self.variables.push((name.into(), access, value));
} }
/// Look up a constant from the list. /// Look up a constant from the list.
#[inline] #[inline]
@ -131,12 +127,9 @@ impl<'a> OptimizerState<'a> {
return None; return None;
} }
for (n, access, value) in self.variables.iter().rev() { for (n, value) in self.variables.iter().rev() {
if n == name { if n.as_str() == name {
return match access { return value.as_ref();
AccessMode::ReadWrite => None,
AccessMode::ReadOnly => value.as_ref(),
};
} }
} }
@ -237,16 +230,13 @@ fn optimize_stmt_block(
optimize_expr(&mut x.1, state, false); optimize_expr(&mut x.1, state, false);
if x.1.is_constant() { if x.1.is_constant() {
state.push_var( state
x.0.as_str(), .push_var(x.0.name.clone(), Some(x.1.get_literal_value().unwrap()));
AccessMode::ReadOnly,
x.1.get_literal_value(),
);
} }
} else { } else {
// Add variables into the state // Add variables into the state
optimize_expr(&mut x.1, state, false); optimize_expr(&mut x.1, state, false);
state.push_var(x.0.as_str(), AccessMode::ReadWrite, None); state.push_var(x.0.name.clone(), None);
} }
} }
// Optimize the statement // Optimize the statement
@ -858,7 +848,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Stmt::Share(x) => { Stmt::Share(x) => {
let len = x.len(); let len = x.len();
x.retain(|(v, _, _)| !state.find_constant(v).is_some()); x.retain(|(v, _)| !state.find_constant(v).is_some());
if x.len() != len { if x.len() != len {
state.set_dirty(); state.set_dirty();
} }
@ -1335,20 +1325,14 @@ impl Engine {
.iter() .iter()
.rev() .rev()
.flat_map(|m| m.iter_var()) .flat_map(|m| m.iter_var())
.for_each(|(name, value)| { .for_each(|(name, value)| state.push_var(name.into(), Some(value.clone())));
state.push_var(name, AccessMode::ReadOnly, Some(value.clone()))
});
// Add constants and variables from the scope // Add constants and variables from the scope
scope scope
.into_iter() .into_iter()
.flat_map(Scope::iter) .flat_map(Scope::iter)
.for_each(|(name, constant, value)| { .for_each(|(name, constant, value)| {
if constant { state.push_var(name.into(), if constant { Some(value) } else { None });
state.push_var(name, AccessMode::ReadOnly, Some(value));
} else {
state.push_var(name, AccessMode::ReadWrite, None);
}
}); });
optimize_stmt_block(statements, &mut state, true, false, true) optimize_stmt_block(statements, &mut state, true, false, true)

View File

@ -1728,6 +1728,9 @@ impl Engine {
) -> ParseResult<Expr> { ) -> ParseResult<Expr> {
let mut settings = settings; let mut settings = settings;
// Break just in case `lhs` is `Expr::Dot` or `Expr::Index`
let mut parent_options = ASTFlags::BREAK;
// Tail processing all possible postfix operators // Tail processing all possible postfix operators
loop { loop {
let (tail_token, ..) = input.peek().expect(NEVER_ENDS); let (tail_token, ..) = input.peek().expect(NEVER_ENDS);
@ -1842,13 +1845,16 @@ impl Engine {
let rhs = let rhs =
self.parse_primary(input, state, lib, settings.level_up()?, options)?; self.parse_primary(input, state, lib, settings.level_up()?, options)?;
Self::make_dot_expr(state, expr, rhs, ASTFlags::empty(), op_flags, tail_pos)? Self::make_dot_expr(state, expr, rhs, parent_options, op_flags, tail_pos)?
} }
// Unknown postfix operator // Unknown postfix operator
(expr, token) => { (expr, token) => {
unreachable!("unknown postfix operator '{}' for {:?}", token, expr) unreachable!("unknown postfix operator '{}' for {:?}", token, expr)
} }
} };
// The chain is now extended
parent_options = ASTFlags::empty();
} }
// Cache the hash key for namespace-qualified variables // Cache the hash key for namespace-qualified variables
@ -2431,13 +2437,7 @@ impl Engine {
} }
#[cfg(not(feature = "no_custom_syntax"))] #[cfg(not(feature = "no_custom_syntax"))]
Token::Custom(s) Token::Custom(s) if self.is_custom_keyword(s.as_str()) => {
if self
.custom_keywords
.as_ref()
.and_then(|m| m.get(s.as_str()))
.map_or(false, Option::is_some) =>
{
op_base.hashes = if native_only { op_base.hashes = if native_only {
FnCallHashes::from_native_only(calc_fn_hash(None, &s, 2)) FnCallHashes::from_native_only(calc_fn_hash(None, &s, 2))
} else { } else {
@ -3781,9 +3781,9 @@ impl Engine {
statements.push(Stmt::Share( statements.push(Stmt::Share(
externals externals
.into_iter() .into_iter()
.map(|Ident { name, pos }| { .map(|var| {
let (index, _) = parent.access_var(&name, lib, pos); let (index, _) = parent.access_var(&var.name, lib, var.pos);
(name, index, pos) (var, index)
}) })
.collect::<crate::FnArgsVec<_>>() .collect::<crate::FnArgsVec<_>>()
.into(), .into(),

View File

@ -2492,7 +2492,7 @@ impl<'a> Iterator for TokenIterator<'a> {
Some((Token::Reserved(s), pos)) => (match Some((Token::Reserved(s), pos)) => (match
(s.as_str(), (s.as_str(),
#[cfg(not(feature = "no_custom_syntax"))] #[cfg(not(feature = "no_custom_syntax"))]
self.engine.custom_keywords.as_ref().map_or(false, |m| m.contains_key(&*s)), self.engine.is_custom_keyword(&*s),
#[cfg(feature = "no_custom_syntax")] #[cfg(feature = "no_custom_syntax")]
false false
) )
@ -2529,7 +2529,7 @@ impl<'a> Iterator for TokenIterator<'a> {
#[cfg(feature = "no_custom_syntax")] #[cfg(feature = "no_custom_syntax")]
(.., true) => unreachable!("no custom operators"), (.., true) => unreachable!("no custom operators"),
// Reserved keyword that is not custom and disabled. // Reserved keyword that is not custom and disabled.
(token, false) if self.engine.disabled_symbols.as_ref().map_or(false,|m| m.contains(token)) => { (token, false) if self.engine.is_symbol_disabled(token) => {
let msg = format!("reserved {} '{token}' is disabled", if is_valid_identifier(token) { "keyword"} else {"symbol"}); let msg = format!("reserved {} '{token}' is disabled", if is_valid_identifier(token) { "keyword"} else {"symbol"});
Token::LexError(LERR::ImproperSymbol(s.to_string(), msg).into()) Token::LexError(LERR::ImproperSymbol(s.to_string(), msg).into())
}, },
@ -2538,13 +2538,13 @@ impl<'a> Iterator for TokenIterator<'a> {
}, pos), }, pos),
// Custom keyword // Custom keyword
#[cfg(not(feature = "no_custom_syntax"))] #[cfg(not(feature = "no_custom_syntax"))]
Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.as_ref().map_or(false,|m| m.contains_key(&*s)) => { Some((Token::Identifier(s), pos)) if self.engine.is_custom_keyword(&*s) => {
(Token::Custom(s), pos) (Token::Custom(s), pos)
} }
// Custom keyword/symbol - must be disabled // Custom keyword/symbol - must be disabled
#[cfg(not(feature = "no_custom_syntax"))] #[cfg(not(feature = "no_custom_syntax"))]
Some((token, pos)) if token.is_literal() && self.engine.custom_keywords.as_ref().map_or(false,|m| m.contains_key(token.literal_syntax())) => { Some((token, pos)) if token.is_literal() && self.engine.is_custom_keyword(token.literal_syntax()) => {
if self.engine.disabled_symbols.as_ref().map_or(false,|m| m.contains(token.literal_syntax())) { if self.engine.is_symbol_disabled(token.literal_syntax()) {
// Disabled standard keyword/symbol // Disabled standard keyword/symbol
(Token::Custom(Box::new(token.literal_syntax().into())), pos) (Token::Custom(Box::new(token.literal_syntax().into())), pos)
} else { } else {
@ -2553,7 +2553,7 @@ impl<'a> Iterator for TokenIterator<'a> {
} }
} }
// Disabled symbol // Disabled symbol
Some((token, pos)) if token.is_literal() && self.engine.disabled_symbols.as_ref().map_or(false,|m| m.contains(token.literal_syntax())) => { Some((token, pos)) if token.is_literal() && self.engine.is_symbol_disabled(token.literal_syntax()) => {
(Token::Reserved(Box::new(token.literal_syntax().into())), pos) (Token::Reserved(Box::new(token.literal_syntax().into())), pos)
} }
// Normal symbol // Normal symbol

View File

@ -1017,18 +1017,23 @@ impl Dynamic {
} }
/// Create a [`Dynamic`] from any type. A [`Dynamic`] value is simply returned as is. /// Create a [`Dynamic`] from any type. A [`Dynamic`] value is simply returned as is.
/// ///
/// # Notes /// # Arrays
/// ///
/// Beware that you need to pass in an [`Array`][crate::Array] type for it to be recognized as /// Beware that you need to pass in an [`Array`][crate::Array] type for it to be recognized as
/// an [`Array`][crate::Array]. A [`Vec<T>`][Vec] does not get automatically converted to an /// an [`Array`][crate::Array]. A [`Vec<T>`][Vec] does not get automatically converted to an
/// [`Array`][crate::Array], but will be a custom type instead (stored as a trait object). Use /// [`Array`][crate::Array], but will be a custom type instead (stored as a trait object).
/// [`Dynamic::from_array`] to convert a [`Vec<T>`][Vec] into a [`Dynamic`] as an ///
/// [`Array`][crate::Array] value. /// Use `array.into()` or `array.into_iter()` to convert a [`Vec<T>`][Vec] into a [`Dynamic`] as
/// an [`Array`][crate::Array] value. See the examples for details.
///
/// # Hash Maps
/// ///
/// Similarly, passing in a [`HashMap<String, T>`][std::collections::HashMap] or /// Similarly, passing in a [`HashMap<String, T>`][std::collections::HashMap] or
/// [`BTreeMap<String, T>`][std::collections::BTreeMap] will not get a [`Map`][crate::Map] but a /// [`BTreeMap<String, T>`][std::collections::BTreeMap] will not get a [`Map`][crate::Map] but a
/// custom type. Again, use [`Dynamic::from_map`] to get a [`Dynamic`] with a [`Map`][crate::Map] /// custom type.
/// value. ///
/// Again, use `map.into()` to get a [`Dynamic`] with a [`Map`][crate::Map] value.
/// See the examples for details.
/// ///
/// # Examples /// # Examples
/// ///
@ -1046,6 +1051,33 @@ impl Dynamic {
/// let new_result = Dynamic::from(result); /// let new_result = Dynamic::from(result);
/// assert_eq!(new_result.type_name(), "string"); /// assert_eq!(new_result.type_name(), "string");
/// assert_eq!(new_result.to_string(), "hello"); /// assert_eq!(new_result.to_string(), "hello");
///
/// # #[cfg(not(feature = "no_index"))]
/// # {
/// // Arrays - this is a custom object!
/// let result = Dynamic::from(vec![1_i64, 2, 3]);
/// assert_eq!(result.type_name(), "alloc::vec::Vec<i64>");
///
/// // Use '.into()' to convert a Vec<T> into an Array
/// let result: Dynamic = vec![1_i64, 2, 3].into();
/// assert_eq!(result.type_name(), "array");
/// # }
///
/// # #[cfg(not(feature = "no_object"))]
/// # {
/// # use std::collections::HashMap;
/// // Hash map
/// let mut map = HashMap::new();
/// map.insert("a".to_string(), 1_i64);
///
/// // This is a custom object!
/// let result = Dynamic::from(map.clone());
/// assert_eq!(result.type_name(), "std::collections::hash::map::HashMap<alloc::string::String, i64>");
///
/// // Use '.into()' to convert a HashMap<String, T> into an object map
/// let result: Dynamic = map.into();
/// assert_eq!(result.type_name(), "map");
/// # }
/// ``` /// ```
#[inline] #[inline]
pub fn from<T: Variant + Clone>(value: T) -> Self { pub fn from<T: Variant + Clone>(value: T) -> Self {

View File

@ -203,6 +203,22 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[cfg(not(feature = "no_float"))]
#[cfg(not(feature = "no_object"))]
#[test]
fn test_array_chaining() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert!(engine.eval::<bool>(
"
let v = [ PI() ];
( v[0].cos() ).sin() == v[0].cos().sin()
"
)?);
Ok(())
}
#[test] #[test]
fn test_array_index_types() -> Result<(), Box<EvalAltResult>> { fn test_array_index_types() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();
@ -210,34 +226,25 @@ fn test_array_index_types() -> Result<(), Box<EvalAltResult>> {
engine.compile("[1, 2, 3][0]['x']")?; engine.compile("[1, 2, 3][0]['x']")?;
assert!(matches!( assert!(matches!(
engine engine.compile("[1, 2, 3]['x']").unwrap_err().err_type(),
.compile("[1, 2, 3]['x']")
.expect_err("should error")
.err_type(),
ParseErrorType::MalformedIndexExpr(..) ParseErrorType::MalformedIndexExpr(..)
)); ));
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
assert!(matches!( assert!(matches!(
engine engine.compile("[1, 2, 3][123.456]").unwrap_err().err_type(),
.compile("[1, 2, 3][123.456]")
.expect_err("should error")
.err_type(),
ParseErrorType::MalformedIndexExpr(..) ParseErrorType::MalformedIndexExpr(..)
)); ));
assert!(matches!( assert!(matches!(
engine engine.compile("[1, 2, 3][()]").unwrap_err().err_type(),
.compile("[1, 2, 3][()]")
.expect_err("should error")
.err_type(),
ParseErrorType::MalformedIndexExpr(..) ParseErrorType::MalformedIndexExpr(..)
)); ));
assert!(matches!( assert!(matches!(
engine engine
.compile(r#"[1, 2, 3]["hello"]"#) .compile(r#"[1, 2, 3]["hello"]"#)
.expect_err("should error") .unwrap_err()
.err_type(), .err_type(),
ParseErrorType::MalformedIndexExpr(..) ParseErrorType::MalformedIndexExpr(..)
)); ));
@ -245,7 +252,7 @@ fn test_array_index_types() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
engine engine
.compile("[1, 2, 3][true && false]") .compile("[1, 2, 3][true && false]")
.expect_err("should error") .unwrap_err()
.err_type(), .err_type(),
ParseErrorType::MalformedIndexExpr(..) ParseErrorType::MalformedIndexExpr(..)
)); ));
@ -517,9 +524,9 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
engine.eval::<()>( engine.eval::<()>(
" "
let x = [1, 2, 3, 2, 1]; let x = [1, 2, 3, 2, 1];
x.find(|v| v > 4) x.find(|v| v > 4)
", ",
)?; )?;
assert_eq!( assert_eq!(
@ -534,9 +541,9 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
engine.eval::<()>( engine.eval::<()>(
" "
let x = [#{alice: 1}, #{bob: 2}, #{clara: 3}]; let x = [#{alice: 1}, #{bob: 2}, #{clara: 3}];
x.find_map(|v| v.dave) x.find_map(|v| v.dave)
", ",
)?; )?;
Ok(()) Ok(())

View File

@ -21,71 +21,44 @@ fn test_assignments_bad_lhs() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();
assert_eq!( assert_eq!(
*engine *engine.compile("(x+y) = 42;").unwrap_err().err_type(),
.compile("(x+y) = 42;")
.expect_err("should error")
.err_type(),
ParseErrorType::AssignmentToInvalidLHS(String::new()) ParseErrorType::AssignmentToInvalidLHS(String::new())
); );
assert_eq!( assert_eq!(
*engine *engine.compile("foo(x) = 42;").unwrap_err().err_type(),
.compile("foo(x) = 42;")
.expect_err("should error")
.err_type(),
ParseErrorType::AssignmentToInvalidLHS(String::new()) ParseErrorType::AssignmentToInvalidLHS(String::new())
); );
assert_eq!( assert_eq!(
*engine *engine.compile("true = 42;").unwrap_err().err_type(),
.compile("true = 42;")
.expect_err("should error")
.err_type(),
ParseErrorType::AssignmentToConstant(String::new()) ParseErrorType::AssignmentToConstant(String::new())
); );
assert_eq!( assert_eq!(
*engine *engine.compile("123 = 42;").unwrap_err().err_type(),
.compile("123 = 42;")
.expect_err("should error")
.err_type(),
ParseErrorType::AssignmentToConstant(String::new()) ParseErrorType::AssignmentToConstant(String::new())
); );
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
{ {
assert_eq!( assert_eq!(
*engine *engine.compile("x.foo() = 42;").unwrap_err().err_type(),
.compile("x.foo() = 42;")
.expect_err("should error")
.err_type(),
ParseErrorType::AssignmentToInvalidLHS(String::new()) ParseErrorType::AssignmentToInvalidLHS(String::new())
); );
assert_eq!( assert_eq!(
*engine *engine.compile("x.foo().x.y = 42;").unwrap_err().err_type(),
.compile("x.foo().x.y = 42;")
.expect_err("should error")
.err_type(),
ParseErrorType::AssignmentToInvalidLHS(String::new()) ParseErrorType::AssignmentToInvalidLHS(String::new())
); );
assert_eq!( assert_eq!(
*engine *engine.compile("x.y.z.foo() = 42;").unwrap_err().err_type(),
.compile("x.y.z.foo() = 42;")
.expect_err("should error")
.err_type(),
ParseErrorType::AssignmentToInvalidLHS(String::new()) ParseErrorType::AssignmentToInvalidLHS(String::new())
); );
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
assert_eq!( assert_eq!(
*engine *engine.compile("x.foo()[0] = 42;").unwrap_err().err_type(),
.compile("x.foo()[0] = 42;")
.expect_err("should error")
.err_type(),
ParseErrorType::AssignmentToInvalidLHS(String::new()) ParseErrorType::AssignmentToInvalidLHS(String::new())
); );
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
assert_eq!( assert_eq!(
*engine *engine.compile("x[y].z.foo() = 42;").unwrap_err().err_type(),
.compile("x[y].z.foo() = 42;")
.expect_err("should error")
.err_type(),
ParseErrorType::AssignmentToInvalidLHS(String::new()) ParseErrorType::AssignmentToInvalidLHS(String::new())
); );
} }

View File

@ -47,10 +47,7 @@ fn test_closures() -> Result<(), Box<EvalAltResult>> {
scope.push("x", 42 as INT); scope.push("x", 42 as INT);
assert!(matches!( assert!(matches!(
engine engine.compile_expression("|x| {}").unwrap_err().err_type(),
.compile_expression("|x| {}")
.expect_err("should error")
.err_type(),
ParseErrorType::BadInput(..) ParseErrorType::BadInput(..)
)); ));
@ -292,7 +289,7 @@ fn test_closures_data_race() -> Result<(), Box<EvalAltResult>> {
a a
" "
) )
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorDataRace(..) EvalAltResult::ErrorDataRace(..)
)); ));

View File

@ -86,7 +86,7 @@ fn test_constant_mut() -> Result<(), Box<EvalAltResult>> {
MY_NUMBER.value = 42; MY_NUMBER.value = 42;
" "
) )
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorNonPureMethodCallOnConstant(..) EvalAltResult::ErrorNonPureMethodCallOnConstant(..)
)); ));
@ -119,7 +119,7 @@ fn test_constant_mut() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
*engine *engine
.run_with_scope(&mut scope, "MY_NUMBER.value = 42;") .run_with_scope(&mut scope, "MY_NUMBER.value = 42;")
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorNonPureMethodCallOnConstant(..) EvalAltResult::ErrorNonPureMethodCallOnConstant(..)
)); ));

View File

@ -13,11 +13,11 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
// 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");
assert!(matches!( assert!(matches!(
engine.compile("while false {}").expect_err("should error").err_type(), engine.compile("while false {}").unwrap_err().err_type(),
ParseErrorType::Reserved(err) if err == "while" ParseErrorType::Reserved(err) if err == "while"
)); ));
assert!(matches!( assert!(matches!(
engine.compile("let while = 0").expect_err("should error").err_type(), engine.compile("let while = 0").unwrap_err().err_type(),
ParseErrorType::Reserved(err) if err == "while" ParseErrorType::Reserved(err) if err == "while"
)); ));
@ -127,7 +127,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
*engine *engine
.run("let foo = (exec [x<<15] -> { x += 2 } while x < 42) * 10;") .run("let foo = (exec [x<<15] -> { x += 2 } while x < 42) * 10;")
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorRuntime(..) EvalAltResult::ErrorRuntime(..)
)); ));
@ -195,7 +195,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
assert_eq!( assert_eq!(
*engine *engine
.register_custom_syntax(["!"], false, |_, _| Ok(Dynamic::UNIT)) .register_custom_syntax(["!"], false, |_, _| Ok(Dynamic::UNIT))
.expect_err("should error") .unwrap_err()
.err_type(), .err_type(),
ParseErrorType::BadInput(LexError::ImproperSymbol( ParseErrorType::BadInput(LexError::ImproperSymbol(
"!".to_string(), "!".to_string(),
@ -364,10 +364,7 @@ fn test_custom_syntax_raw() -> Result<(), Box<EvalAltResult>> {
); );
assert_eq!(engine.eval::<INT>("(hello kitty) + foo")?, 1041); assert_eq!(engine.eval::<INT>("(hello kitty) + foo")?, 1041);
assert_eq!( assert_eq!(
*engine *engine.compile("hello hey").unwrap_err().err_type(),
.compile("hello hey")
.expect_err("should error")
.err_type(),
ParseErrorType::BadInput(LexError::ImproperSymbol("hey".to_string(), String::new())) ParseErrorType::BadInput(LexError::ImproperSymbol("hey".to_string(), String::new()))
); );

View File

@ -15,7 +15,7 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
assert_eq!( assert_eq!(
*engine *engine
.compile(r#"let x = "hello, world!";"#) .compile(r#"let x = "hello, world!";"#)
.expect_err("should error") .unwrap_err()
.err_type(), .err_type(),
ParseErrorType::LiteralTooLarge("Length of string".to_string(), 10) ParseErrorType::LiteralTooLarge("Length of string".to_string(), 10)
); );
@ -23,7 +23,7 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
assert_eq!( assert_eq!(
*engine *engine
.compile(r#"let x = "朝に紅顔、暮に白骨";"#) .compile(r#"let x = "朝に紅顔、暮に白骨";"#)
.expect_err("should error") .unwrap_err()
.err_type(), .err_type(),
ParseErrorType::LiteralTooLarge("Length of string".to_string(), 10) ParseErrorType::LiteralTooLarge("Length of string".to_string(), 10)
); );
@ -37,7 +37,7 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
x + y x + y
"# "#
) )
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorDataTooLarge(..) EvalAltResult::ErrorDataTooLarge(..)
)); ));
@ -51,7 +51,7 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
x x
"# "#
) )
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorDataTooLarge(..) EvalAltResult::ErrorDataTooLarge(..)
)); ));
@ -83,7 +83,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
assert_eq!( assert_eq!(
*engine *engine
.compile("let x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];") .compile("let x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];")
.expect_err("should error") .unwrap_err()
.err_type(), .err_type(),
ParseErrorType::LiteralTooLarge("Size of array literal".to_string(), 10) ParseErrorType::LiteralTooLarge("Size of array literal".to_string(), 10)
); );
@ -97,7 +97,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
x + y x + y
" "
) )
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorDataTooLarge(..) EvalAltResult::ErrorDataTooLarge(..)
)); ));
@ -130,7 +130,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
} }
" "
) )
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorDataTooLarge(..) EvalAltResult::ErrorDataTooLarge(..)
)); ));
@ -142,7 +142,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
loop { x[0] = x; } loop { x[0] = x; }
" "
) )
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorDataTooLarge(..) EvalAltResult::ErrorDataTooLarge(..)
)); ));
@ -168,7 +168,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
x x
" "
) )
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorDataTooLarge(..) EvalAltResult::ErrorDataTooLarge(..)
)); ));
@ -190,7 +190,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
[x, x, x, x] [x, x, x, x]
" "
) )
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorDataTooLarge(..) EvalAltResult::ErrorDataTooLarge(..)
)); ));
@ -203,7 +203,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
[x, x, x, x] [x, x, x, x]
" "
) )
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorDataTooLarge(..) EvalAltResult::ErrorDataTooLarge(..)
)); ));
@ -217,7 +217,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
[z, z, z] [z, z, z]
" "
) )
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorDataTooLarge(..) EvalAltResult::ErrorDataTooLarge(..)
)); ));
@ -265,7 +265,7 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
.compile( .compile(
"let x = #{a:1,b:2,c:3,d:4,e:5,f:6,g:7,h:8,i:9,j:10,k:11,l:12,m:13,n:14,o:15};" "let x = #{a:1,b:2,c:3,d:4,e:5,f:6,g:7,h:8,i:9,j:10,k:11,l:12,m:13,n:14,o:15};"
) )
.expect_err("should error") .unwrap_err()
.err_type(), .err_type(),
ParseErrorType::LiteralTooLarge( ParseErrorType::LiteralTooLarge(
"Number of properties in object map literal".to_string(), "Number of properties in object map literal".to_string(),
@ -281,7 +281,7 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
loop { x.a = x; } loop { x.a = x; }
" "
) )
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorDataTooLarge(..) EvalAltResult::ErrorDataTooLarge(..)
)); ));
@ -294,7 +294,7 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
x + y x + y
" "
) )
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorDataTooLarge(..) EvalAltResult::ErrorDataTooLarge(..)
)); ));
@ -306,7 +306,7 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
#{u:x, v:x, w:x, z:x} #{u:x, v:x, w:x, z:x}
" "
) )
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorDataTooLarge(..) EvalAltResult::ErrorDataTooLarge(..)
)); ));
@ -319,7 +319,7 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
#{u:x, v:x, w:x, z:x} #{u:x, v:x, w:x, z:x}
" "
) )
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorDataTooLarge(..) EvalAltResult::ErrorDataTooLarge(..)
)); ));

View File

@ -169,7 +169,7 @@ fn test_eval_disabled() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
engine engine
.compile(r#"eval("40 + 2")"#) .compile(r#"eval("40 + 2")"#)
.expect_err("should error") .unwrap_err()
.err_type(), .err_type(),
ParseErrorType::BadInput(LexError::ImproperSymbol(err, ..)) if err == "eval" ParseErrorType::BadInput(LexError::ImproperSymbol(err, ..)) if err == "eval"
)); ));

View File

@ -72,7 +72,7 @@ fn test_fn_ptr() -> Result<(), Box<EvalAltResult>> {
x x
"# "#
) )
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorInFunctionCall(fn_name, _, err, ..) EvalAltResult::ErrorInFunctionCall(fn_name, _, err, ..)
if fn_name == "foo" && matches!(*err, EvalAltResult::ErrorUnboundThis(..)) if fn_name == "foo" && matches!(*err, EvalAltResult::ErrorUnboundThis(..))
)); ));

View File

@ -93,7 +93,7 @@ fn test_functions_global_module() -> Result<(), Box<EvalAltResult>> {
const ANSWER = 42; const ANSWER = 42;
foo() foo()
} }
").expect_err("should error"), ").unwrap_err(),
EvalAltResult::ErrorInFunctionCall(.., err, _) EvalAltResult::ErrorInFunctionCall(.., err, _)
if matches!(&*err, EvalAltResult::ErrorVariableNotFound(v, ..) if v == "global::ANSWER") if matches!(&*err, EvalAltResult::ErrorVariableNotFound(v, ..) if v == "global::ANSWER")
)); ));
@ -112,7 +112,7 @@ fn test_functions_global_module() -> Result<(), Box<EvalAltResult>> {
const LOCAL_VALUE = 42; const LOCAL_VALUE = 42;
global::LOCAL_VALUE global::LOCAL_VALUE
}); });
").expect_err("should error"), ").unwrap_err(),
EvalAltResult::ErrorInFunctionCall(.., err, _) EvalAltResult::ErrorInFunctionCall(.., err, _)
if matches!(&*err, EvalAltResult::ErrorVariableNotFound(v, ..) if v == "global::LOCAL_VALUE") if matches!(&*err, EvalAltResult::ErrorVariableNotFound(v, ..) if v == "global::LOCAL_VALUE")
)); ));

View File

@ -109,7 +109,7 @@ fn test_internal_fn_overloading() -> Result<(), Box<EvalAltResult>> {
fn abc(x) { x - 42 } fn abc(x) { x - 42 }
" "
) )
.expect_err("should error") .unwrap_err()
.err_type(), .err_type(),
ParseErrorType::FnDuplicatedDefinition("abc".to_string(), 1) ParseErrorType::FnDuplicatedDefinition("abc".to_string(), 1)
); );
@ -125,7 +125,7 @@ fn test_internal_fn_params() -> Result<(), Box<EvalAltResult>> {
assert_eq!( assert_eq!(
*engine *engine
.compile("fn hello(x, x) { x }") .compile("fn hello(x, x) { x }")
.expect_err("should error") .unwrap_err()
.err_type(), .err_type(),
ParseErrorType::FnDuplicatedParam("hello".to_string(), "x".to_string()) ParseErrorType::FnDuplicatedParam("hello".to_string(), "x".to_string())
); );
@ -169,7 +169,7 @@ fn test_function_pointers() -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
assert!(matches!( assert!(matches!(
*engine.eval::<INT>(r#"let f = Fn("abc"); f.call(0)"#).expect_err("should error"), *engine.eval::<INT>(r#"let f = Fn("abc"); f.call(0)"#).unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("abc (") EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("abc (")
)); ));
@ -247,7 +247,7 @@ fn test_internal_fn_bang() -> Result<(), Box<EvalAltResult>> {
y.foo!(); y.foo!();
" "
) )
.expect_err("should error") .unwrap_err()
.err_type(), .err_type(),
ParseErrorType::MalformedCapture(..) ParseErrorType::MalformedCapture(..)
)); ));

View File

@ -27,10 +27,7 @@ fn test_loop() -> Result<(), Box<EvalAltResult>> {
); );
assert_eq!( assert_eq!(
*engine *engine.compile("let x = 0; break;").unwrap_err().err_type(),
.compile("let x = 0; break;")
.expect_err("should error")
.err_type(),
ParseErrorType::LoopBreak ParseErrorType::LoopBreak
); );
@ -38,7 +35,7 @@ fn test_loop() -> Result<(), Box<EvalAltResult>> {
assert_eq!( assert_eq!(
*engine *engine
.compile("loop { let f = || { break; } }") .compile("loop { let f = || { break; } }")
.expect_err("should error") .unwrap_err()
.err_type(), .err_type(),
ParseErrorType::LoopBreak ParseErrorType::LoopBreak
); );
@ -46,7 +43,7 @@ fn test_loop() -> Result<(), Box<EvalAltResult>> {
assert_eq!( assert_eq!(
*engine *engine
.compile("let x = 0; if x > 0 { continue; }") .compile("let x = 0; if x > 0 { continue; }")
.expect_err("should error") .unwrap_err()
.err_type(), .err_type(),
ParseErrorType::LoopBreak ParseErrorType::LoopBreak
); );

View File

@ -52,7 +52,7 @@ b`: 1}; y["a\nb"]
assert!(matches!( assert!(matches!(
*engine *engine
.eval::<INT>("let y = #{`a${1}`: 1}; y.a1") .eval::<INT>("let y = #{`a${1}`: 1}; y.a1")
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorParsing(ParseErrorType::PropertyExpected, ..) EvalAltResult::ErrorParsing(ParseErrorType::PropertyExpected, ..)
)); ));
@ -116,7 +116,7 @@ fn test_map_prop() -> Result<(), Box<EvalAltResult>> {
engine.set_fail_on_invalid_map_property(true); engine.set_fail_on_invalid_map_property(true);
assert!( assert!(
matches!(*engine.eval::<()>("let x = #{a: 42}; x.b").expect_err("should error"), matches!(*engine.eval::<()>("let x = #{a: 42}; x.b").unwrap_err(),
EvalAltResult::ErrorPropertyNotFound(prop, _) if prop == "b" EvalAltResult::ErrorPropertyNotFound(prop, _) if prop == "b"
) )
); );
@ -134,7 +134,7 @@ fn test_map_index_types() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
engine engine
.compile("#{a:1, b:2, c:3}['x']") .compile("#{a:1, b:2, c:3}['x']")
.expect_err("should error") .unwrap_err()
.err_type(), .err_type(),
ParseErrorType::MalformedIndexExpr(..) ParseErrorType::MalformedIndexExpr(..)
)); ));
@ -142,7 +142,7 @@ fn test_map_index_types() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
engine engine
.compile("#{a:1, b:2, c:3}[1]") .compile("#{a:1, b:2, c:3}[1]")
.expect_err("should error") .unwrap_err()
.err_type(), .err_type(),
ParseErrorType::MalformedIndexExpr(..) ParseErrorType::MalformedIndexExpr(..)
)); ));
@ -151,7 +151,7 @@ fn test_map_index_types() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
engine engine
.compile("#{a:1, b:2, c:3}[123.456]") .compile("#{a:1, b:2, c:3}[123.456]")
.expect_err("should error") .unwrap_err()
.err_type(), .err_type(),
ParseErrorType::MalformedIndexExpr(..) ParseErrorType::MalformedIndexExpr(..)
)); ));
@ -159,7 +159,7 @@ fn test_map_index_types() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
engine engine
.compile("#{a:1, b:2, c:3}[()]") .compile("#{a:1, b:2, c:3}[()]")
.expect_err("should error") .unwrap_err()
.err_type(), .err_type(),
ParseErrorType::MalformedIndexExpr(..) ParseErrorType::MalformedIndexExpr(..)
)); ));
@ -167,7 +167,7 @@ fn test_map_index_types() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
engine engine
.compile("#{a:1, b:2, c:3}[true && false]") .compile("#{a:1, b:2, c:3}[true && false]")
.expect_err("should error") .unwrap_err()
.err_type(), .err_type(),
ParseErrorType::MalformedIndexExpr(..) ParseErrorType::MalformedIndexExpr(..)
)); ));
@ -272,38 +272,32 @@ fn test_map_json() -> Result<(), Box<EvalAltResult>> {
engine.parse_json(json, true)?; engine.parse_json(json, true)?;
assert!(matches!( assert!(matches!(
*engine.parse_json("123", true).expect_err("should error"), *engine.parse_json("123", true).unwrap_err(),
EvalAltResult::ErrorMismatchOutputType(..) EvalAltResult::ErrorMismatchOutputType(..)
)); ));
assert!(matches!( assert!(matches!(
*engine.parse_json("{a:42}", true).expect_err("should error"), *engine.parse_json("{a:42}", true).unwrap_err(),
EvalAltResult::ErrorParsing(..) EvalAltResult::ErrorParsing(..)
)); ));
assert!(matches!( assert!(matches!(
*engine *engine.parse_json("#{a:123}", true).unwrap_err(),
.parse_json("#{a:123}", true)
.expect_err("should error"),
EvalAltResult::ErrorParsing(..) EvalAltResult::ErrorParsing(..)
)); ));
assert!(matches!( assert!(matches!(
*engine.parse_json("{a:()}", true).expect_err("should error"), *engine.parse_json("{a:()}", true).unwrap_err(),
EvalAltResult::ErrorParsing(..) EvalAltResult::ErrorParsing(..)
)); ));
assert!(matches!( assert!(matches!(
*engine *engine.parse_json("#{a:123+456}", true).unwrap_err(),
.parse_json("#{a:123+456}", true)
.expect_err("should error"),
EvalAltResult::ErrorParsing(..) EvalAltResult::ErrorParsing(..)
)); ));
assert!(matches!( assert!(matches!(
*engine *engine.parse_json("{a:`hello${world}`}", true).unwrap_err(),
.parse_json("{a:`hello${world}`}", true)
.expect_err("should error"),
EvalAltResult::ErrorParsing(..) EvalAltResult::ErrorParsing(..)
)); ));

View File

@ -127,7 +127,7 @@ fn test_method_call_typed() -> Result<(), Box<EvalAltResult>> {
foo(1000); foo(1000);
"# "#
) )
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("foo") EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("foo")
)); ));
@ -142,7 +142,7 @@ fn test_method_call_typed() -> Result<(), Box<EvalAltResult>> {
x.foo(1000); x.foo(1000);
"# "#
) )
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("foo") EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("foo")
)); ));

View File

@ -36,21 +36,21 @@ fn test_mismatched_op_custom_type() -> Result<(), Box<EvalAltResult>> {
let x = new_ts(); let x = new_ts();
let y = new_ts(); let y = new_ts();
x == y x == y
").expect_err("should error"), ").unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(f, ..) if f == "== (TestStruct, TestStruct)")); EvalAltResult::ErrorFunctionNotFound(f, ..) if f == "== (TestStruct, TestStruct)"));
assert!( assert!(
matches!(*engine.eval::<bool>("new_ts() == 42").expect_err("should error"), matches!(*engine.eval::<bool>("new_ts() == 42").unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("== (TestStruct, ")) EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("== (TestStruct, "))
); );
assert!(matches!( assert!(matches!(
*engine.eval::<INT>("60 + new_ts()").expect_err("should error"), *engine.eval::<INT>("60 + new_ts()").unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(f, ..) if f == format!("+ ({}, TestStruct)", std::any::type_name::<INT>()) EvalAltResult::ErrorFunctionNotFound(f, ..) if f == format!("+ ({}, TestStruct)", std::any::type_name::<INT>())
)); ));
assert!(matches!( assert!(matches!(
*engine.eval::<TestStruct>("42").expect_err("should error"), *engine.eval::<TestStruct>("42").unwrap_err(),
EvalAltResult::ErrorMismatchOutputType(need, actual, ..) EvalAltResult::ErrorMismatchOutputType(need, actual, ..)
if need == "TestStruct" && actual == std::any::type_name::<INT>() if need == "TestStruct" && actual == std::any::type_name::<INT>()
)); ));

View File

@ -227,7 +227,7 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
sum sum
"# "#
) )
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorTooManyModules(..) EvalAltResult::ErrorTooManyModules(..)
)); ));
@ -250,7 +250,7 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
sum sum
"# "#
) )
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorInFunctionCall(fn_name, ..) if fn_name == "foo" EvalAltResult::ErrorInFunctionCall(fn_name, ..) if fn_name == "foo"
)); ));
@ -403,7 +403,7 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
*engine *engine
.run(r#"import "testing" as ttt; ttt::hidden()"#) .run(r#"import "testing" as ttt; ttt::hidden()"#)
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(fn_name, ..) if fn_name == "ttt::hidden ()" EvalAltResult::ErrorFunctionNotFound(fn_name, ..) if fn_name == "ttt::hidden ()"
)); ));
@ -415,13 +415,13 @@ fn test_module_export() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();
assert!(matches!( assert!(matches!(
engine.compile("let x = 10; { export x; }").expect_err("should error"), engine.compile("let x = 10; { export x; }").unwrap_err(),
ParseError(x, ..) if *x == ParseErrorType::WrongExport ParseError(x, ..) if *x == ParseErrorType::WrongExport
)); ));
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
assert!(matches!( assert!(matches!(
engine.compile("fn abc(x) { export x; }").expect_err("should error"), engine.compile("fn abc(x) { export x; }").unwrap_err(),
ParseError(x, ..) if *x == ParseErrorType::WrongExport ParseError(x, ..) if *x == ParseErrorType::WrongExport
)); ));

View File

@ -18,7 +18,7 @@ fn test_max_operations() -> Result<(), Box<EvalAltResult>> {
engine.run("let x = 0; while x < 20 { x += 1; }")?; engine.run("let x = 0; while x < 20 { x += 1; }")?;
assert!(matches!( assert!(matches!(
*engine.run("for x in 0..500 {}").expect_err("should error"), *engine.run("for x in 0..500 {}").unwrap_err(),
EvalAltResult::ErrorTooManyOperations(..) EvalAltResult::ErrorTooManyOperations(..)
)); ));
@ -41,9 +41,7 @@ fn test_max_operations_literal() -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
assert!(matches!( assert!(matches!(
*engine *engine.run("[1, 2, 3, 4, 5, 6, 7, 8, 9]").unwrap_err(),
.run("[1, 2, 3, 4, 5, 6, 7, 8, 9]")
.expect_err("should error"),
EvalAltResult::ErrorTooManyOperations(..) EvalAltResult::ErrorTooManyOperations(..)
)); ));
@ -54,7 +52,7 @@ fn test_max_operations_literal() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
*engine *engine
.run("#{a:1, b:2, c:3, d:4, e:5, f:6, g:7, h:8, i:9}") .run("#{a:1, b:2, c:3, d:4, e:5, f:6, g:7, h:8, i:9}")
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorTooManyOperations(..) EvalAltResult::ErrorTooManyOperations(..)
)); ));
@ -110,7 +108,7 @@ fn test_max_operations_functions() -> Result<(), Box<EvalAltResult>> {
} }
"#, "#,
) )
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorTooManyOperations(..) EvalAltResult::ErrorTooManyOperations(..)
)); ));
@ -137,7 +135,7 @@ fn test_max_operations_eval() -> Result<(), Box<EvalAltResult>> {
eval(script); eval(script);
"# "#
) )
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorInFunctionCall(.., err, _) if matches!(*err, EvalAltResult::ErrorTooManyOperations(..)) EvalAltResult::ErrorInFunctionCall(.., err, _) if matches!(*err, EvalAltResult::ErrorTooManyOperations(..))
)); ));
@ -162,7 +160,7 @@ fn test_max_operations_progress() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
*engine *engine
.run("for x in 0..500 {}") .run("for x in 0..500 {}")
.expect_err("should error"), .unwrap_err(),
EvalAltResult::ErrorTerminated(x, ..) if x.as_int()? == 42 EvalAltResult::ErrorTerminated(x, ..) if x.as_int()? == 42
)); ));

View File

@ -26,17 +26,17 @@ fn test_ops_other_number_types() -> Result<(), Box<EvalAltResult>> {
scope.push("x", 42_u16); scope.push("x", 42_u16);
assert!(matches!( assert!(matches!(
*engine.eval_with_scope::<bool>(&mut scope, "x == 42").expect_err("should error"), *engine.eval_with_scope::<bool>(&mut scope, "x == 42").unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("== (u16,") EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("== (u16,")
)); ));
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
assert!(matches!( assert!(matches!(
*engine.eval_with_scope::<bool>(&mut scope, "x == 42.0").expect_err("should error"), *engine.eval_with_scope::<bool>(&mut scope, "x == 42.0").unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("== (u16,") EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("== (u16,")
)); ));
assert!( assert!(
matches!(*engine.eval_with_scope::<bool>(&mut scope, r#"x == "hello""#).expect_err("should error"), matches!(*engine.eval_with_scope::<bool>(&mut scope, r#"x == "hello""#).unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("== (u16,") EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("== (u16,")
) )
); );

View File

@ -182,9 +182,44 @@ fn test_optimizer_reoptimize() -> Result<(), Box<EvalAltResult>> {
let scope: Scope = ast.iter_literal_variables(true, false).collect(); let scope: Scope = ast.iter_literal_variables(true, false).collect();
let ast = engine.optimize_ast(&scope, ast, OptimizationLevel::Simple); let ast = engine.optimize_ast(&scope, ast, OptimizationLevel::Simple);
println!("{ast:#?}");
assert_eq!(engine.eval_ast::<INT>(&ast)?, 84); assert_eq!(engine.eval_ast::<INT>(&ast)?, 84);
Ok(()) Ok(())
} }
#[test]
fn test_optimizer_full() -> Result<(), Box<EvalAltResult>> {
#[derive(Debug, Clone)]
struct TestStruct(INT);
const SCRIPT: &str = "
const FOO = ts(40) + ts(2);
value(FOO)
";
let mut engine = Engine::new();
let mut scope = Scope::new();
engine.set_optimization_level(OptimizationLevel::Full);
engine
.register_type_with_name::<TestStruct>("TestStruct")
.register_fn("ts", |n: INT| TestStruct(n))
.register_fn("value", |ts: &mut TestStruct| ts.0)
.register_fn("+", |ts1: &mut TestStruct, ts2: TestStruct| {
TestStruct(ts1.0 + ts2.0)
});
let ast = engine.compile(SCRIPT)?;
#[cfg(feature = "internals")]
assert_eq!(ast.statements().len(), 2);
assert_eq!(engine.eval_ast_with_scope::<INT>(&mut scope, &ast)?, 42);
assert_eq!(scope.len(), 1);
assert_eq!(scope.get_value::<TestStruct>("FOO").unwrap().0, 42);
Ok(())
}

View File

@ -118,7 +118,7 @@ fn test_plugins_package() -> Result<(), Box<EvalAltResult>> {
engine.run("const A = [1, 2, 3]; A.no_effect = 42;")?; engine.run("const A = [1, 2, 3]; A.no_effect = 42;")?;
assert!( assert!(
matches!(*engine.run("const A = [1, 2, 3]; A.test(42);").expect_err("should error"), matches!(*engine.run("const A = [1, 2, 3]; A.test(42);").unwrap_err(),
EvalAltResult::ErrorNonPureMethodCallOnConstant(x, ..) if x == "test") EvalAltResult::ErrorNonPureMethodCallOnConstant(x, ..) if x == "test")
) )
} }

File diff suppressed because one or more lines are too long

View File

@ -116,7 +116,7 @@ fn test_string_mut() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>(r#"foo("hello")"#)?, 5); assert_eq!(engine.eval::<INT>(r#"foo("hello")"#)?, 5);
assert_eq!(engine.eval::<INT>(r#"bar("hello")"#)?, 5); assert_eq!(engine.eval::<INT>(r#"bar("hello")"#)?, 5);
assert!( assert!(
matches!(*engine.eval::<INT>(r#"baz("hello")"#).expect_err("should error"), matches!(*engine.eval::<INT>(r#"baz("hello")"#).unwrap_err(),
EvalAltResult::ErrorFunctionNotFound(f, ..) if f == "baz (&str | ImmutableString | String)" EvalAltResult::ErrorFunctionNotFound(f, ..) if f == "baz (&str | ImmutableString | String)"
) )
); );

View File

@ -110,7 +110,7 @@ fn test_switch_errors() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
engine engine
.compile("switch x { _ => 123, 1 => 42 }") .compile("switch x { _ => 123, 1 => 42 }")
.expect_err("should error") .unwrap_err()
.err_type(), .err_type(),
ParseErrorType::WrongSwitchDefaultCase ParseErrorType::WrongSwitchDefaultCase
)); ));
@ -174,7 +174,7 @@ fn test_switch_condition() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
engine engine
.compile("switch x { 1 => 123, _ if true => 42 }") .compile("switch x { 1 => 123, _ if true => 42 }")
.expect_err("should error") .unwrap_err()
.err_type(), .err_type(),
ParseErrorType::WrongSwitchCaseCondition ParseErrorType::WrongSwitchCaseCondition
)); ));
@ -269,14 +269,14 @@ fn test_switch_ranges() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
engine.compile( engine.compile(
"switch x { 10..20 => (), 20..=42 => 'a', 25..45 => 'z', 42 => 'x', 30..100 => true }" "switch x { 10..20 => (), 20..=42 => 'a', 25..45 => 'z', 42 => 'x', 30..100 => true }"
).expect_err("should error").err_type(), ).unwrap_err().err_type(),
ParseErrorType::WrongSwitchIntegerCase ParseErrorType::WrongSwitchIntegerCase
)); ));
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
assert!(matches!( assert!(matches!(
engine.compile( engine.compile(
"switch x { 10..20 => (), 20..=42 => 'a', 25..45 => 'z', 42.0 => 'x', 30..100 => true }" "switch x { 10..20 => (), 20..=42 => 'a', 25..45 => 'z', 42.0 => 'x', 30..100 => true }"
).expect_err("should error").err_type(), ).unwrap_err().err_type(),
ParseErrorType::WrongSwitchIntegerCase ParseErrorType::WrongSwitchIntegerCase
)); ));
assert_eq!( assert_eq!(

View File

@ -9,7 +9,7 @@ fn test_tokens_disabled() {
assert!(matches!( assert!(matches!(
engine engine
.compile("let x = if true { 42 } else { 0 };") .compile("let x = if true { 42 } else { 0 };")
.expect_err("should error") .unwrap_err()
.err_type(), .err_type(),
ParseErrorType::Reserved(err) if err == "if" ParseErrorType::Reserved(err) if err == "if"
)); ));
@ -19,13 +19,13 @@ fn test_tokens_disabled() {
assert_eq!( assert_eq!(
*engine *engine
.compile("let x = 40 + 2; x += 1;") .compile("let x = 40 + 2; x += 1;")
.expect_err("should error") .unwrap_err()
.err_type(), .err_type(),
ParseErrorType::UnknownOperator("+=".to_string()) ParseErrorType::UnknownOperator("+=".to_string())
); );
assert!(matches!( assert!(matches!(
engine.compile("let x = += 0;").expect_err("should error").err_type(), engine.compile("let x = += 0;").unwrap_err().err_type(),
ParseErrorType::Reserved(err) if err == "+=" ParseErrorType::Reserved(err) if err == "+="
)); ));
} }

View File

@ -17,6 +17,6 @@ fn test_unit_eq() -> Result<(), Box<EvalAltResult>> {
#[test] #[test]
fn test_unit_with_spaces() -> Result<(), Box<EvalAltResult>> { fn test_unit_with_spaces() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();
let _ = engine.run("let x = ( ); x").expect_err("should error"); let _ = engine.run("let x = ( ); x").unwrap_err();
Ok(()) Ok(())
} }

View File

@ -209,7 +209,7 @@ fn test_var_resolver() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "chameleon")?, 1); assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "chameleon")?, 1);
assert!( assert!(
matches!(*engine.eval_with_scope::<INT>(&mut scope, "DO_NOT_USE").expect_err("should error"), matches!(*engine.eval_with_scope::<INT>(&mut scope, "DO_NOT_USE").unwrap_err(),
EvalAltResult::ErrorVariableNotFound(n, ..) if n == "DO_NOT_USE") EvalAltResult::ErrorVariableNotFound(n, ..) if n == "DO_NOT_USE")
); );
@ -235,7 +235,7 @@ fn test_var_def_filter() -> Result<(), Box<EvalAltResult>> {
); );
assert!(matches!( assert!(matches!(
engine.compile("let x = 42;").expect_err("should error").err_type(), engine.compile("let x = 42;").unwrap_err().err_type(),
ParseErrorType::ForbiddenVariable(s) if s == "x" ParseErrorType::ForbiddenVariable(s) if s == "x"
)); ));
assert!(matches!( assert!(matches!(

View File

@ -1,7 +1,9 @@
Build Tools Build Tools
=========== ===========
| File | Description | This directory contains input files for various build tools required for building Rhai.
| -------------- | ------------------------------------------- |
| `keywords.txt` | Input file for GNU gperf for the tokenizer. | | File | Build tool | Description |
| `reserved.txt` | Input file for GNU gperf for the tokenizer. | | -------------- | :---------: | ------------------------------------------------------------------ |
| `keywords.txt` | GNU `gperf` | Input file for the tokenizer &ndash; keywords recognition. |
| `reserved.txt` | GNU `gperf` | Input file for the tokenizer &ndash; reserved symbols recognition. |