Implement variable resolver.

This commit is contained in:
Stephen Chung
2020-10-11 21:58:11 +08:00
parent 9d93dac8e7
commit fd5a932611
18 changed files with 511 additions and 237 deletions

View File

@@ -1,7 +1,7 @@
//! Module that defines the extern API of `Engine`.
use crate::any::{Dynamic, Variant};
use crate::engine::{Engine, Imports, State};
use crate::engine::{Engine, EvalContext, Imports, State};
use crate::error::ParseError;
use crate::fn_native::{IteratorFn, SendSync};
use crate::module::{FuncReturn, Module};
@@ -1686,6 +1686,54 @@ impl Engine {
optimize_into_ast(self, scope, stmt, lib, optimization_level)
}
/// Provide a callback that will be invoked before each variable access.
///
/// ## Return Value of Callback
///
/// Return `Ok(None)` to continue with normal variable access.
/// Return `Ok(Some(Dynamic))` as the variable's value.
///
/// ## Errors in Callback
///
/// Return `Err(...)` if there is an error.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// use rhai::Engine;
///
/// let mut engine = Engine::new();
///
/// // Register a variable resolver.
/// engine.on_var(|name, _, _, _| {
/// match name {
/// "MYSTIC_NUMBER" => Ok(Some(42_i64.into())),
/// _ => Ok(None)
/// }
/// });
///
/// engine.eval::<i64>("MYSTIC_NUMBER")?;
///
/// # Ok(())
/// # }
/// ```
#[inline(always)]
pub fn on_var(
&mut self,
callback: impl Fn(
&str,
Option<usize>,
&Scope,
&EvalContext,
) -> Result<Option<Dynamic>, Box<EvalAltResult>>
+ SendSync
+ 'static,
) -> &mut Self {
self.resolve_var = Some(Box::new(callback));
self
}
/// Register a callback for script evaluation progress.
///
/// # Example

View File

