commit
160f72b4cd
@ -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).
|
||||
* 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
|
||||
------------
|
||||
@ -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.
|
||||
* `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
|
||||
==============
|
||||
|
@ -11,21 +11,23 @@ Root Sources
|
||||
| `tokenizer.rs` | Script tokenizer/lexer |
|
||||
| `parser.rs` | Script parser |
|
||||
| `optimizer.rs` | Script optimizer |
|
||||
| `defer.rs` | Utilities for deferred clean-up of resources |
|
||||
| `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-directory | Description |
|
||||
| ------------- | ----------------------------------------------------- |
|
||||
| `types` | Common data types (e.g. `Dynamic`, errors) |
|
||||
| `api` | Public API for the scripting engine |
|
||||
| `ast` | AST definition |
|
||||
| `module` | Support for modules |
|
||||
| `packages` | Pre-defined packages |
|
||||
| `func` | Support for function calls |
|
||||
| `eval` | Evaluation engine |
|
||||
| `serde` | Support for [`serde`](https://crates.io/crates/serde) |
|
||||
| `bin` | Pre-built CLI binaries (e.g. `rhai-run`, `rhai-repl`) |
|
||||
| Sub-directory | Description |
|
||||
| ------------- | ------------------------------------------------------------------ |
|
||||
| `config` | Configuration |
|
||||
| `types` | Common data types (e.g. `Dynamic`, errors) |
|
||||
| `api` | Public API for the scripting engine |
|
||||
| `ast` | AST definition |
|
||||
| `module` | Support for modules |
|
||||
| `packages` | Pre-defined packages |
|
||||
| `func` | Registering and calling functions (native Rust and script-defined) |
|
||||
| `eval` | AST evaluation |
|
||||
| `serde` | Support for [`serde`](https://crates.io/crates/serde) and metadata |
|
||||
| `bin` | Pre-built CLI binaries |
|
||||
|
@ -259,15 +259,9 @@ impl Engine {
|
||||
// Keyword/symbol not in first position
|
||||
_ if !segments.is_empty() && token.is_some() => {
|
||||
// Make it a custom keyword/symbol if it is disabled or reserved
|
||||
if (self
|
||||
.disabled_symbols
|
||||
.as_ref()
|
||||
.map_or(false, |m| m.contains(s))
|
||||
if (self.is_symbol_disabled(s)
|
||||
|| token.as_ref().map_or(false, Token::is_reserved))
|
||||
&& !self
|
||||
.custom_keywords
|
||||
.as_ref()
|
||||
.map_or(false, |m| m.contains_key(s))
|
||||
&& !self.is_custom_keyword(s)
|
||||
{
|
||||
self.custom_keywords
|
||||
.get_or_insert_with(Default::default)
|
||||
@ -279,10 +273,7 @@ impl Engine {
|
||||
// Standard keyword in first position but not disabled
|
||||
_ if segments.is_empty()
|
||||
&& token.as_ref().map_or(false, Token::is_standard_keyword)
|
||||
&& !self
|
||||
.disabled_symbols
|
||||
.as_ref()
|
||||
.map_or(false, |m| m.contains(s)) =>
|
||||
&& !self.is_symbol_disabled(s) =>
|
||||
{
|
||||
return Err(LexError::ImproperSymbol(
|
||||
s.to_string(),
|
||||
@ -299,15 +290,9 @@ impl Engine {
|
||||
&& (is_valid_identifier(s) || is_reserved_keyword_or_symbol(s).0) =>
|
||||
{
|
||||
// Make it a custom keyword/symbol if it is disabled or reserved
|
||||
if self
|
||||
.disabled_symbols
|
||||
.as_ref()
|
||||
.map_or(false, |m| m.contains(s))
|
||||
if self.is_symbol_disabled(s)
|
||||
|| (token.as_ref().map_or(false, Token::is_reserved)
|
||||
&& !self
|
||||
.custom_keywords
|
||||
.as_ref()
|
||||
.map_or(false, |m| m.contains_key(s)))
|
||||
&& !self.is_custom_keyword(s))
|
||||
{
|
||||
self.custom_keywords
|
||||
.get_or_insert_with(Default::default)
|
||||
|
@ -457,12 +457,7 @@ impl Module {
|
||||
!f.metadata.name.contains('$') && !is_valid_function_name(&f.metadata.name);
|
||||
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
let operator = operator
|
||||
|| def
|
||||
.engine
|
||||
.custom_keywords
|
||||
.as_ref()
|
||||
.map_or(false, |m| m.contains_key(f.metadata.name.as_str()));
|
||||
let operator = operator || def.engine.is_custom_keyword(f.metadata.name.as_str());
|
||||
|
||||
f.write_definition(writer, def, operator)?;
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ impl Engine {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
#[inline]
|
||||
pub fn disable_symbol(&mut self, symbol: impl Into<Identifier>) -> &mut Self {
|
||||
self.disabled_symbols
|
||||
.get_or_insert_with(Default::default)
|
||||
@ -117,6 +117,26 @@ impl Engine {
|
||||
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.
|
||||
///
|
||||
/// Not available under `no_custom_syntax`.
|
||||
@ -168,32 +188,21 @@ impl Engine {
|
||||
Some(Token::Custom(..)) => (),
|
||||
// Active standard keywords cannot be made custom
|
||||
// Disabled keywords are OK
|
||||
Some(token) if token.is_standard_keyword() => {
|
||||
if !self
|
||||
.disabled_symbols
|
||||
.as_ref()
|
||||
.map_or(false, |m| m.contains(token.literal_syntax()))
|
||||
{
|
||||
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"));
|
||||
}
|
||||
Some(token)
|
||||
if token.is_standard_keyword()
|
||||
&& !self.is_symbol_disabled(token.literal_syntax()) =>
|
||||
{
|
||||
return Err(format!("'{keyword}' is a reserved keyword"))
|
||||
}
|
||||
// Active standard symbols cannot be made custom
|
||||
Some(token)
|
||||
if !self
|
||||
.disabled_symbols
|
||||
.as_ref()
|
||||
.map_or(false, |m| m.contains(token.literal_syntax())) =>
|
||||
if token.is_standard_symbol()
|
||||
&& !self.is_symbol_disabled(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"))
|
||||
}
|
||||
// Disabled symbols are OK
|
||||
@ -207,6 +216,16 @@ impl Engine {
|
||||
|
||||
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.
|
||||
#[inline(always)]
|
||||
|
@ -90,15 +90,15 @@ impl Engine {
|
||||
let param_type_names: Option<&[&str]> = None;
|
||||
|
||||
let fn_name = name.as_ref();
|
||||
let no_const = false;
|
||||
let is_pure = true;
|
||||
|
||||
#[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"))]
|
||||
let no_const =
|
||||
no_const || (F::num_params() == 2 && fn_name.starts_with(crate::engine::FN_SET));
|
||||
let is_pure =
|
||||
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(
|
||||
name,
|
||||
|
@ -721,7 +721,7 @@ pub enum Stmt {
|
||||
/// 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.
|
||||
#[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 {
|
||||
@ -816,7 +816,7 @@ impl Stmt {
|
||||
Self::Export(.., pos) => *pos,
|
||||
|
||||
#[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.
|
||||
@ -848,7 +848,7 @@ impl Stmt {
|
||||
Self::Export(.., pos) => *pos = new_pos,
|
||||
|
||||
#[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
|
||||
|
@ -927,19 +927,19 @@ impl Engine {
|
||||
// Share statement
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Stmt::Share(x) => {
|
||||
for (name, index, pos) in &**x {
|
||||
if let Some(index) = index
|
||||
for (var, index) in &**x {
|
||||
let index = index
|
||||
.map(|n| scope.len() - n.get())
|
||||
.or_else(|| scope.search(name))
|
||||
{
|
||||
let val = scope.get_mut_by_index(index);
|
||||
.or_else(|| scope.search(&var.name))
|
||||
.ok_or_else(|| {
|
||||
Box::new(ERR::ErrorVariableNotFound(var.name.to_string(), var.pos))
|
||||
})?;
|
||||
|
||||
if !val.is_shared() {
|
||||
// Replace the variable with a shared value.
|
||||
*val = std::mem::take(val).into_shared();
|
||||
}
|
||||
} else {
|
||||
return Err(ERR::ErrorVariableNotFound(name.to_string(), *pos).into());
|
||||
let val = scope.get_mut_by_index(index);
|
||||
|
||||
if !val.is_shared() {
|
||||
// Replace the variable with a shared value.
|
||||
*val = std::mem::take(val).into_shared();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -276,6 +276,7 @@ impl Engine {
|
||||
func: CallableFunction::Method {
|
||||
func: Shared::new(f),
|
||||
has_context,
|
||||
is_pure: false,
|
||||
},
|
||||
source: None,
|
||||
})
|
||||
@ -285,6 +286,7 @@ impl Engine {
|
||||
func: CallableFunction::Method {
|
||||
func: Shared::new(f),
|
||||
has_context,
|
||||
is_pure: true,
|
||||
},
|
||||
source: None,
|
||||
}),
|
||||
@ -372,8 +374,8 @@ impl Engine {
|
||||
|
||||
let backup = &mut ArgBackup::new();
|
||||
|
||||
// Calling pure function but the first argument is a reference?
|
||||
let swap = is_ref_mut && func.is_pure() && !args.is_empty();
|
||||
// Calling non-method function but the first argument is a reference?
|
||||
let swap = is_ref_mut && !func.is_method() && !args.is_empty();
|
||||
|
||||
if swap {
|
||||
// Clone the first argument
|
||||
@ -400,12 +402,11 @@ impl Engine {
|
||||
.has_context()
|
||||
.then(|| (self, name, src, &*global, pos).into());
|
||||
|
||||
let mut _result = if let Some(f) = func.get_plugin_fn() {
|
||||
if !f.is_pure() && !args.is_empty() && args[0].is_read_only() {
|
||||
Err(ERR::ErrorNonPureMethodCallOnConstant(name.to_string(), pos).into())
|
||||
} else {
|
||||
f.call(context, args)
|
||||
}
|
||||
let mut _result = if !func.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())
|
||||
} else if let Some(f) = func.get_plugin_fn() {
|
||||
f.call(context, args)
|
||||
} else if let Some(f) = func.get_native_fn() {
|
||||
f(context, args)
|
||||
} else {
|
||||
@ -603,8 +604,11 @@ impl Engine {
|
||||
};
|
||||
|
||||
if error {
|
||||
let sig = self.gen_fn_call_signature(fn_name, args);
|
||||
return Err(ERR::ErrorFunctionNotFound(sig, pos).into());
|
||||
return Err(ERR::ErrorFunctionNotFound(
|
||||
self.gen_fn_call_signature(fn_name, args),
|
||||
pos,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
|
||||
@ -814,12 +818,16 @@ impl Engine {
|
||||
// Handle obj.call(fn_ptr, ...)
|
||||
KEYWORD_FN_PTR_CALL => {
|
||||
if call_args.is_empty() {
|
||||
let typ = self.map_type_name(target.type_name());
|
||||
return Err(self.make_type_mismatch_err::<FnPtr>(typ, fn_call_pos));
|
||||
return Err(self.make_type_mismatch_err::<FnPtr>(
|
||||
self.map_type_name(target.type_name()),
|
||||
fn_call_pos,
|
||||
));
|
||||
}
|
||||
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>(typ, first_arg_pos));
|
||||
return Err(self.make_type_mismatch_err::<FnPtr>(
|
||||
self.map_type_name(call_args[0].type_name()),
|
||||
first_arg_pos,
|
||||
));
|
||||
}
|
||||
|
||||
// FnPtr call on object
|
||||
@ -903,8 +911,10 @@ impl Engine {
|
||||
}
|
||||
KEYWORD_FN_PTR_CURRY => {
|
||||
if !target.is_fnptr() {
|
||||
let typ = self.map_type_name(target.type_name());
|
||||
return Err(self.make_type_mismatch_err::<FnPtr>(typ, fn_call_pos));
|
||||
return Err(self.make_type_mismatch_err::<FnPtr>(
|
||||
self.map_type_name(target.type_name()),
|
||||
fn_call_pos,
|
||||
));
|
||||
}
|
||||
|
||||
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)?;
|
||||
|
||||
if !arg_value.is_fnptr() {
|
||||
let typ = self.map_type_name(arg_value.type_name());
|
||||
return Err(self.make_type_mismatch_err::<FnPtr>(typ, arg_pos));
|
||||
return Err(self.make_type_mismatch_err::<FnPtr>(
|
||||
self.map_type_name(arg_value.type_name()),
|
||||
arg_pos,
|
||||
));
|
||||
}
|
||||
|
||||
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)?;
|
||||
|
||||
if !arg_value.is_fnptr() {
|
||||
let typ = self.map_type_name(arg_value.type_name());
|
||||
return Err(self.make_type_mismatch_err::<FnPtr>(typ, arg_pos));
|
||||
return Err(self.make_type_mismatch_err::<FnPtr>(
|
||||
self.map_type_name(arg_value.type_name()),
|
||||
arg_pos,
|
||||
));
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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() => {
|
||||
let f = f.get_plugin_fn().expect("plugin function");
|
||||
let context = f
|
||||
.has_context()
|
||||
.then(|| (self, fn_name, module.id(), &*global, pos).into());
|
||||
if !f.is_pure() && !args.is_empty() && args[0].is_read_only() {
|
||||
Err(ERR::ErrorNonPureMethodCallOnConstant(fn_name.to_string(), pos).into())
|
||||
} else {
|
||||
f.call(context, args)
|
||||
.and_then(|r| self.check_data_size(r, pos))
|
||||
}
|
||||
f.call(context, args)
|
||||
.and_then(|r| self.check_data_size(r, pos))
|
||||
}
|
||||
|
||||
Some(f) if f.is_native() => {
|
||||
|
@ -39,6 +39,8 @@ pub enum CallableFunction {
|
||||
func: Shared<FnAny>,
|
||||
/// Does the function take a [`NativeCallContext`][crate::NativeCallContext] parameter?
|
||||
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,
|
||||
/// and the rest passed by value.
|
||||
@ -47,6 +49,8 @@ pub enum CallableFunction {
|
||||
func: Shared<FnAny>,
|
||||
/// Does the function take a [`NativeCallContext`][crate::NativeCallContext] parameter?
|
||||
has_context: bool,
|
||||
/// Allow operating on constants?
|
||||
is_pure: bool,
|
||||
},
|
||||
/// An iterator function.
|
||||
Iterator {
|
||||
@ -105,9 +109,10 @@ impl CallableFunction {
|
||||
pub fn is_pure(&self) -> bool {
|
||||
match self {
|
||||
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"))]
|
||||
Self::Script { .. } => false,
|
||||
|
@ -84,7 +84,7 @@ pub trait RegisterNativeFunction<
|
||||
{
|
||||
/// Convert this function into a [`CallableFunction`].
|
||||
#[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.
|
||||
#[must_use]
|
||||
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 {
|
||||
() => {
|
||||
def_register!(imp Pure : 0;);
|
||||
@ -160,11 +147,9 @@ macro_rules! def_register {
|
||||
> RegisterNativeFunction<($($mark,)*), $n, false, RET, false> for FN {
|
||||
#[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] }
|
||||
#[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| {
|
||||
// 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 $par = $clone(drain.next().unwrap()); )*
|
||||
|
||||
@ -173,7 +158,7 @@ macro_rules! def_register {
|
||||
|
||||
// Map the result
|
||||
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 {
|
||||
#[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] }
|
||||
#[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| {
|
||||
let ctx = ctx.unwrap();
|
||||
|
||||
// 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 $par = $clone(drain.next().unwrap()); )*
|
||||
|
||||
@ -199,7 +182,7 @@ macro_rules! def_register {
|
||||
|
||||
// Map the result
|
||||
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>()),*] }
|
||||
#[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>>() }
|
||||
#[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| {
|
||||
// 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 $par = $clone(drain.next().unwrap()); )*
|
||||
|
||||
// Call the function with each argument value
|
||||
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>()),*] }
|
||||
#[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>>() }
|
||||
#[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| {
|
||||
let ctx = ctx.unwrap();
|
||||
|
||||
// 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 $par = $clone(drain.next().unwrap()); )*
|
||||
|
||||
// Call the function with each argument value
|
||||
self(ctx, $($arg),*).map(Dynamic::from)
|
||||
}), has_context: true }
|
||||
}), has_context: true, is_pure }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1266,6 +1266,7 @@ impl Module {
|
||||
CallableFunction::Method {
|
||||
func: Shared::new(f),
|
||||
has_context: true,
|
||||
is_pure: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -1303,15 +1304,15 @@ impl Module {
|
||||
F: RegisterNativeFunction<A, N, C, T, true>,
|
||||
{
|
||||
let fn_name = name.into();
|
||||
let no_const = false;
|
||||
let is_pure = true;
|
||||
|
||||
#[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"))]
|
||||
let no_const =
|
||||
no_const || (F::num_params() == 2 && fn_name.starts_with(crate::engine::FN_SET));
|
||||
let is_pure =
|
||||
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(
|
||||
fn_name,
|
||||
@ -1350,7 +1351,7 @@ impl Module {
|
||||
F: RegisterNativeFunction<(Mut<A>,), 1, C, T, true> + SendSync + 'static,
|
||||
{
|
||||
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(
|
||||
fn_name,
|
||||
@ -1394,7 +1395,7 @@ impl Module {
|
||||
F: RegisterNativeFunction<(Mut<A>, T), 2, C, (), true> + SendSync + 'static,
|
||||
{
|
||||
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(
|
||||
fn_name,
|
||||
@ -1510,7 +1511,7 @@ impl Module {
|
||||
FnAccess::Public,
|
||||
None,
|
||||
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,
|
||||
None,
|
||||
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`].
|
||||
#[inline]
|
||||
#[inline(always)]
|
||||
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
|
||||
.as_ref()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|(k, m)| (k.as_str(), m))
|
||||
.map(|(k, m)| (k, m))
|
||||
}
|
||||
|
||||
/// Get an iterator to the variables in the [`Module`].
|
||||
#[inline]
|
||||
#[inline(always)]
|
||||
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
|
||||
.as_ref()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|(k, v)| (k.as_str(), v))
|
||||
.map(|(k, v)| (k, v))
|
||||
}
|
||||
|
||||
/// Get an iterator to the functions in the [`Module`].
|
||||
|
@ -14,10 +14,9 @@ use crate::func::builtin::get_builtin_binary_op_fn;
|
||||
use crate::func::hashing::get_hasher;
|
||||
use crate::module::ModuleFlags;
|
||||
use crate::tokenizer::Token;
|
||||
use crate::types::dynamic::AccessMode;
|
||||
use crate::{
|
||||
calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnPtr, Identifier, ImmutableString, Position,
|
||||
Scope, StaticVec, AST,
|
||||
calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnPtr, ImmutableString, Position, Scope,
|
||||
StaticVec, AST,
|
||||
};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -54,8 +53,8 @@ impl Default for OptimizationLevel {
|
||||
struct OptimizerState<'a> {
|
||||
/// Has the [`AST`] been changed during this pass?
|
||||
changed: bool,
|
||||
/// Collection of constants to use for eager function evaluations.
|
||||
variables: StaticVec<(Identifier, AccessMode, Option<Dynamic>)>,
|
||||
/// Collection of variables/constants to use for eager function evaluations.
|
||||
variables: StaticVec<(ImmutableString, Option<Dynamic>)>,
|
||||
/// Activate constants propagation?
|
||||
propagate_constants: bool,
|
||||
/// An [`Engine`] instance for eager function evaluation.
|
||||
@ -115,14 +114,11 @@ impl<'a> OptimizerState<'a> {
|
||||
self.variables.truncate(len);
|
||||
}
|
||||
/// Add a new variable to the list.
|
||||
///
|
||||
/// `Some(value)` if constant, `None` or otherwise.
|
||||
#[inline(always)]
|
||||
pub fn push_var(
|
||||
&mut self,
|
||||
name: impl Into<Identifier>,
|
||||
access: AccessMode,
|
||||
value: Option<Dynamic>,
|
||||
) {
|
||||
self.variables.push((name.into(), access, value));
|
||||
pub fn push_var(&mut self, name: ImmutableString, value: Option<Dynamic>) {
|
||||
self.variables.push((name, value));
|
||||
}
|
||||
/// Look up a constant from the list.
|
||||
#[inline]
|
||||
@ -131,12 +127,9 @@ impl<'a> OptimizerState<'a> {
|
||||
return None;
|
||||
}
|
||||
|
||||
for (n, access, value) in self.variables.iter().rev() {
|
||||
if n == name {
|
||||
return match access {
|
||||
AccessMode::ReadWrite => None,
|
||||
AccessMode::ReadOnly => value.as_ref(),
|
||||
};
|
||||
for (n, value) in self.variables.iter().rev() {
|
||||
if n.as_str() == name {
|
||||
return value.as_ref();
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,16 +230,13 @@ fn optimize_stmt_block(
|
||||
optimize_expr(&mut x.1, state, false);
|
||||
|
||||
if x.1.is_constant() {
|
||||
state.push_var(
|
||||
x.0.as_str(),
|
||||
AccessMode::ReadOnly,
|
||||
x.1.get_literal_value(),
|
||||
);
|
||||
state
|
||||
.push_var(x.0.name.clone(), Some(x.1.get_literal_value().unwrap()));
|
||||
}
|
||||
} else {
|
||||
// Add variables into the state
|
||||
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
|
||||
@ -858,7 +848,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Stmt::Share(x) => {
|
||||
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 {
|
||||
state.set_dirty();
|
||||
}
|
||||
@ -1335,20 +1325,14 @@ impl Engine {
|
||||
.iter()
|
||||
.rev()
|
||||
.flat_map(|m| m.iter_var())
|
||||
.for_each(|(name, value)| {
|
||||
state.push_var(name, AccessMode::ReadOnly, Some(value.clone()))
|
||||
});
|
||||
.for_each(|(name, value)| state.push_var(name.into(), Some(value.clone())));
|
||||
|
||||
// Add constants and variables from the scope
|
||||
scope
|
||||
.into_iter()
|
||||
.flat_map(Scope::iter)
|
||||
.for_each(|(name, constant, value)| {
|
||||
if constant {
|
||||
state.push_var(name, AccessMode::ReadOnly, Some(value));
|
||||
} else {
|
||||
state.push_var(name, AccessMode::ReadWrite, None);
|
||||
}
|
||||
state.push_var(name.into(), if constant { Some(value) } else { None });
|
||||
});
|
||||
|
||||
optimize_stmt_block(statements, &mut state, true, false, true)
|
||||
|
@ -1728,6 +1728,9 @@ impl Engine {
|
||||
) -> ParseResult<Expr> {
|
||||
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
|
||||
loop {
|
||||
let (tail_token, ..) = input.peek().expect(NEVER_ENDS);
|
||||
@ -1842,13 +1845,16 @@ impl Engine {
|
||||
let rhs =
|
||||
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
|
||||
(expr, token) => {
|
||||
unreachable!("unknown postfix operator '{}' for {:?}", token, expr)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// The chain is now extended
|
||||
parent_options = ASTFlags::empty();
|
||||
}
|
||||
|
||||
// Cache the hash key for namespace-qualified variables
|
||||
@ -2431,13 +2437,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_custom_syntax"))]
|
||||
Token::Custom(s)
|
||||
if self
|
||||
.custom_keywords
|
||||
.as_ref()
|
||||
.and_then(|m| m.get(s.as_str()))
|
||||
.map_or(false, Option::is_some) =>
|
||||
{
|
||||
Token::Custom(s) if self.is_custom_keyword(s.as_str()) => {
|
||||
op_base.hashes = if native_only {
|
||||
FnCallHashes::from_native_only(calc_fn_hash(None, &s, 2))
|
||||
} else {
|
||||
@ -3781,9 +3781,9 @@ impl Engine {
|
||||
statements.push(Stmt::Share(
|
||||
externals
|
||||
.into_iter()
|
||||
.map(|Ident { name, pos }| {
|
||||
let (index, _) = parent.access_var(&name, lib, pos);
|
||||
(name, index, pos)
|
||||
.map(|var| {
|
||||
let (index, _) = parent.access_var(&var.name, lib, var.pos);
|
||||
(var, index)
|
||||
})
|
||||
.collect::<crate::FnArgsVec<_>>()
|
||||
.into(),
|
||||
|
@ -2492,7 +2492,7 @@ impl<'a> Iterator for TokenIterator<'a> {
|
||||
Some((Token::Reserved(s), pos)) => (match
|
||||
(s.as_str(),
|
||||
#[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")]
|
||||
false
|
||||
)
|
||||
@ -2529,7 +2529,7 @@ impl<'a> Iterator for TokenIterator<'a> {
|
||||
#[cfg(feature = "no_custom_syntax")]
|
||||
(.., true) => unreachable!("no custom operators"),
|
||||
// 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"});
|
||||
Token::LexError(LERR::ImproperSymbol(s.to_string(), msg).into())
|
||||
},
|
||||
@ -2538,13 +2538,13 @@ impl<'a> Iterator for TokenIterator<'a> {
|
||||
}, pos),
|
||||
// Custom keyword
|
||||
#[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)
|
||||
}
|
||||
// Custom keyword/symbol - must be disabled
|
||||
#[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())) => {
|
||||
if 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_custom_keyword(token.literal_syntax()) => {
|
||||
if self.engine.is_symbol_disabled(token.literal_syntax()) {
|
||||
// Disabled standard keyword/symbol
|
||||
(Token::Custom(Box::new(token.literal_syntax().into())), pos)
|
||||
} else {
|
||||
@ -2553,7 +2553,7 @@ impl<'a> Iterator for TokenIterator<'a> {
|
||||
}
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
// Normal symbol
|
||||
|
@ -1017,18 +1017,23 @@ impl Dynamic {
|
||||
}
|
||||
/// 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
|
||||
/// 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
|
||||
/// [`Dynamic::from_array`] to convert a [`Vec<T>`][Vec] into a [`Dynamic`] as an
|
||||
/// [`Array`][crate::Array] value.
|
||||
/// [`Array`][crate::Array], but will be a custom type instead (stored as a trait object).
|
||||
///
|
||||
/// 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
|
||||
/// [`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]
|
||||
/// value.
|
||||
/// custom type.
|
||||
///
|
||||
/// Again, use `map.into()` to get a [`Dynamic`] with a [`Map`][crate::Map] value.
|
||||
/// See the examples for details.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@ -1046,6 +1051,33 @@ impl Dynamic {
|
||||
/// let new_result = Dynamic::from(result);
|
||||
/// assert_eq!(new_result.type_name(), "string");
|
||||
/// 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]
|
||||
pub fn from<T: Variant + Clone>(value: T) -> Self {
|
||||
|
@ -203,6 +203,22 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
|
||||
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]
|
||||
fn test_array_index_types() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
@ -210,34 +226,25 @@ fn test_array_index_types() -> Result<(), Box<EvalAltResult>> {
|
||||
engine.compile("[1, 2, 3][0]['x']")?;
|
||||
|
||||
assert!(matches!(
|
||||
engine
|
||||
.compile("[1, 2, 3]['x']")
|
||||
.expect_err("should error")
|
||||
.err_type(),
|
||||
engine.compile("[1, 2, 3]['x']").unwrap_err().err_type(),
|
||||
ParseErrorType::MalformedIndexExpr(..)
|
||||
));
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
assert!(matches!(
|
||||
engine
|
||||
.compile("[1, 2, 3][123.456]")
|
||||
.expect_err("should error")
|
||||
.err_type(),
|
||||
engine.compile("[1, 2, 3][123.456]").unwrap_err().err_type(),
|
||||
ParseErrorType::MalformedIndexExpr(..)
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
engine
|
||||
.compile("[1, 2, 3][()]")
|
||||
.expect_err("should error")
|
||||
.err_type(),
|
||||
engine.compile("[1, 2, 3][()]").unwrap_err().err_type(),
|
||||
ParseErrorType::MalformedIndexExpr(..)
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
engine
|
||||
.compile(r#"[1, 2, 3]["hello"]"#)
|
||||
.expect_err("should error")
|
||||
.unwrap_err()
|
||||
.err_type(),
|
||||
ParseErrorType::MalformedIndexExpr(..)
|
||||
));
|
||||
@ -245,7 +252,7 @@ fn test_array_index_types() -> Result<(), Box<EvalAltResult>> {
|
||||
assert!(matches!(
|
||||
engine
|
||||
.compile("[1, 2, 3][true && false]")
|
||||
.expect_err("should error")
|
||||
.unwrap_err()
|
||||
.err_type(),
|
||||
ParseErrorType::MalformedIndexExpr(..)
|
||||
));
|
||||
@ -517,9 +524,9 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
engine.eval::<()>(
|
||||
"
|
||||
let x = [1, 2, 3, 2, 1];
|
||||
x.find(|v| v > 4)
|
||||
",
|
||||
let x = [1, 2, 3, 2, 1];
|
||||
x.find(|v| v > 4)
|
||||
",
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
@ -534,9 +541,9 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
engine.eval::<()>(
|
||||
"
|
||||
let x = [#{alice: 1}, #{bob: 2}, #{clara: 3}];
|
||||
x.find_map(|v| v.dave)
|
||||
",
|
||||
let x = [#{alice: 1}, #{bob: 2}, #{clara: 3}];
|
||||
x.find_map(|v| v.dave)
|
||||
",
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
|
@ -21,71 +21,44 @@ fn test_assignments_bad_lhs() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(
|
||||
*engine
|
||||
.compile("(x+y) = 42;")
|
||||
.expect_err("should error")
|
||||
.err_type(),
|
||||
*engine.compile("(x+y) = 42;").unwrap_err().err_type(),
|
||||
ParseErrorType::AssignmentToInvalidLHS(String::new())
|
||||
);
|
||||
assert_eq!(
|
||||
*engine
|
||||
.compile("foo(x) = 42;")
|
||||
.expect_err("should error")
|
||||
.err_type(),
|
||||
*engine.compile("foo(x) = 42;").unwrap_err().err_type(),
|
||||
ParseErrorType::AssignmentToInvalidLHS(String::new())
|
||||
);
|
||||
assert_eq!(
|
||||
*engine
|
||||
.compile("true = 42;")
|
||||
.expect_err("should error")
|
||||
.err_type(),
|
||||
*engine.compile("true = 42;").unwrap_err().err_type(),
|
||||
ParseErrorType::AssignmentToConstant(String::new())
|
||||
);
|
||||
assert_eq!(
|
||||
*engine
|
||||
.compile("123 = 42;")
|
||||
.expect_err("should error")
|
||||
.err_type(),
|
||||
*engine.compile("123 = 42;").unwrap_err().err_type(),
|
||||
ParseErrorType::AssignmentToConstant(String::new())
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
{
|
||||
assert_eq!(
|
||||
*engine
|
||||
.compile("x.foo() = 42;")
|
||||
.expect_err("should error")
|
||||
.err_type(),
|
||||
*engine.compile("x.foo() = 42;").unwrap_err().err_type(),
|
||||
ParseErrorType::AssignmentToInvalidLHS(String::new())
|
||||
);
|
||||
assert_eq!(
|
||||
*engine
|
||||
.compile("x.foo().x.y = 42;")
|
||||
.expect_err("should error")
|
||||
.err_type(),
|
||||
*engine.compile("x.foo().x.y = 42;").unwrap_err().err_type(),
|
||||
ParseErrorType::AssignmentToInvalidLHS(String::new())
|
||||
);
|
||||
assert_eq!(
|
||||
*engine
|
||||
.compile("x.y.z.foo() = 42;")
|
||||
.expect_err("should error")
|
||||
.err_type(),
|
||||
*engine.compile("x.y.z.foo() = 42;").unwrap_err().err_type(),
|
||||
ParseErrorType::AssignmentToInvalidLHS(String::new())
|
||||
);
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
assert_eq!(
|
||||
*engine
|
||||
.compile("x.foo()[0] = 42;")
|
||||
.expect_err("should error")
|
||||
.err_type(),
|
||||
*engine.compile("x.foo()[0] = 42;").unwrap_err().err_type(),
|
||||
ParseErrorType::AssignmentToInvalidLHS(String::new())
|
||||
);
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
assert_eq!(
|
||||
*engine
|
||||
.compile("x[y].z.foo() = 42;")
|
||||
.expect_err("should error")
|
||||
.err_type(),
|
||||
*engine.compile("x[y].z.foo() = 42;").unwrap_err().err_type(),
|
||||
ParseErrorType::AssignmentToInvalidLHS(String::new())
|
||||
);
|
||||
}
|
||||
|
@ -47,10 +47,7 @@ fn test_closures() -> Result<(), Box<EvalAltResult>> {
|
||||
scope.push("x", 42 as INT);
|
||||
|
||||
assert!(matches!(
|
||||
engine
|
||||
.compile_expression("|x| {}")
|
||||
.expect_err("should error")
|
||||
.err_type(),
|
||||
engine.compile_expression("|x| {}").unwrap_err().err_type(),
|
||||
ParseErrorType::BadInput(..)
|
||||
));
|
||||
|
||||
@ -292,7 +289,7 @@ fn test_closures_data_race() -> Result<(), Box<EvalAltResult>> {
|
||||
a
|
||||
"
|
||||
)
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorDataRace(..)
|
||||
));
|
||||
|
||||
|
@ -86,7 +86,7 @@ fn test_constant_mut() -> Result<(), Box<EvalAltResult>> {
|
||||
MY_NUMBER.value = 42;
|
||||
"
|
||||
)
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorNonPureMethodCallOnConstant(..)
|
||||
));
|
||||
|
||||
@ -119,7 +119,7 @@ fn test_constant_mut() -> Result<(), Box<EvalAltResult>> {
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.run_with_scope(&mut scope, "MY_NUMBER.value = 42;")
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorNonPureMethodCallOnConstant(..)
|
||||
));
|
||||
|
||||
|
@ -13,11 +13,11 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
||||
// Disable 'while' and make sure it still works with custom syntax
|
||||
engine.disable_symbol("while");
|
||||
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"
|
||||
));
|
||||
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"
|
||||
));
|
||||
|
||||
@ -127,7 +127,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.run("let foo = (exec [x<<15] -> { x += 2 } while x < 42) * 10;")
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorRuntime(..)
|
||||
));
|
||||
|
||||
@ -195,7 +195,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
||||
assert_eq!(
|
||||
*engine
|
||||
.register_custom_syntax(["!"], false, |_, _| Ok(Dynamic::UNIT))
|
||||
.expect_err("should error")
|
||||
.unwrap_err()
|
||||
.err_type(),
|
||||
ParseErrorType::BadInput(LexError::ImproperSymbol(
|
||||
"!".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
|
||||
.compile("hello hey")
|
||||
.expect_err("should error")
|
||||
.err_type(),
|
||||
*engine.compile("hello hey").unwrap_err().err_type(),
|
||||
ParseErrorType::BadInput(LexError::ImproperSymbol("hey".to_string(), String::new()))
|
||||
);
|
||||
|
||||
|
@ -15,7 +15,7 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
|
||||
assert_eq!(
|
||||
*engine
|
||||
.compile(r#"let x = "hello, world!";"#)
|
||||
.expect_err("should error")
|
||||
.unwrap_err()
|
||||
.err_type(),
|
||||
ParseErrorType::LiteralTooLarge("Length of string".to_string(), 10)
|
||||
);
|
||||
@ -23,7 +23,7 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
|
||||
assert_eq!(
|
||||
*engine
|
||||
.compile(r#"let x = "朝に紅顔、暮に白骨";"#)
|
||||
.expect_err("should error")
|
||||
.unwrap_err()
|
||||
.err_type(),
|
||||
ParseErrorType::LiteralTooLarge("Length of string".to_string(), 10)
|
||||
);
|
||||
@ -37,7 +37,7 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
|
||||
x + y
|
||||
"#
|
||||
)
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorDataTooLarge(..)
|
||||
));
|
||||
|
||||
@ -51,7 +51,7 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
|
||||
x
|
||||
"#
|
||||
)
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorDataTooLarge(..)
|
||||
));
|
||||
|
||||
@ -83,7 +83,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
|
||||
assert_eq!(
|
||||
*engine
|
||||
.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(),
|
||||
ParseErrorType::LiteralTooLarge("Size of array literal".to_string(), 10)
|
||||
);
|
||||
@ -97,7 +97,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
|
||||
x + y
|
||||
"
|
||||
)
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorDataTooLarge(..)
|
||||
));
|
||||
|
||||
@ -130,7 +130,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
|
||||
}
|
||||
"
|
||||
)
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorDataTooLarge(..)
|
||||
));
|
||||
|
||||
@ -142,7 +142,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
|
||||
loop { x[0] = x; }
|
||||
"
|
||||
)
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorDataTooLarge(..)
|
||||
));
|
||||
|
||||
@ -168,7 +168,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
|
||||
x
|
||||
"
|
||||
)
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorDataTooLarge(..)
|
||||
));
|
||||
|
||||
@ -190,7 +190,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
|
||||
[x, x, x, x]
|
||||
"
|
||||
)
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorDataTooLarge(..)
|
||||
));
|
||||
|
||||
@ -203,7 +203,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
|
||||
[x, x, x, x]
|
||||
"
|
||||
)
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorDataTooLarge(..)
|
||||
));
|
||||
|
||||
@ -217,7 +217,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
|
||||
[z, z, z]
|
||||
"
|
||||
)
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorDataTooLarge(..)
|
||||
));
|
||||
|
||||
@ -265,7 +265,7 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
|
||||
.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};"
|
||||
)
|
||||
.expect_err("should error")
|
||||
.unwrap_err()
|
||||
.err_type(),
|
||||
ParseErrorType::LiteralTooLarge(
|
||||
"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; }
|
||||
"
|
||||
)
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorDataTooLarge(..)
|
||||
));
|
||||
|
||||
@ -294,7 +294,7 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
|
||||
x + y
|
||||
"
|
||||
)
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorDataTooLarge(..)
|
||||
));
|
||||
|
||||
@ -306,7 +306,7 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
|
||||
#{u:x, v:x, w:x, z:x}
|
||||
"
|
||||
)
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorDataTooLarge(..)
|
||||
));
|
||||
|
||||
@ -319,7 +319,7 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
|
||||
#{u:x, v:x, w:x, z:x}
|
||||
"
|
||||
)
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorDataTooLarge(..)
|
||||
));
|
||||
|
||||
|
@ -169,7 +169,7 @@ fn test_eval_disabled() -> Result<(), Box<EvalAltResult>> {
|
||||
assert!(matches!(
|
||||
engine
|
||||
.compile(r#"eval("40 + 2")"#)
|
||||
.expect_err("should error")
|
||||
.unwrap_err()
|
||||
.err_type(),
|
||||
ParseErrorType::BadInput(LexError::ImproperSymbol(err, ..)) if err == "eval"
|
||||
));
|
||||
|
@ -72,7 +72,7 @@ fn test_fn_ptr() -> Result<(), Box<EvalAltResult>> {
|
||||
x
|
||||
"#
|
||||
)
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorInFunctionCall(fn_name, _, err, ..)
|
||||
if fn_name == "foo" && matches!(*err, EvalAltResult::ErrorUnboundThis(..))
|
||||
));
|
||||
|
@ -93,7 +93,7 @@ fn test_functions_global_module() -> Result<(), Box<EvalAltResult>> {
|
||||
const ANSWER = 42;
|
||||
foo()
|
||||
}
|
||||
").expect_err("should error"),
|
||||
").unwrap_err(),
|
||||
EvalAltResult::ErrorInFunctionCall(.., err, _)
|
||||
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;
|
||||
global::LOCAL_VALUE
|
||||
});
|
||||
").expect_err("should error"),
|
||||
").unwrap_err(),
|
||||
EvalAltResult::ErrorInFunctionCall(.., err, _)
|
||||
if matches!(&*err, EvalAltResult::ErrorVariableNotFound(v, ..) if v == "global::LOCAL_VALUE")
|
||||
));
|
||||
|
@ -109,7 +109,7 @@ fn test_internal_fn_overloading() -> Result<(), Box<EvalAltResult>> {
|
||||
fn abc(x) { x - 42 }
|
||||
"
|
||||
)
|
||||
.expect_err("should error")
|
||||
.unwrap_err()
|
||||
.err_type(),
|
||||
ParseErrorType::FnDuplicatedDefinition("abc".to_string(), 1)
|
||||
);
|
||||
@ -125,7 +125,7 @@ fn test_internal_fn_params() -> Result<(), Box<EvalAltResult>> {
|
||||
assert_eq!(
|
||||
*engine
|
||||
.compile("fn hello(x, x) { x }")
|
||||
.expect_err("should error")
|
||||
.unwrap_err()
|
||||
.err_type(),
|
||||
ParseErrorType::FnDuplicatedParam("hello".to_string(), "x".to_string())
|
||||
);
|
||||
@ -169,7 +169,7 @@ fn test_function_pointers() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
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 (")
|
||||
));
|
||||
|
||||
@ -247,7 +247,7 @@ fn test_internal_fn_bang() -> Result<(), Box<EvalAltResult>> {
|
||||
y.foo!();
|
||||
"
|
||||
)
|
||||
.expect_err("should error")
|
||||
.unwrap_err()
|
||||
.err_type(),
|
||||
ParseErrorType::MalformedCapture(..)
|
||||
));
|
||||
|
@ -27,10 +27,7 @@ fn test_loop() -> Result<(), Box<EvalAltResult>> {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
*engine
|
||||
.compile("let x = 0; break;")
|
||||
.expect_err("should error")
|
||||
.err_type(),
|
||||
*engine.compile("let x = 0; break;").unwrap_err().err_type(),
|
||||
ParseErrorType::LoopBreak
|
||||
);
|
||||
|
||||
@ -38,7 +35,7 @@ fn test_loop() -> Result<(), Box<EvalAltResult>> {
|
||||
assert_eq!(
|
||||
*engine
|
||||
.compile("loop { let f = || { break; } }")
|
||||
.expect_err("should error")
|
||||
.unwrap_err()
|
||||
.err_type(),
|
||||
ParseErrorType::LoopBreak
|
||||
);
|
||||
@ -46,7 +43,7 @@ fn test_loop() -> Result<(), Box<EvalAltResult>> {
|
||||
assert_eq!(
|
||||
*engine
|
||||
.compile("let x = 0; if x > 0 { continue; }")
|
||||
.expect_err("should error")
|
||||
.unwrap_err()
|
||||
.err_type(),
|
||||
ParseErrorType::LoopBreak
|
||||
);
|
||||
|
@ -52,7 +52,7 @@ b`: 1}; y["a\nb"]
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.eval::<INT>("let y = #{`a${1}`: 1}; y.a1")
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorParsing(ParseErrorType::PropertyExpected, ..)
|
||||
));
|
||||
|
||||
@ -116,7 +116,7 @@ fn test_map_prop() -> Result<(), Box<EvalAltResult>> {
|
||||
engine.set_fail_on_invalid_map_property(true);
|
||||
|
||||
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"
|
||||
)
|
||||
);
|
||||
@ -134,7 +134,7 @@ fn test_map_index_types() -> Result<(), Box<EvalAltResult>> {
|
||||
assert!(matches!(
|
||||
engine
|
||||
.compile("#{a:1, b:2, c:3}['x']")
|
||||
.expect_err("should error")
|
||||
.unwrap_err()
|
||||
.err_type(),
|
||||
ParseErrorType::MalformedIndexExpr(..)
|
||||
));
|
||||
@ -142,7 +142,7 @@ fn test_map_index_types() -> Result<(), Box<EvalAltResult>> {
|
||||
assert!(matches!(
|
||||
engine
|
||||
.compile("#{a:1, b:2, c:3}[1]")
|
||||
.expect_err("should error")
|
||||
.unwrap_err()
|
||||
.err_type(),
|
||||
ParseErrorType::MalformedIndexExpr(..)
|
||||
));
|
||||
@ -151,7 +151,7 @@ fn test_map_index_types() -> Result<(), Box<EvalAltResult>> {
|
||||
assert!(matches!(
|
||||
engine
|
||||
.compile("#{a:1, b:2, c:3}[123.456]")
|
||||
.expect_err("should error")
|
||||
.unwrap_err()
|
||||
.err_type(),
|
||||
ParseErrorType::MalformedIndexExpr(..)
|
||||
));
|
||||
@ -159,7 +159,7 @@ fn test_map_index_types() -> Result<(), Box<EvalAltResult>> {
|
||||
assert!(matches!(
|
||||
engine
|
||||
.compile("#{a:1, b:2, c:3}[()]")
|
||||
.expect_err("should error")
|
||||
.unwrap_err()
|
||||
.err_type(),
|
||||
ParseErrorType::MalformedIndexExpr(..)
|
||||
));
|
||||
@ -167,7 +167,7 @@ fn test_map_index_types() -> Result<(), Box<EvalAltResult>> {
|
||||
assert!(matches!(
|
||||
engine
|
||||
.compile("#{a:1, b:2, c:3}[true && false]")
|
||||
.expect_err("should error")
|
||||
.unwrap_err()
|
||||
.err_type(),
|
||||
ParseErrorType::MalformedIndexExpr(..)
|
||||
));
|
||||
@ -272,38 +272,32 @@ fn test_map_json() -> Result<(), Box<EvalAltResult>> {
|
||||
engine.parse_json(json, true)?;
|
||||
|
||||
assert!(matches!(
|
||||
*engine.parse_json("123", true).expect_err("should error"),
|
||||
*engine.parse_json("123", true).unwrap_err(),
|
||||
EvalAltResult::ErrorMismatchOutputType(..)
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
*engine.parse_json("{a:42}", true).expect_err("should error"),
|
||||
*engine.parse_json("{a:42}", true).unwrap_err(),
|
||||
EvalAltResult::ErrorParsing(..)
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.parse_json("#{a:123}", true)
|
||||
.expect_err("should error"),
|
||||
*engine.parse_json("#{a:123}", true).unwrap_err(),
|
||||
EvalAltResult::ErrorParsing(..)
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
*engine.parse_json("{a:()}", true).expect_err("should error"),
|
||||
*engine.parse_json("{a:()}", true).unwrap_err(),
|
||||
EvalAltResult::ErrorParsing(..)
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.parse_json("#{a:123+456}", true)
|
||||
.expect_err("should error"),
|
||||
*engine.parse_json("#{a:123+456}", true).unwrap_err(),
|
||||
EvalAltResult::ErrorParsing(..)
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.parse_json("{a:`hello${world}`}", true)
|
||||
.expect_err("should error"),
|
||||
*engine.parse_json("{a:`hello${world}`}", true).unwrap_err(),
|
||||
EvalAltResult::ErrorParsing(..)
|
||||
));
|
||||
|
||||
|
@ -127,7 +127,7 @@ fn test_method_call_typed() -> Result<(), Box<EvalAltResult>> {
|
||||
foo(1000);
|
||||
"#
|
||||
)
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("foo")
|
||||
));
|
||||
|
||||
@ -142,7 +142,7 @@ fn test_method_call_typed() -> Result<(), Box<EvalAltResult>> {
|
||||
x.foo(1000);
|
||||
"#
|
||||
)
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("foo")
|
||||
));
|
||||
|
||||
|
@ -36,21 +36,21 @@ fn test_mismatched_op_custom_type() -> Result<(), Box<EvalAltResult>> {
|
||||
let x = new_ts();
|
||||
let y = new_ts();
|
||||
x == y
|
||||
").expect_err("should error"),
|
||||
").unwrap_err(),
|
||||
EvalAltResult::ErrorFunctionNotFound(f, ..) if f == "== (TestStruct, TestStruct)"));
|
||||
|
||||
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, "))
|
||||
);
|
||||
|
||||
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>())
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
*engine.eval::<TestStruct>("42").expect_err("should error"),
|
||||
*engine.eval::<TestStruct>("42").unwrap_err(),
|
||||
EvalAltResult::ErrorMismatchOutputType(need, actual, ..)
|
||||
if need == "TestStruct" && actual == std::any::type_name::<INT>()
|
||||
));
|
||||
|
@ -227,7 +227,7 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
|
||||
sum
|
||||
"#
|
||||
)
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorTooManyModules(..)
|
||||
));
|
||||
|
||||
@ -250,7 +250,7 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
|
||||
sum
|
||||
"#
|
||||
)
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorInFunctionCall(fn_name, ..) if fn_name == "foo"
|
||||
));
|
||||
|
||||
@ -403,7 +403,7 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.run(r#"import "testing" as ttt; ttt::hidden()"#)
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorFunctionNotFound(fn_name, ..) if fn_name == "ttt::hidden ()"
|
||||
));
|
||||
|
||||
@ -415,13 +415,13 @@ fn test_module_export() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
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
|
||||
));
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
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
|
||||
));
|
||||
|
||||
|
@ -18,7 +18,7 @@ fn test_max_operations() -> Result<(), Box<EvalAltResult>> {
|
||||
engine.run("let x = 0; while x < 20 { x += 1; }")?;
|
||||
|
||||
assert!(matches!(
|
||||
*engine.run("for x in 0..500 {}").expect_err("should error"),
|
||||
*engine.run("for x in 0..500 {}").unwrap_err(),
|
||||
EvalAltResult::ErrorTooManyOperations(..)
|
||||
));
|
||||
|
||||
@ -41,9 +41,7 @@ fn test_max_operations_literal() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.run("[1, 2, 3, 4, 5, 6, 7, 8, 9]")
|
||||
.expect_err("should error"),
|
||||
*engine.run("[1, 2, 3, 4, 5, 6, 7, 8, 9]").unwrap_err(),
|
||||
EvalAltResult::ErrorTooManyOperations(..)
|
||||
));
|
||||
|
||||
@ -54,7 +52,7 @@ fn test_max_operations_literal() -> Result<(), Box<EvalAltResult>> {
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.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(..)
|
||||
));
|
||||
|
||||
@ -110,7 +108,7 @@ fn test_max_operations_functions() -> Result<(), Box<EvalAltResult>> {
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorTooManyOperations(..)
|
||||
));
|
||||
|
||||
@ -137,7 +135,7 @@ fn test_max_operations_eval() -> Result<(), Box<EvalAltResult>> {
|
||||
eval(script);
|
||||
"#
|
||||
)
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorInFunctionCall(.., err, _) if matches!(*err, EvalAltResult::ErrorTooManyOperations(..))
|
||||
));
|
||||
|
||||
@ -162,7 +160,7 @@ fn test_max_operations_progress() -> Result<(), Box<EvalAltResult>> {
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.run("for x in 0..500 {}")
|
||||
.expect_err("should error"),
|
||||
.unwrap_err(),
|
||||
EvalAltResult::ErrorTerminated(x, ..) if x.as_int()? == 42
|
||||
));
|
||||
|
||||
|
@ -26,17 +26,17 @@ fn test_ops_other_number_types() -> Result<(), Box<EvalAltResult>> {
|
||||
scope.push("x", 42_u16);
|
||||
|
||||
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,")
|
||||
));
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
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,")
|
||||
));
|
||||
|
||||
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,")
|
||||
)
|
||||
);
|
||||
|
@ -182,9 +182,44 @@ fn test_optimizer_reoptimize() -> Result<(), Box<EvalAltResult>> {
|
||||
let scope: Scope = ast.iter_literal_variables(true, false).collect();
|
||||
let ast = engine.optimize_ast(&scope, ast, OptimizationLevel::Simple);
|
||||
|
||||
println!("{ast:#?}");
|
||||
|
||||
assert_eq!(engine.eval_ast::<INT>(&ast)?, 84);
|
||||
|
||||
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(())
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ fn test_plugins_package() -> Result<(), Box<EvalAltResult>> {
|
||||
engine.run("const A = [1, 2, 3]; A.no_effect = 42;")?;
|
||||
|
||||
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")
|
||||
)
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -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#"bar("hello")"#)?, 5);
|
||||
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)"
|
||||
)
|
||||
);
|
||||
|
@ -110,7 +110,7 @@ fn test_switch_errors() -> Result<(), Box<EvalAltResult>> {
|
||||
assert!(matches!(
|
||||
engine
|
||||
.compile("switch x { _ => 123, 1 => 42 }")
|
||||
.expect_err("should error")
|
||||
.unwrap_err()
|
||||
.err_type(),
|
||||
ParseErrorType::WrongSwitchDefaultCase
|
||||
));
|
||||
@ -174,7 +174,7 @@ fn test_switch_condition() -> Result<(), Box<EvalAltResult>> {
|
||||
assert!(matches!(
|
||||
engine
|
||||
.compile("switch x { 1 => 123, _ if true => 42 }")
|
||||
.expect_err("should error")
|
||||
.unwrap_err()
|
||||
.err_type(),
|
||||
ParseErrorType::WrongSwitchCaseCondition
|
||||
));
|
||||
@ -269,14 +269,14 @@ fn test_switch_ranges() -> Result<(), Box<EvalAltResult>> {
|
||||
assert!(matches!(
|
||||
engine.compile(
|
||||
"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
|
||||
));
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
assert!(matches!(
|
||||
engine.compile(
|
||||
"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
|
||||
));
|
||||
assert_eq!(
|
||||
|
@ -9,7 +9,7 @@ fn test_tokens_disabled() {
|
||||
assert!(matches!(
|
||||
engine
|
||||
.compile("let x = if true { 42 } else { 0 };")
|
||||
.expect_err("should error")
|
||||
.unwrap_err()
|
||||
.err_type(),
|
||||
ParseErrorType::Reserved(err) if err == "if"
|
||||
));
|
||||
@ -19,13 +19,13 @@ fn test_tokens_disabled() {
|
||||
assert_eq!(
|
||||
*engine
|
||||
.compile("let x = 40 + 2; x += 1;")
|
||||
.expect_err("should error")
|
||||
.unwrap_err()
|
||||
.err_type(),
|
||||
ParseErrorType::UnknownOperator("+=".to_string())
|
||||
);
|
||||
|
||||
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 == "+="
|
||||
));
|
||||
}
|
||||
|
@ -17,6 +17,6 @@ fn test_unit_eq() -> Result<(), Box<EvalAltResult>> {
|
||||
#[test]
|
||||
fn test_unit_with_spaces() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
let _ = engine.run("let x = ( ); x").expect_err("should error");
|
||||
let _ = engine.run("let x = ( ); x").unwrap_err();
|
||||
Ok(())
|
||||
}
|
||||
|
@ -209,7 +209,7 @@ fn test_var_resolver() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "chameleon")?, 1);
|
||||
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")
|
||||
);
|
||||
|
||||
@ -235,7 +235,7 @@ fn test_var_def_filter() -> Result<(), Box<EvalAltResult>> {
|
||||
);
|
||||
|
||||
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"
|
||||
));
|
||||
assert!(matches!(
|
||||
|
@ -1,7 +1,9 @@
|
||||
Build Tools
|
||||
===========
|
||||
|
||||
| File | Description |
|
||||
| -------------- | ------------------------------------------- |
|
||||
| `keywords.txt` | Input file for GNU gperf for the tokenizer. |
|
||||
| `reserved.txt` | Input file for GNU gperf for the tokenizer. |
|
||||
This directory contains input files for various build tools required for building Rhai.
|
||||
|
||||
| File | Build tool | Description |
|
||||
| -------------- | :---------: | ------------------------------------------------------------------ |
|
||||
| `keywords.txt` | GNU `gperf` | Input file for the tokenizer – keywords recognition. |
|
||||
| `reserved.txt` | GNU `gperf` | Input file for the tokenizer – reserved symbols recognition. |
|
||||
|
Loading…
Reference in New Issue
Block a user