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).
* 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
==============

View File

@ -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 |
| ------------- | ----------------------------------------------------- |
| ------------- | ------------------------------------------------------------------ |
| `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` | 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`) |
| `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 |

View File

@ -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)

View File

@ -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)?;
}

View File

@ -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()))
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 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"));
}
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)]

View File

@ -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,

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
/// 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

View File

@ -927,20 +927,20 @@ 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))
{
.or_else(|| scope.search(&var.name))
.ok_or_else(|| {
Box::new(ERR::ErrorVariableNotFound(var.name.to_string(), var.pos))
})?;
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();
}
} else {
return Err(ERR::ErrorVariableNotFound(name.to_string(), *pos).into());
}
}
Ok(Dynamic::UNIT)

View File

@ -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() {
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 {
} 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,18 +1507,19 @@ 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))
}
}
Some(f) if f.is_native() => {
let func = f.get_native_fn().expect("native function");

View File

@ -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,

View File

@ -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 }
}
}

View File

@ -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`].

View File

@ -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)

View File

@ -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(),

View File

@ -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

View File

@ -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 {

View File

@ -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(..)
));

View File

@ -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())
);
}

View File

@ -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(..)
));

View File

@ -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(..)
));

View File

@ -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()))
);

View File

@ -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(..)
));

View File

@ -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"
));

View File

@ -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(..))
));

View File

@ -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")
));

View File

@ -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(..)
));

View File

@ -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
);

View File

@ -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(..)
));

View File

@ -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")
));

View File

@ -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>()
));

View File

@ -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
));

View File

@ -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
));

View File

@ -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,")
)
);

View File

@ -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(())
}

View File

@ -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

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#"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)"
)
);

View File

@ -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!(

View File

@ -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 == "+="
));
}

View File

@ -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(())
}

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!(
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!(

View File

@ -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 &ndash; keywords recognition. |
| `reserved.txt` | GNU `gperf` | Input file for the tokenizer &ndash; reserved symbols recognition. |