@@ -2,7 +2,7 @@
use crate::any::{map_std_type_name, Dynamic, Union};
use crate::fn_call::run_builtin_op_assignment;
use crate::fn_native::{Callback, FnPtr};
use crate::fn_native::{Callback, FnPtr, OnVarCallback};
use crate::module::{Module, ModuleRef};
use crate::optimize::OptimizationLevel;
use crate::packages::{Package, PackagesCollection, StandardPackage};
@@ -10,7 +10,7 @@ use crate::parser::{Expr, ReturnType, Stmt, INT};
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
use crate::result::EvalAltResult;
use crate::scope::{EntryType as ScopeEntryType, Scope};
use crate::syntax::{CustomSyntax, EvalContext};
use crate::syntax::CustomSyntax;
use crate::token::Position;
use crate::{calc_fn_hash, StaticVec};
@@ -148,7 +148,7 @@ pub enum Target<'a> {
}
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
impl Target<'_> {
impl<'a> Target<'a> {
/// Is the `Target` a reference pointing to other data?
#[allow(dead_code)]
#[inline(always)]
@@ -207,7 +207,7 @@ impl Target<'_> {
}
/// Get the value of the `Target` as a `Dynamic`, cloning a referenced value if necessary.
#[inline(always)]
pub fn clone_into_dynamic(self) -> Dynamic {
pub fn take_or_clone(self) -> Dynamic {
match self {
Self::Ref(r) => r.clone(), // Referenced value is cloned
#[cfg(not(feature = "no_closure"))]
@@ -218,6 +218,14 @@ impl Target<'_> {
Self::StringChar(_, _, ch) => ch, // Character is taken
}
}
/// Take a `&mut Dynamic` reference from the `Target`.
#[inline(always)]
pub fn take_ref(self) -> Option<&'a mut Dynamic> {
match self {
Self::Ref(r) => Some(r),
_ => None,
}
}
/// Get a mutable reference from the `Target`.
#[inline(always)]
pub fn as_mut(&mut self) -> &mut Dynamic {
@@ -374,6 +382,39 @@ pub struct Limits {
pub max_map_size: usize,
}
/// Context of a script evaluation process.
#[derive(Debug)]
pub struct EvalContext<'e, 'a, 's, 'm, 't, 'd: 't> {
pub(crate) engine: &'e Engine,
pub(crate) mods: &'a mut Imports,
pub(crate) state: &'s mut State,
pub(crate) lib: &'m Module,
pub(crate) this_ptr: &'t mut Option<&'d mut Dynamic>,
pub(crate) level: usize,
}
impl<'e, 'a, 's, 'm, 't, 'd> EvalContext<'e, 'a, 's, 'm, 't, 'd> {
/// The current `Engine`.
pub fn engine(&self) -> &'e Engine {
self.engine
}
/// _[INTERNALS]_ The current set of modules imported via `import` statements.
/// Available under the `internals` feature only.
#[cfg(feature = "internals")]
#[cfg(not(feature = "no_modules"))]
pub fn imports(&self) -> &'a Imports {
self.mods
}
/// The global namespace containing definition of all script-defined functions.
pub fn namespace(&self) -> &'m Module {
self.lib
}
/// The current nesting level of function calls.
pub fn call_level(&self) -> usize {
self.level
}
}
/// Rhai main scripting engine.
///
/// ```
@@ -412,6 +453,8 @@ pub struct Engine {
pub(crate) custom_keywords: Option<HashMap<String, u8>>,
/// Custom syntax.
pub(crate) custom_syntax: Option<HashMap<String, CustomSyntax>>,
/// Callback closure for resolving variable access.
pub(crate) resolve_var: Option<OnVarCallback>,
/// Callback closure for implementing the `print` command.
pub(crate) print: Callback<str, ()>,
@@ -522,88 +565,6 @@ pub fn search_imports_mut<'s>(
})
}
/// Search for a variable within the scope or within imports,
/// depending on whether the variable name is qualified.
pub fn search_namespace<'s, 'a>(
scope: &'s mut Scope,
mods: &'s mut Imports,
state: &mut State,
this_ptr: &'s mut Option<&mut Dynamic>,
expr: &'a Expr,
) -> Result<(&'s mut Dynamic, &'a str, ScopeEntryType, Position), Box<EvalAltResult>> {
match expr {
Expr::Variable(v) => match v.as_ref() {
// Qualified variable
((name, pos), Some(modules), hash_var, _) => {
let module = search_imports_mut(mods, state, modules)?;
let target = module
.get_qualified_var_mut(*hash_var)
.map_err(|err| match *err {
EvalAltResult::ErrorVariableNotFound(_, _) => {
EvalAltResult::ErrorVariableNotFound(
format!("{}{}", modules, name),
*pos,
)
.into()
}
_ => err.new_position(*pos),
})?;
// Module variables are constant
Ok((target, name, ScopeEntryType::Constant, *pos))
}
// Normal variable access
_ => search_scope_only(scope, state, this_ptr, expr),
},
_ => unreachable!(),
}
}
/// Search for a variable within the scope
pub fn search_scope_only<'s, 'a>(
scope: &'s mut Scope,
state: &mut State,
this_ptr: &'s mut Option<&mut Dynamic>,
expr: &'a Expr,
) -> Result<(&'s mut Dynamic, &'a str, ScopeEntryType, Position), Box<EvalAltResult>> {
let ((name, pos), _, _, index) = match expr {
Expr::Variable(v) => v.as_ref(),
_ => unreachable!(),
};
// Check if the variable is `this`
if name == KEYWORD_THIS {
if let Some(val) = this_ptr {
return Ok(((*val).into(), KEYWORD_THIS, ScopeEntryType::Normal, *pos));
} else {
return EvalAltResult::ErrorUnboundThis(*pos).into();
}
}
// Check if it is directly indexed
let index = if state.always_search { None } else { *index };
let index = if let Some(index) = index {
scope.len() - index.get()
} else {
// Find the variable in the scope
scope
.get_index(name)
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(name.into(), *pos))?
.0
};
let (val, typ) = scope.get_mut(index);
// Check for data race - probably not necessary because the only place it should conflict is in a method call
// when the object variable is also used as a parameter.
// if cfg!(not(feature = "no_closure")) && val.is_locked() {
// return EvalAltResult::ErrorDataRace(name.into(), *pos).into();
// }
Ok((val, name, typ, *pos))
}
impl Engine {
/// Create a new `Engine`
#[inline(always)]
@@ -628,6 +589,9 @@ impl Engine {
custom_keywords: None,
custom_syntax: None,
// variable resolver
resolve_var: None,
// default print/debug implementations
print: Box::new(default_print),
debug: Box::new(default_print),
@@ -678,6 +642,8 @@ impl Engine {
custom_keywords: None,
custom_syntax: None,
resolve_var: None,
print: Box::new(|_| {}),
debug: Box::new(|_| {}),
progress: None,
@@ -702,6 +668,111 @@ impl Engine {
}
}
/// Search for a variable within the scope or within imports,
/// depending on whether the variable name is qualified.
pub(crate) fn search_namespace<'s, 'a>(
&self,
scope: &'s mut Scope,
mods: &'s mut Imports,
state: &mut State,
lib: &Module,
this_ptr: &'s mut Option<&mut Dynamic>,
expr: &'a Expr,
) -> Result<(Target<'s>, &'a str, ScopeEntryType, Position), Box<EvalAltResult>> {
match expr {
Expr::Variable(v) => match v.as_ref() {
// Qualified variable
((name, pos), Some(modules), hash_var, _) => {
let module = search_imports_mut(mods, state, modules)?;
let target =
module
.get_qualified_var_mut(*hash_var)
.map_err(|err| match *err {
EvalAltResult::ErrorVariableNotFound(_, _) => {
EvalAltResult::ErrorVariableNotFound(
format!("{}{}", modules, name),
*pos,
)
.into()
}
_ => err.fill_position(*pos),
})?;
// Module variables are constant
Ok((target.into(), name, ScopeEntryType::Constant, *pos))
}
// Normal variable access
_ => self.search_scope_only(scope, mods, state, lib, this_ptr, expr),
},
_ => unreachable!(),
}
}
/// Search for a variable within the scope
pub(crate) fn search_scope_only<'s, 'a>(
&self,
scope: &'s mut Scope,
mods: &mut Imports,
state: &mut State,
lib: &Module,
this_ptr: &'s mut Option<&mut Dynamic>,
expr: &'a Expr,
) -> Result<(Target<'s>, &'a str, ScopeEntryType, Position), Box<EvalAltResult>> {
let ((name, pos), _, _, index) = match expr {
Expr::Variable(v) => v.as_ref(),
_ => unreachable!(),
};
// Check if the variable is `this`
if name == KEYWORD_THIS {
if let Some(val) = this_ptr {
return Ok(((*val).into(), KEYWORD_THIS, ScopeEntryType::Normal, *pos));
} else {
return EvalAltResult::ErrorUnboundThis(*pos).into();
}
}
// Check if it is directly indexed
let index = if state.always_search { None } else { *index };
// Check the variable resolver, if any
if let Some(ref resolve_var) = self.resolve_var {
let context = EvalContext {
engine: self,
mods,
state,
lib,
this_ptr,
level: 0,
};
if let Some(result) = resolve_var(name, index.map(|v| v.get()), scope, &context)
.map_err(|err| err.fill_position(*pos))?
{
return Ok((result.into(), name, ScopeEntryType::Constant, *pos));
}
}
let index = if let Some(index) = index {
scope.len() - index.get()
} else {
// Find the variable in the scope
scope
.get_index(name)
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(name.into(), *pos))?
.0
};
let (val, typ) = scope.get_mut(index);
// Check for data race - probably not necessary because the only place it should conflict is in a method call
// when the object variable is also used as a parameter.
// if cfg!(not(feature = "no_closure")) && val.is_locked() {
// return EvalAltResult::ErrorDataRace(name.into(), *pos).into();
// }
Ok((val.into(), name, typ, *pos))
}
/// Chain-evaluate a dot/index chain.
/// Position in `EvalAltResult` is `None` and must be set afterwards.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
@@ -750,7 +821,7 @@ impl Engine {
state, lib, this_ptr, obj_ptr, expr, idx_values, next_chain, level,
new_val,
)
.map_err(|err| err.new_position(*pos))
.map_err(|err| err.fill_position(*pos))
}
// xxx[rhs] = new_val
_ if new_val.is_some() => {
@@ -800,7 +871,7 @@ impl Engine {
// xxx[rhs]
_ => self
.get_indexed_mut(state, lib, target, idx_val, pos, false, true, level)
.map(|v| (v.clone_into_dynamic(), false)),
.map(|v| (v.take_or_clone(), false)),
}
}
@@ -815,7 +886,7 @@ impl Engine {
state, lib, name, *hash, target, idx_val, &def_val, *native, false,
level,
)
.map_err(|err| err.new_position(*pos))
.map_err(|err| err.fill_position(*pos))
}
// xxx.module::fn_name(...) - syntax error
Expr::FnCall(_) => unreachable!(),
@@ -837,7 +908,7 @@ impl Engine {
state, lib, target, index, *pos, false, false, level,
)?;
Ok((val.clone_into_dynamic(), false))
Ok((val.take_or_clone(), false))
}
// xxx.id = ???
Expr::Property(x) if new_val.is_some() => {
@@ -849,7 +920,7 @@ impl Engine {
level,
)
.map(|(v, _)| (v, true))
.map_err(|err| err.new_position(*pos))
.map_err(|err| err.fill_position(*pos))
}
// xxx.id
Expr::Property(x) => {
@@ -860,7 +931,7 @@ impl Engine {
level,
)
.map(|(v, _)| (v, false))
.map_err(|err| err.new_position(*pos))
.map_err(|err| err.fill_position(*pos))
}
// {xxx:map}.sub_lhs[expr] | {xxx:map}.sub_lhs.expr
Expr::Index(x) | Expr::Dot(x) if target.is::<Map>() => {
@@ -883,7 +954,7 @@ impl Engine {
state, lib, name, *hash, target, idx_val, &def_val,
*native, false, level,
)
.map_err(|err| err.new_position(*pos))?;
.map_err(|err| err.fill_position(*pos))?;
val.into()
}
// {xxx:map}.module::fn_name(...) - syntax error
@@ -896,7 +967,7 @@ impl Engine {
state, lib, this_ptr, &mut val, expr, idx_values, next_chain, level,
new_val,
)
.map_err(|err| err.new_position(*pos))
.map_err(|err| err.fill_position(*pos))
}
// xxx.sub_lhs[expr] | xxx.sub_lhs.expr
Expr::Index(x) | Expr::Dot(x) => {
@@ -913,7 +984,7 @@ impl Engine {
state, lib, getter, 0, args, is_ref, true, false, None,
&None, level,
)
.map_err(|err| err.new_position(*pos))?;
.map_err(|err| err.fill_position(*pos))?;
let val = &mut val;
@@ -929,7 +1000,7 @@ impl Engine {
level,
new_val,
)
.map_err(|err| err.new_position(*pos))?;
.map_err(|err| err.fill_position(*pos))?;
// Feed the value back via a setter just in case it has been updated
if updated || may_be_changed {
@@ -945,7 +1016,7 @@ impl Engine {
EvalAltResult::ErrorDotExpr(_, _) => {
Ok(Default::default())
}
_ => Err(err.new_position(*pos)),
_ => Err(err.fill_position(*pos)),
},
)?;
}
@@ -961,7 +1032,7 @@ impl Engine {
state, lib, name, *hash, target, idx_val, &def_val,
*native, false, level,
)
.map_err(|err| err.new_position(*pos))?;
.map_err(|err| err.fill_position(*pos))?;
let val = &mut val;
let target = &mut val.into();
@@ -969,7 +1040,7 @@ impl Engine {
state, lib, this_ptr, target, expr, idx_values, next_chain,
level, new_val,
)
.map_err(|err| err.new_position(*pos))
.map_err(|err| err.fill_position(*pos))
}
// xxx.module::fn_name(...) - syntax error
Expr::FnCall(_) => unreachable!(),
@@ -1017,10 +1088,10 @@ impl Engine {
let (var_name, var_pos) = &x.0;
self.inc_operations(state)
.map_err(|err| err.new_position(*var_pos))?;
.map_err(|err| err.fill_position(*var_pos))?;
let (target, _, typ, pos) =
search_namespace(scope, mods, state, this_ptr, dot_lhs)?;
self.search_namespace(scope, mods, state, lib, this_ptr, dot_lhs)?;
// Constants cannot be modified
match typ {
@@ -1036,7 +1107,7 @@ impl Engine {
state, lib, &mut None, obj_ptr, dot_rhs, idx_values, chain_type, level, new_val,
)
.map(|(v, _)| v)
.map_err(|err| err.new_position(*op_pos))
.map_err(|err| err.fill_position(*op_pos))
}
// {expr}.??? = ??? or {expr}[???] = ???
expr if new_val.is_some() => {
@@ -1050,7 +1121,7 @@ impl Engine {
state, lib, this_ptr, obj_ptr, dot_rhs, idx_values, chain_type, level, new_val,
)
.map(|(v, _)| v)
.map_err(|err| err.new_position(*op_pos))
.map_err(|err| err.fill_position(*op_pos))
}
}
}
@@ -1075,7 +1146,7 @@ impl Engine {
level: usize,
) -> Result<(), Box<EvalAltResult>> {
self.inc_operations(state)
.map_err(|err| err.new_position(expr.position()))?;
.map_err(|err| err.fill_position(expr.position()))?;
match expr {
Expr::FnCall(x) if x.1.is_none() => {
@@ -1248,7 +1319,7 @@ impl Engine {
level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> {
self.inc_operations(state)
.map_err(|err| err.new_position(rhs.position()))?;
.map_err(|err| err.fill_position(rhs.position()))?;
let lhs_value = self.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?;
let rhs_value = self.eval_expr(scope, mods, state, lib, this_ptr, rhs, level)?;
@@ -1270,7 +1341,7 @@ impl Engine {
if self
.call_native_fn(state, lib, op, hash, args, false, false, &def_value)
.map_err(|err| err.new_position(rhs.position()))?
.map_err(|err| err.fill_position(rhs.position()))?
.0
.as_bool()
.unwrap_or(false)
@@ -1310,7 +1381,7 @@ impl Engine {
level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> {
self.inc_operations(state)
.map_err(|err| err.new_position(expr.position()))?;
.map_err(|err| err.fill_position(expr.position()))?;
let result = match expr {
Expr::Expr(x) => self.eval_expr(scope, mods, state, lib, this_ptr, x.as_ref(), level),
@@ -1329,8 +1400,9 @@ impl Engine {
}
}
Expr::Variable(_) => {
let (val, _, _, _) = search_namespace(scope, mods, state, this_ptr, expr)?;
Ok(val.clone())
let (val, _, _, _) =
self.search_namespace(scope, mods, state, lib, this_ptr, expr)?;
Ok(val.take_or_clone())
}
Expr::Property(_) => unreachable!(),
@@ -1340,12 +1412,18 @@ impl Engine {
// var op= rhs
Expr::Assignment(x) if matches!(x.0, Expr::Variable(_)) => {
let (lhs_expr, op, rhs_expr, op_pos) = x.as_ref();
let mut rhs_val =
self.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?;
let (lhs_ptr, name, typ, pos) =
search_namespace(scope, mods, state, this_ptr, lhs_expr)?;
let mut rhs_val = self
.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?
.flatten();
let (mut lhs_ptr, name, typ, pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?;
if !lhs_ptr.is_ref() {
return EvalAltResult::ErrorAssignmentToConstant(name.to_string(), pos).into();
}
self.inc_operations(state)
.map_err(|err| err.new_position(pos))?;
.map_err(|err| err.fill_position(pos))?;
match typ {
// Assignment to constant variable
@@ -1354,11 +1432,10 @@ impl Engine {
)),
// Normal assignment
ScopeEntryType::Normal if op.is_empty() => {
let value = rhs_val.flatten();
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
*lhs_ptr.write_lock::<Dynamic>().unwrap() = value;
*lhs_ptr.as_mut().write_lock::<Dynamic>().unwrap() = rhs_val;
} else {
*lhs_ptr = value;
*lhs_ptr.as_mut() = rhs_val;
}
Ok(Default::default())
}
@@ -1369,7 +1446,8 @@ impl Engine {
// 3) Map to `var = var op rhs`
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
let arg_types = once(lhs_ptr.type_id()).chain(once(rhs_val.type_id()));
let arg_types =
once(lhs_ptr.as_mut().type_id()).chain(once(rhs_val.type_id()));
let hash_fn = calc_fn_hash(empty(), op, 2, arg_types);
match self
@@ -1383,10 +1461,10 @@ impl Engine {
let lhs_ptr_inner;
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
lock_guard = lhs_ptr.write_lock::<Dynamic>().unwrap();
lock_guard = lhs_ptr.as_mut().write_lock::<Dynamic>().unwrap();
lhs_ptr_inner = lock_guard.deref_mut();
} else {
lhs_ptr_inner = lhs_ptr;
lhs_ptr_inner = lhs_ptr.as_mut();
}
let args = &mut [lhs_ptr_inner, &mut rhs_val];
@@ -1399,13 +1477,14 @@ impl Engine {
}
}
// Built-in op-assignment function
_ if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_some() => {}
_ if run_builtin_op_assignment(op, lhs_ptr.as_mut(), &rhs_val)?
.is_some() => {}
// Not built-in: expand to `var = var op rhs`
_ => {
let op = &op[..op.len() - 1]; // extract operator without =
// Clone the LHS value
let args = &mut [&mut lhs_ptr.clone(), &mut rhs_val];
let args = &mut [&mut lhs_ptr.as_mut().clone(), &mut rhs_val];
// Run function
let (value, _) = self
@@ -1413,14 +1492,14 @@ impl Engine {
state, lib, op, 0, args, false, false, false, None, &None,
level,
)
.map_err(|err| err.new_position(*op_pos))?;
.map_err(|err| err.fill_position(*op_pos))?;
let value = value.flatten();
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
*lhs_ptr.write_lock::<Dynamic>().unwrap() = value;
*lhs_ptr.as_mut().write_lock::<Dynamic>().unwrap() = value;
} else {
*lhs_ptr = value;
*lhs_ptr.as_mut() = value;
}
}
}
@@ -1451,7 +1530,7 @@ impl Engine {
state, lib, op, 0, args, false, false, false, None, &None, level,
)
.map(|(v, _)| v)
.map_err(|err| err.new_position(*op_pos))?;
.map_err(|err| err.fill_position(*op_pos))?;
Some((result, rhs_expr.position()))
};
@@ -1520,7 +1599,7 @@ impl Engine {
scope, mods, state, lib, this_ptr, name, args_expr, &def_val, *hash, *native,
false, *capture, level,
)
.map_err(|err| err.new_position(*pos))
.map_err(|err| err.fill_position(*pos))
}
// Module-qualified function call
@@ -1530,7 +1609,7 @@ impl Engine {
scope, mods, state, lib, this_ptr, modules, name, args_expr, *def_val, *hash,
*capture, level,
)
.map_err(|err| err.new_position(*pos))
.map_err(|err| err.fill_position(*pos))
}
Expr::In(x) => self.eval_in_expr(scope, mods, state, lib, this_ptr, &x.0, &x.1, level),
@@ -1571,20 +1650,21 @@ impl Engine {
let func = (x.0).1.as_ref();
let ep = (x.0).0.iter().map(|e| e.into()).collect::<StaticVec<_>>();
let mut context = EvalContext {
engine: self,
mods,
state,
lib,
this_ptr,
level,
};
func(self, &mut context, scope, ep.as_ref())
func(scope, &mut context, ep.as_ref())
}
_ => unreachable!(),
};
self.check_data_size(result)
.map_err(|err| err.new_position(expr.position()))
.map_err(|err| err.fill_position(expr.position()))
}
/// Evaluate a statement
@@ -1605,7 +1685,7 @@ impl Engine {
level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> {
self.inc_operations(state)
.map_err(|err| err.new_position(stmt.position()))?;
.map_err(|err| err.fill_position(stmt.position()))?;
let result = match stmt {
// No-op
@@ -1720,7 +1800,7 @@ impl Engine {
}
self.inc_operations(state)
.map_err(|err| err.new_position(stmt.position()))?;
.map_err(|err| err.fill_position(stmt.position()))?;
match self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level) {
Ok(_) => (),
@@ -1872,7 +1952,7 @@ impl Engine {
};
self.check_data_size(result)
.map_err(|err| err.new_position(stmt.position()))
.map_err(|err| err.fill_position(stmt.position()))
}
/// Check a result to ensure that the data size is within allowable limit.

View File

@@ -2,9 +2,9 @@
use crate::any::Dynamic;
use crate::engine::{
search_imports, search_namespace, search_scope_only, Engine, Imports, State, KEYWORD_DEBUG,
KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_FN,
KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
search_imports, Engine, Imports, State, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR,
KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_FN, KEYWORD_IS_DEF_VAR,
KEYWORD_PRINT, KEYWORD_TYPE_OF,
};
use crate::error::ParseErrorType;
use crate::fn_native::{FnCallArgs, FnPtr};
@@ -861,7 +861,7 @@ impl Engine {
})
.and_then(|s| FnPtr::try_from(s))
.map(Into::<Dynamic>::into)
.map_err(|err| err.new_position(expr.position()));
.map_err(|err| err.fill_position(expr.position()));
}
}
@@ -1000,7 +1000,7 @@ impl Engine {
})?;
let result = if !script.is_empty() {
self.eval_script_expr(scope, mods, state, lib, script, level + 1)
.map_err(|err| err.new_position(expr.position()))
.map_err(|err| err.fill_position(expr.position()))
} else {
Ok(().into())
};
@@ -1040,18 +1040,21 @@ impl Engine {
.map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level))
.collect::<Result<_, _>>()?;
let (target, _, _, pos) = search_namespace(scope, mods, state, this_ptr, lhs)?;
let (target, _, _, pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?;
self.inc_operations(state)
.map_err(|err| err.new_position(pos))?;
.map_err(|err| err.fill_position(pos))?;
args = if target.is_shared() {
arg_values.insert(0, target.flatten_clone());
args = if target.is_shared() || target.is_value() {
arg_values.insert(0, target.take_or_clone().flatten());
arg_values.iter_mut().collect()
} else {
// Turn it into a method call only if the object is not shared
// Turn it into a method call only if the object is not shared and not a simple value
is_ref = true;
once(target).chain(arg_values.iter_mut()).collect()
once(target.take_ref().unwrap())
.chain(arg_values.iter_mut())
.collect()
};
}
// func(..., ...)
@@ -1121,16 +1124,23 @@ impl Engine {
.collect::<Result<_, _>>()?;
// Get target reference to first argument
let var_expr = args_expr.get(0).unwrap();
let (target, _, _, pos) =
search_scope_only(scope, state, this_ptr, args_expr.get(0).unwrap())?;
self.search_scope_only(scope, mods, state, lib, this_ptr, var_expr)?;
self.inc_operations(state)
.map_err(|err| err.new_position(pos))?;
.map_err(|err| err.fill_position(pos))?;
let (first, rest) = arg_values.split_first_mut().unwrap();
first_arg_value = Some(first);
args = once(target).chain(rest.iter_mut()).collect();
if target.is_shared() || target.is_value() {
arg_values[0] = target.take_or_clone().flatten();
args = arg_values.iter_mut().collect();
} else {
let (first, rest) = arg_values.split_first_mut().unwrap();
first_arg_value = Some(first);
args = once(target.take_ref().unwrap())
.chain(rest.iter_mut())
.collect();
}
}
// func(..., ...) or func(mod::x, ...)
_ => {

View File

@@ -1,11 +1,12 @@
//! Module defining interfaces to native-Rust functions.
use crate::any::Dynamic;
use crate::engine::Engine;
use crate::engine::{Engine, EvalContext};
use crate::module::Module;
use crate::parser::{FnAccess, ScriptFnDef};
use crate::plugin::PluginFunction;
use crate::result::EvalAltResult;
use crate::scope::Scope;
use crate::token::{is_valid_identifier, Position};
use crate::utils::ImmutableString;
@@ -220,6 +221,21 @@ pub type Callback<T, R> = Box<dyn Fn(&T) -> R + 'static>;
#[cfg(feature = "sync")]
pub type Callback<T, R> = Box<dyn Fn(&T) -> R + Send + Sync + 'static>;
/// A standard callback function.
#[cfg(not(feature = "sync"))]
pub type OnVarCallback = Box<
dyn Fn(&str, Option<usize>, &Scope, &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>>
+ 'static,
>;
/// A standard callback function.
#[cfg(feature = "sync")]
pub type OnVarCallback = Box<
dyn Fn(&str, Option<usize>, &Scope, &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>>
+ Send
+ Sync
+ 'static,
>;
/// A type encapsulating a function callable by Rhai.
#[derive(Clone)]
pub enum CallableFunction {

View File

@@ -83,7 +83,7 @@ mod r#unsafe;
mod utils;
pub use any::Dynamic;
pub use engine::Engine;
pub use engine::{Engine, EvalContext};
pub use error::{ParseError, ParseErrorType};
pub use fn_native::{FnPtr, IteratorFn};
pub use fn_register::{RegisterFn, RegisterResultFn};
@@ -91,7 +91,7 @@ pub use module::Module;
pub use parser::{ImmutableString, AST, INT};
pub use result::EvalAltResult;
pub use scope::Scope;
pub use syntax::{EvalContext, Expression};
pub use syntax::Expression;
pub use token::Position;
#[cfg(feature = "internals")]

View File

@@ -1430,11 +1430,12 @@ impl Module {
)
.map_err(|err| {
// Wrap the error in a module-error
Box::new(EvalAltResult::ErrorInModule(
EvalAltResult::ErrorInModule(
"".to_string(),
err,
Position::none(),
))
)
.into()
})
},
);

View File

@@ -339,7 +339,7 @@ impl EvalAltResult {
/// Consume the current `EvalAltResult` and return a new one with the specified `Position`
/// if the current position is `Position::None`.
#[inline(always)]
pub(crate) fn new_position(mut self: Box<Self>, new_position: Position) -> Box<Self> {
pub(crate) fn fill_position(mut self: Box<Self>, new_position: Position) -> Box<Self> {
if self.position().is_none() {
self.set_position(new_position);
}

View File

@@ -1,10 +1,9 @@
//! Module implementing custom syntax for `Engine`.
use crate::any::Dynamic;
use crate::engine::{Engine, Imports, State, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
use crate::engine::{Engine, EvalContext, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
use crate::error::{LexError, ParseError};
use crate::fn_native::{SendSync, Shared};
use crate::module::Module;
use crate::parser::Expr;
use crate::result::EvalAltResult;
use crate::scope::Scope;
@@ -19,15 +18,11 @@ use crate::stdlib::{
/// A general expression evaluation trait object.
#[cfg(not(feature = "sync"))]
pub type FnCustomSyntaxEval = dyn Fn(
&Engine,
&mut EvalContext,
&mut Scope,
&[Expression],
) -> Result<Dynamic, Box<EvalAltResult>>;
pub type FnCustomSyntaxEval =
dyn Fn(&mut Scope, &mut EvalContext, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>;
/// A general expression evaluation trait object.
#[cfg(feature = "sync")]
pub type FnCustomSyntaxEval = dyn Fn(&Engine, &mut EvalContext, &mut Scope, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>
pub type FnCustomSyntaxEval = dyn Fn(&mut Scope, &mut EvalContext, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>
+ Send
+ Sync;
@@ -63,6 +58,30 @@ impl Expression<'_> {
}
}
impl EvalContext<'_, '_, '_, '_, '_, '_> {
/// Evaluate an expression tree.
///
/// ## WARNING - Low Level API
///
/// This function is very low level. It evaluates an expression from an AST.
#[inline(always)]
pub fn eval_expression_tree(
&mut self,
scope: &mut Scope,
expr: &Expression,
) -> Result<Dynamic, Box<EvalAltResult>> {
self.engine.eval_expr(
scope,
self.mods,
self.state,
self.lib,
self.this_ptr,
expr.expr(),
self.level,
)
}
}
#[derive(Clone)]
pub struct CustomSyntax {
pub segments: StaticVec<String>,
@@ -77,27 +96,17 @@ impl fmt::Debug for CustomSyntax {
}
}
/// Context of a script evaluation process.
#[derive(Debug)]
pub struct EvalContext<'a, 's, 'm, 't, 'd: 't> {
pub(crate) mods: &'a mut Imports,
pub(crate) state: &'s mut State,
pub(crate) lib: &'m Module,
pub(crate) this_ptr: &'t mut Option<&'d mut Dynamic>,
pub(crate) level: usize,
}
impl Engine {
/// Register a custom syntax with the `Engine`.
///
/// * `keywords` holds a slice of strings that define the custom syntax.
/// * `new_vars` is the number of new variables declared by this custom syntax, or the number of variables removed (if negative).
/// * `func` is the implementation function.
pub fn register_custom_syntax<S: AsRef<str> + ToString>(
&mut self,
keywords: &[S],
scope_delta: isize,
func: impl Fn(
&Engine,
&mut EvalContext,
&mut Scope,
&[Expression],
) -> Result<Dynamic, Box<EvalAltResult>>
new_vars: isize,
func: impl Fn(&mut Scope, &mut EvalContext, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>
+ SendSync
+ 'static,
) -> Result<&mut Self, ParseError> {
@@ -176,7 +185,7 @@ impl Engine {
let syntax = CustomSyntax {
segments,
func: (Box::new(func) as Box<FnCustomSyntaxEval>).into(),
scope_delta,
scope_delta: new_vars,
};
if self.custom_syntax.is_none() {
@@ -190,27 +199,4 @@ impl Engine {
Ok(self)
}
/// Evaluate an expression tree.
///
/// ## WARNING - Low Level API
///
/// This function is very low level. It evaluates an expression from an AST.
#[inline(always)]
pub fn eval_expression_tree(
&self,
context: &mut EvalContext,
scope: &mut Scope,
expr: &Expression,
) -> Result<Dynamic, Box<EvalAltResult>> {
self.eval_expr(
scope,
context.mods,
context.state,
context.lib,
context.this_ptr,
expr.expr(),
context.level,
)
}
}