Streamline function pointers and currying.

This commit is contained in:
Stephen Chung 2020-10-17 13:49:16 +08:00
parent a16fc71935
commit 39474d6420
9 changed files with 171 additions and 132 deletions

View File

@ -1,6 +1,17 @@
Rhai Release Notes Rhai Release Notes
================== ==================
Version 0.19.3
==============
Version 0.19.2
==============
Bug fix on call module functions.
Version 0.19.1 Version 0.19.1
============== ==============

View File

@ -6,6 +6,8 @@ Function Pointers
It is possible to store a _function pointer_ in a variable just like a normal value. It is possible to store a _function pointer_ in a variable just like a normal value.
In fact, internally a function pointer simply stores the _name_ of the function as a string. In fact, internally a function pointer simply stores the _name_ of the function as a string.
A function pointer is created via the `Fn` function, which takes a [string] parameter.
Call a function pointer using the `call` method. Call a function pointer using the `call` method.

View File

@ -1479,7 +1479,7 @@ impl Engine {
Expr::Stmt(x) => self.eval_stmt(scope, mods, state, lib, this_ptr, &x.0, level), Expr::Stmt(x) => self.eval_stmt(scope, mods, state, lib, this_ptr, &x.0, level),
// var op= rhs // var op= rhs
Expr::Assignment(x) if matches!(x.0, Expr::Variable(_)) => { Expr::Assignment(x) if x.0.get_variable_access(false).is_some() => {
let (lhs_expr, op, rhs_expr, op_pos) = x.as_ref(); let (lhs_expr, op, rhs_expr, op_pos) = x.as_ref();
let mut rhs_val = self let mut rhs_val = self
.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)? .eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?

View File

@ -711,16 +711,17 @@ impl Engine {
let (result, updated) = if _fn_name == KEYWORD_FN_PTR_CALL && obj.is::<FnPtr>() { let (result, updated) = if _fn_name == KEYWORD_FN_PTR_CALL && obj.is::<FnPtr>() {
// FnPtr call // FnPtr call
let fn_ptr = obj.read_lock::<FnPtr>().unwrap(); let fn_ptr = obj.read_lock::<FnPtr>().unwrap();
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
// Redirect function name // Redirect function name
let fn_name = fn_ptr.fn_name(); let fn_name = fn_ptr.fn_name();
let args_len = call_args.len() + fn_ptr.curry().len();
// Recalculate hash // Recalculate hash
let hash = if native { let hash = if native {
0 0
} else { } else {
calc_fn_hash(empty(), fn_name, curry.len() + call_args.len(), empty()) calc_fn_hash(empty(), fn_name, args_len, empty())
}; };
// Arguments are passed as-is, adding the curried arguments // Arguments are passed as-is, adding the curried arguments
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
let mut arg_values = curry let mut arg_values = curry
.iter_mut() .iter_mut()
.chain(call_args.iter_mut()) .chain(call_args.iter_mut())
@ -737,16 +738,17 @@ impl Engine {
{ {
// FnPtr call on object // FnPtr call on object
let fn_ptr = call_args.remove(0).cast::<FnPtr>(); let fn_ptr = call_args.remove(0).cast::<FnPtr>();
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
// Redirect function name // Redirect function name
let fn_name = fn_ptr.get_fn_name().clone(); let fn_name = fn_ptr.fn_name();
let args_len = call_args.len() + fn_ptr.curry().len();
// Recalculate hash // Recalculate hash
let hash = if native { let hash = if native {
0 0
} else { } else {
calc_fn_hash(empty(), &fn_name, curry.len() + call_args.len(), empty()) calc_fn_hash(empty(), fn_name, args_len, empty())
}; };
// Replace the first argument with the object pointer, adding the curried arguments // Replace the first argument with the object pointer, adding the curried arguments
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
let mut arg_values = once(obj) let mut arg_values = once(obj)
.chain(curry.iter_mut()) .chain(curry.iter_mut())
.chain(call_args.iter_mut()) .chain(call_args.iter_mut())
@ -755,7 +757,7 @@ impl Engine {
// Map it to name(args) in function-call style // Map it to name(args) in function-call style
self.exec_fn_call( self.exec_fn_call(
state, lib, &fn_name, hash, args, is_ref, true, pub_only, None, def_val, level, state, lib, fn_name, hash, args, is_ref, true, pub_only, None, def_val, level,
) )
} else if _fn_name == KEYWORD_FN_PTR_CURRY && obj.is::<FnPtr>() { } else if _fn_name == KEYWORD_FN_PTR_CURRY && obj.is::<FnPtr>() {
// Curry call // Curry call
@ -796,14 +798,12 @@ impl Engine {
_redirected = fn_ptr.get_fn_name().clone(); _redirected = fn_ptr.get_fn_name().clone();
_fn_name = &_redirected; _fn_name = &_redirected;
// Add curried arguments // Add curried arguments
if !fn_ptr.curry().is_empty() { fn_ptr
fn_ptr .curry()
.curry() .iter()
.iter() .cloned()
.cloned() .enumerate()
.enumerate() .for_each(|(i, v)| call_args.insert(i, v));
.for_each(|(i, v)| call_args.insert(i, v));
}
// Recalculate the hash based on the new function name and new arguments // Recalculate the hash based on the new function name and new arguments
hash = if native { hash = if native {
0 0
@ -861,35 +861,33 @@ impl Engine {
if !self.has_override(lib, hash_fn, hash_script, pub_only) { if !self.has_override(lib, hash_fn, hash_script, pub_only) {
// Fn - only in function call style // Fn - only in function call style
let expr = args_expr.get(0).unwrap(); return self
let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; .eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?
return arg_value
.take_immutable_string() .take_immutable_string()
.map_err(|typ| { .map_err(|typ| {
self.make_type_mismatch_err::<ImmutableString>(typ, expr.position()) self.make_type_mismatch_err::<ImmutableString>(typ, args_expr[0].position())
}) })
.and_then(|s| FnPtr::try_from(s)) .and_then(|s| FnPtr::try_from(s))
.map(Into::<Dynamic>::into) .map(Into::<Dynamic>::into)
.map_err(|err| err.fill_position(expr.position())); .map_err(|err| err.fill_position(args_expr[0].position()));
} }
} }
// Handle curry() // Handle curry()
if name == KEYWORD_FN_PTR_CURRY && args_expr.len() > 1 { if name == KEYWORD_FN_PTR_CURRY && args_expr.len() > 1 {
let expr = args_expr.get(0).unwrap(); let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
if !fn_ptr.is::<FnPtr>() { if !fn_ptr.is::<FnPtr>() {
return Err(self.make_type_mismatch_err::<FnPtr>( return Err(self.make_type_mismatch_err::<FnPtr>(
self.map_type_name(fn_ptr.type_name()), self.map_type_name(fn_ptr.type_name()),
expr.position(), args_expr[0].position(),
)); ));
} }
let (fn_name, mut fn_curry) = fn_ptr.cast::<FnPtr>().take_data(); let (fn_name, mut fn_curry) = fn_ptr.cast::<FnPtr>().take_data();
// Append the new curried arguments to the existing list. // Append the new curried arguments to the existing list.
args_expr args_expr
.iter() .iter()
.skip(1) .skip(1)
@ -904,8 +902,7 @@ impl Engine {
// Handle is_shared() // Handle is_shared()
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
if name == KEYWORD_IS_SHARED && args_expr.len() == 1 { if name == KEYWORD_IS_SHARED && args_expr.len() == 1 {
let expr = args_expr.get(0).unwrap(); let value = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
return Ok(value.is_shared().into()); return Ok(value.is_shared().into());
} }
@ -913,25 +910,24 @@ impl Engine {
// Handle call() - Redirect function call // Handle call() - Redirect function call
let redirected; let redirected;
let mut args_expr = args_expr.as_ref(); let mut args_expr = args_expr.as_ref();
let mut curry: StaticVec<_> = Default::default(); let mut curry = StaticVec::new();
let mut name = name; let mut name = name;
if name == KEYWORD_FN_PTR_CALL if name == KEYWORD_FN_PTR_CALL
&& args_expr.len() >= 1 && args_expr.len() >= 1
&& !self.has_override(lib, 0, hash_script, pub_only) && !self.has_override(lib, 0, hash_script, pub_only)
{ {
let expr = args_expr.get(0).unwrap(); let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
if !fn_ptr.is::<FnPtr>() { if !fn_ptr.is::<FnPtr>() {
return Err(self.make_type_mismatch_err::<FnPtr>( return Err(self.make_type_mismatch_err::<FnPtr>(
self.map_type_name(fn_ptr.type_name()), self.map_type_name(fn_ptr.type_name()),
expr.position(), args_expr[0].position(),
)); ));
} }
let fn_ptr = fn_ptr.cast::<FnPtr>(); let fn_ptr = fn_ptr.cast::<FnPtr>();
curry = fn_ptr.curry().iter().cloned().collect(); curry.extend(fn_ptr.curry().iter().cloned());
// Redirect function name // Redirect function name
redirected = fn_ptr.take_data().0; redirected = fn_ptr.take_data().0;
@ -941,7 +937,8 @@ impl Engine {
args_expr = &args_expr.as_ref()[1..]; args_expr = &args_expr.as_ref()[1..];
// Recalculate hash // Recalculate hash
hash_script = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty()); let args_len = args_expr.len() + curry.len();
hash_script = calc_fn_hash(empty(), name, args_len, empty());
} }
// Handle is_def_var() // Handle is_def_var()
@ -949,10 +946,10 @@ impl Engine {
let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>())); let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
if !self.has_override(lib, hash_fn, hash_script, pub_only) { if !self.has_override(lib, hash_fn, hash_script, pub_only) {
let expr = args_expr.get(0).unwrap(); let var_name =
let var_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
let var_name = var_name.as_str().map_err(|err| { let var_name = var_name.as_str().map_err(|err| {
self.make_type_mismatch_err::<ImmutableString>(err, expr.position()) self.make_type_mismatch_err::<ImmutableString>(err, args_expr[0].position())
})?; })?;
if var_name.is_empty() { if var_name.is_empty() {
return Ok(false.into()); return Ok(false.into());
@ -974,18 +971,17 @@ impl Engine {
); );
if !self.has_override(lib, hash_fn, hash_script, pub_only) { if !self.has_override(lib, hash_fn, hash_script, pub_only) {
let expr0 = args_expr.get(0).unwrap(); let fn_name =
let expr1 = args_expr.get(1).unwrap(); self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
let num_params =
let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr0, level)?; self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[1], level)?;
let num_params = self.eval_expr(scope, mods, state, lib, this_ptr, expr1, level)?;
let fn_name = fn_name.as_str().map_err(|err| { let fn_name = fn_name.as_str().map_err(|err| {
self.make_type_mismatch_err::<ImmutableString>(err, expr0.position()) self.make_type_mismatch_err::<ImmutableString>(err, args_expr[0].position())
})?;
let num_params = num_params.as_int().map_err(|err| {
self.make_type_mismatch_err::<INT>(err, args_expr[1].position())
})?; })?;
let num_params = num_params
.as_int()
.map_err(|err| self.make_type_mismatch_err::<INT>(err, expr1.position()))?;
if fn_name.is_empty() || num_params < 0 { if fn_name.is_empty() || num_params < 0 {
return Ok(false.into()); return Ok(false.into());
@ -1003,14 +999,14 @@ impl Engine {
if !self.has_override(lib, hash_fn, hash_script, pub_only) { if !self.has_override(lib, hash_fn, hash_script, pub_only) {
// eval - only in function call style // eval - only in function call style
let prev_len = scope.len(); let prev_len = scope.len();
let expr = args_expr.get(0).unwrap(); let script =
let script = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
let script = script.as_str().map_err(|typ| { let script = script.as_str().map_err(|typ| {
self.make_type_mismatch_err::<ImmutableString>(typ, expr.position()) self.make_type_mismatch_err::<ImmutableString>(typ, args_expr[0].position())
})?; })?;
let result = if !script.is_empty() { let result = if !script.is_empty() {
self.eval_script_expr(scope, mods, state, lib, script, level + 1) self.eval_script_expr(scope, mods, state, lib, script, level + 1)
.map_err(|err| err.fill_position(expr.position())) .map_err(|err| err.fill_position(args_expr[0].position()))
} else { } else {
Ok(().into()) Ok(().into())
}; };
@ -1041,41 +1037,38 @@ impl Engine {
} else { } else {
// If the first argument is a variable, and there is no curried arguments, convert to method-call style // If the first argument is a variable, and there is no curried arguments, convert to method-call style
// in order to leverage potential &mut first argument and avoid cloning the value // in order to leverage potential &mut first argument and avoid cloning the value
match args_expr.get(0).unwrap() { if args_expr[0].get_variable_access(false).is_some() && curry.is_empty() {
// func(x, ...) -> x.func(...) // func(x, ...) -> x.func(...)
lhs @ Expr::Variable(_) if curry.is_empty() => { arg_values = args_expr
arg_values = args_expr .iter()
.iter() .skip(1)
.skip(1) .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level))
.map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) .collect::<Result<_, _>>()?;
.collect::<Result<_, _>>()?;
let (target, _, _, pos) = let (target, _, _, pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?; self.search_namespace(scope, mods, state, lib, this_ptr, &args_expr[0])?;
self.inc_operations(state) self.inc_operations(state)
.map_err(|err| err.fill_position(pos))?; .map_err(|err| err.fill_position(pos))?;
args = if target.is_shared() || target.is_value() { args = if target.is_shared() || target.is_value() {
arg_values.insert(0, target.take_or_clone().flatten()); arg_values.insert(0, target.take_or_clone().flatten());
arg_values.iter_mut().collect() arg_values.iter_mut().collect()
} else { } else {
// Turn it into a method call only if the object is not shared and not a simple value // Turn it into a method call only if the object is not shared and not a simple value
is_ref = true; is_ref = true;
once(target.take_ref().unwrap()) once(target.take_ref().unwrap())
.chain(arg_values.iter_mut()) .chain(arg_values.iter_mut())
.collect() .collect()
}; };
} } else {
// func(..., ...) // func(..., ...)
_ => { arg_values = args_expr
arg_values = args_expr .iter()
.iter() .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level))
.map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) .collect::<Result<_, _>>()?;
.collect::<Result<_, _>>()?;
args = curry.iter_mut().chain(arg_values.iter_mut()).collect(); args = curry.iter_mut().chain(arg_values.iter_mut()).collect();
}
} }
} }
@ -1116,50 +1109,46 @@ impl Engine {
// See if the first argument is a variable (not module-qualified). // See if the first argument is a variable (not module-qualified).
// If so, convert to method-call style in order to leverage potential // If so, convert to method-call style in order to leverage potential
// &mut first argument and avoid cloning the value // &mut first argument and avoid cloning the value
match args_expr.get(0).unwrap() { if args_expr[0].get_variable_access(true).is_some() {
// func(x, ...) -> x.func(...) // func(x, ...) -> x.func(...)
Expr::Variable(x) if x.1.is_none() => { arg_values = args_expr
arg_values = args_expr .iter()
.iter() .enumerate()
.enumerate() .map(|(i, expr)| {
.map(|(i, expr)| { // Skip the first argument
// Skip the first argument if i == 0 {
if i == 0 { Ok(Default::default())
Ok(Default::default()) } else {
} else { self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) }
} })
}) .collect::<Result<_, _>>()?;
.collect::<Result<_, _>>()?;
// Get target reference to first argument // Get target reference to first argument
let var_expr = args_expr.get(0).unwrap(); let (target, _, _, pos) =
let (target, _, _, pos) = self.search_scope_only(scope, mods, state, lib, this_ptr, &args_expr[0])?;
self.search_scope_only(scope, mods, state, lib, this_ptr, var_expr)?;
self.inc_operations(state) self.inc_operations(state)
.map_err(|err| err.fill_position(pos))?; .map_err(|err| err.fill_position(pos))?;
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, ...)
_ => {
arg_values = args_expr
.iter()
.map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level))
.collect::<Result<_, _>>()?;
if target.is_shared() || target.is_value() {
arg_values[0] = target.take_or_clone().flatten();
args = arg_values.iter_mut().collect(); 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();
} }
} else {
// func(..., ...) or func(mod::x, ...)
arg_values = args_expr
.iter()
.map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level))
.collect::<Result<_, _>>()?;
args = arg_values.iter_mut().collect();
} }
} }

View File

@ -11,9 +11,7 @@ use crate::token::{is_valid_identifier, Position};
use crate::utils::ImmutableString; use crate::utils::ImmutableString;
use crate::{calc_fn_hash, StaticVec}; use crate::{calc_fn_hash, StaticVec};
use crate::stdlib::{ use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, iter::empty, mem, string::String};
boxed::Box, convert::TryFrom, fmt, iter::empty, mem, string::String, vec::Vec,
};
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
use crate::stdlib::sync::{Arc, RwLock}; use crate::stdlib::sync::{Arc, RwLock};
@ -79,12 +77,15 @@ pub type FnCallArgs<'a> = [&'a mut Dynamic];
/// A general function pointer, which may carry additional (i.e. curried) argument values /// A general function pointer, which may carry additional (i.e. curried) argument values
/// to be passed onto a function during a call. /// to be passed onto a function during a call.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct FnPtr(ImmutableString, Vec<Dynamic>); pub struct FnPtr(ImmutableString, StaticVec<Dynamic>);
impl FnPtr { impl FnPtr {
/// Create a new function pointer. /// Create a new function pointer.
#[inline(always)] #[inline(always)]
pub(crate) fn new_unchecked<S: Into<ImmutableString>>(name: S, curry: Vec<Dynamic>) -> Self { pub(crate) fn new_unchecked<S: Into<ImmutableString>>(
name: S,
curry: StaticVec<Dynamic>,
) -> Self {
Self(name.into(), curry) Self(name.into(), curry)
} }
/// Get the name of the function. /// Get the name of the function.
@ -99,13 +100,13 @@ impl FnPtr {
} }
/// Get the underlying data of the function pointer. /// Get the underlying data of the function pointer.
#[inline(always)] #[inline(always)]
pub(crate) fn take_data(self) -> (ImmutableString, Vec<Dynamic>) { pub(crate) fn take_data(self) -> (ImmutableString, StaticVec<Dynamic>) {
(self.0, self.1) (self.0, self.1)
} }
/// Get the curried arguments. /// Get the curried arguments.
#[inline(always)] #[inline(always)]
pub fn curry(&self) -> &[Dynamic] { pub fn curry(&self) -> &[Dynamic] {
&self.1 self.1.as_ref()
} }
/// Call the function pointer with curried arguments (if any). /// Call the function pointer with curried arguments (if any).
@ -125,24 +126,24 @@ impl FnPtr {
this_ptr: Option<&mut Dynamic>, this_ptr: Option<&mut Dynamic>,
mut arg_values: impl AsMut<[Dynamic]>, mut arg_values: impl AsMut<[Dynamic]>,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let arg_values = arg_values.as_mut();
let fn_name = self.fn_name();
let mut args_data = self let mut args_data = self
.1 .curry()
.iter() .iter()
.cloned() .cloned()
.chain(arg_values.as_mut().iter_mut().map(|v| mem::take(v))) .chain(arg_values.iter_mut().map(mem::take))
.collect::<StaticVec<_>>(); .collect::<StaticVec<_>>();
let has_this = this_ptr.is_some(); let has_this = this_ptr.is_some();
let args_len = args_data.len();
let mut args = args_data.iter_mut().collect::<StaticVec<_>>(); let mut args = args_data.iter_mut().collect::<StaticVec<_>>();
let hash_script = calc_fn_hash(empty(), fn_name, args.len(), empty());
if let Some(obj) = this_ptr { if let Some(obj) = this_ptr {
args.insert(0, obj); args.insert(0, obj);
} }
let fn_name = self.0.as_str();
let hash_script = calc_fn_hash(empty(), fn_name, args_len, empty());
engine engine
.exec_fn_call( .exec_fn_call(
&mut Default::default(), &mut Default::default(),

View File

@ -425,6 +425,30 @@ impl Module {
} }
} }
/// Does the particular Rust function exist in the module?
///
/// The `u64` hash is calculated by the function `crate::calc_fn_hash`.
/// It is also returned by the `set_fn_XXX` calls.
///
/// # Examples
///
/// ```
/// use rhai::Module;
///
/// let mut module = Module::new();
/// module.set_fn_0("calc", || Ok(42_i64));
/// assert!(module.contains_fn_with_name("calc", true));
/// ```
#[inline]
#[allow(dead_code)]
pub(crate) fn contains_fn_with_name(&self, fn_name: &str, public_only: bool) -> bool {
self.functions
.values()
.filter(|(_, access, _, _, _)| !public_only || access.is_public())
.find(|(name, _, _, _, _)| name == fn_name)
.is_some()
}
/// Set a Rust function into the module, returning a hash key. /// Set a Rust function into the module, returning a hash key.
/// ///
/// If there is an existing Rust function of the same hash, it is replaced. /// If there is an existing Rust function of the same hash, it is replaced.

View File

@ -65,6 +65,13 @@ impl PackagesCollection {
pub fn contains_fn(&self, hash: u64, public_only: bool) -> bool { pub fn contains_fn(&self, hash: u64, public_only: bool) -> bool {
self.0.iter().any(|p| p.contains_fn(hash, public_only)) self.0.iter().any(|p| p.contains_fn(hash, public_only))
} }
/// Does the specified function name exist in the `PackagesCollection`?
#[allow(dead_code)]
pub fn contains_fn_with_name(&self, fn_name: &str, public_only: bool) -> bool {
self.0
.iter()
.any(|p| p.contains_fn_with_name(fn_name, public_only))
}
/// Get specified function via its hash key. /// Get specified function via its hash key.
pub fn get_fn(&self, hash: u64, public_only: bool) -> Option<&CallableFunction> { pub fn get_fn(&self, hash: u64, public_only: bool) -> Option<&CallableFunction> {
self.0 self.0

View File

@ -1079,6 +1079,14 @@ impl Expr {
}) })
} }
/// Is the expression a simple variable access?
pub(crate) fn get_variable_access(&self, non_qualified: bool) -> Option<&str> {
match self {
Self::Variable(x) if !non_qualified || x.1.is_none() => Some((x.0).0.as_str()),
_ => None,
}
}
/// Get the `Position` of the expression. /// Get the `Position` of the expression.
pub fn position(&self) -> Position { pub fn position(&self) -> Position {
match self { match self {

View File

@ -41,10 +41,7 @@ impl Expression<'_> {
/// If this expression is a variable name, return it. Otherwise `None`. /// If this expression is a variable name, return it. Otherwise `None`.
#[inline(always)] #[inline(always)]
pub fn get_variable_name(&self) -> Option<&str> { pub fn get_variable_name(&self) -> Option<&str> {
match self.0 { self.0.get_variable_access(true)
Expr::Variable(x) => Some((x.0).0.as_str()),
_ => None,
}
} }
/// Get the expression. /// Get the expression.
#[inline(always)] #[inline(always)]