Skip to content

Commit

Permalink
Merge pull request #954 from schungx/master
Browse files Browse the repository at this point in the history
Add EvalContext::call_fn_XXX API
  • Loading branch information
schungx authored Jan 20, 2025
2 parents 23964c8 + d7fafc0 commit dc861c4
Show file tree
Hide file tree
Showing 5 changed files with 257 additions and 8 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ Bug fixes
New Features
------------

* It is possible to create a function pointer (`FnPtr`) which binds to a native Rust function or closure via `FnPtr::from_dn` and `FnPtr::from_dyn_fn`. When called in script, the embedded function will be called.
* It is possible to create a function pointer (`FnPtr`) which binds to a native Rust function or closure via `FnPtr::from_dn` and `FnPtr::from_dyn_fn`. When called in script, the embedded function will be called (thanks [`@makspll`](https://github.com/makspll) [952](https://github.com/rhaiscript/rhai/pull/952)).

Enhancements
------------

* The methods `call_fn`, `call_native_fn`, `call_fn_raw` and `call_native_fn_raw` are added to `EvalContext` (thanks [`@rawhuul`](https://github.com/rawhuul) [954](https://github.com/rhaiscript/rhai/pull/954)).
* A new `internals` function, `Engine::collect_fn_metadata`, is added to collect all functions metadata. This is to facilitate better error reporting for missing functions (thanks [`therealprof`](https://github.com/therealprof) [945](https://github.com/rhaiscript/rhai/pull/945)).


Expand Down
250 changes: 249 additions & 1 deletion src/eval/eval_context.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
//! Evaluation context.
use super::{Caches, GlobalRuntimeState};
use crate::{expose_under_internals, Dynamic, Engine, Scope};
use crate::ast::FnCallHashes;
use crate::tokenizer::{is_valid_function_name, Token};
use crate::types::dynamic::Variant;
use crate::{
calc_fn_hash, expose_under_internals, Dynamic, Engine, FnArgsVec, FuncArgs, Position,
RhaiResult, RhaiResultOf, Scope, StaticVec, ERR,
};
use std::any::type_name;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;

Expand Down Expand Up @@ -184,4 +191,245 @@ impl<'a, 's, 'ps, 'g, 'c, 't> EvalContext<'a, 's, 'ps, 'g, 'c, 't> {
.eval_expr(self.global, self.caches, self.scope, this_ptr, expr),
}
}

/// Call a function inside the [evaluation context][`EvalContext`] with the provided arguments.
pub fn call_fn<T: Variant + Clone>(
&mut self,
fn_name: impl AsRef<str>,
args: impl FuncArgs,
) -> RhaiResultOf<T> {
let engine = self.engine();

let mut arg_values = StaticVec::new_const();
args.parse(&mut arg_values);

let args = &mut arg_values.iter_mut().collect::<FnArgsVec<_>>();

let is_ref_mut = if let Some(this_ptr) = self.this_ptr.as_deref_mut() {
args.insert(0, this_ptr);
true
} else {
false
};

_call_fn_raw(
engine,
self.global,
self.caches,
self.scope,
fn_name,
args,
false,
is_ref_mut,
false,
)
.and_then(|result| {
result.try_cast_result().map_err(|r| {
let result_type = engine.map_type_name(r.type_name());
let cast_type = match type_name::<T>() {
typ if typ.contains("::") => engine.map_type_name(typ),
typ => typ,
};
ERR::ErrorMismatchOutputType(cast_type.into(), result_type.into(), Position::NONE)
.into()
})
})
}
/// Call a registered native Rust function inside the [evaluation context][`EvalContext`] with
/// the provided arguments.
///
/// This is often useful because Rust functions typically only want to cross-call other
/// registered Rust functions and not have to worry about scripted functions hijacking the
/// process unknowingly (or deliberately).
pub fn call_native_fn<T: Variant + Clone>(
&mut self,
fn_name: impl AsRef<str>,
args: impl FuncArgs,
) -> RhaiResultOf<T> {
let engine = self.engine();

let mut arg_values = StaticVec::new_const();
args.parse(&mut arg_values);

let args = &mut arg_values.iter_mut().collect::<FnArgsVec<_>>();

let is_ref_mut = if let Some(this_ptr) = self.this_ptr.as_deref_mut() {
args.insert(0, this_ptr);
true
} else {
false
};

_call_fn_raw(
engine,
self.global,
self.caches,
self.scope,
fn_name,
args,
true,
is_ref_mut,
false,
)
.and_then(|result| {
result.try_cast_result().map_err(|r| {
let result_type = engine.map_type_name(r.type_name());
let cast_type = match type_name::<T>() {
typ if typ.contains("::") => engine.map_type_name(typ),
typ => typ,
};
ERR::ErrorMismatchOutputType(cast_type.into(), result_type.into(), Position::NONE)
.into()
})
})
}
/// Call a function (native Rust or scripted) inside the [evaluation context][`EvalContext`].
///
/// If `is_method_call` is [`true`], the first argument is assumed to be the `this` pointer for
/// a script-defined function (or the object of a method call).
///
/// # WARNING - Low Level API
///
/// This function is very low level.
///
/// # Arguments
///
/// All arguments may be _consumed_, meaning that they may be replaced by `()`. This is to avoid
/// unnecessarily cloning the arguments.
///
/// **DO NOT** reuse the arguments after this call. If they are needed afterwards, clone them
/// _before_ calling this function.
///
/// If `is_ref_mut` is [`true`], the first argument is assumed to be passed by reference and is
/// not consumed.
#[inline(always)]
pub fn call_fn_raw(
&mut self,
fn_name: impl AsRef<str>,
is_ref_mut: bool,
is_method_call: bool,
args: &mut [&mut Dynamic],
) -> RhaiResult {
let name = fn_name.as_ref();
let native_only = !is_valid_function_name(name);
#[cfg(not(feature = "no_function"))]
let native_only = native_only && !crate::parser::is_anonymous_fn(name);

_call_fn_raw(
self.engine(),
self.global,
self.caches,
self.scope,
fn_name,
args,
native_only,
is_ref_mut,
is_method_call,
)
}
/// Call a registered native Rust function inside the [evaluation context][`EvalContext`].
///
/// This is often useful because Rust functions typically only want to cross-call other
/// registered Rust functions and not have to worry about scripted functions hijacking the
/// process unknowingly (or deliberately).
///
/// # WARNING - Low Level API
///
/// This function is very low level.
///
/// # Arguments
///
/// All arguments may be _consumed_, meaning that they may be replaced by `()`. This is to avoid
/// unnecessarily cloning the arguments.
///
/// **DO NOT** reuse the arguments after this call. If they are needed afterwards, clone them
/// _before_ calling this function.
///
/// If `is_ref_mut` is [`true`], the first argument is assumed to be passed by reference and is
/// not consumed.
#[inline(always)]
pub fn call_native_fn_raw(
&mut self,
fn_name: impl AsRef<str>,
is_ref_mut: bool,
args: &mut [&mut Dynamic],
) -> RhaiResult {
_call_fn_raw(
self.engine(),
self.global,
self.caches,
self.scope,
fn_name,
args,
true,
is_ref_mut,
false,
)
}
}

/// Call a function (native Rust or scripted) inside the [evaluation context][`EvalContext`].
fn _call_fn_raw(
engine: &Engine,
global: &mut GlobalRuntimeState,
caches: &mut Caches,
scope: &mut Scope,
fn_name: impl AsRef<str>,
args: &mut [&mut Dynamic],
native_only: bool,
is_ref_mut: bool,
is_method_call: bool,
) -> RhaiResult {
defer! { let orig_level = global.level; global.level += 1 }

let fn_name = fn_name.as_ref();
let op_token = Token::lookup_symbol_from_syntax(fn_name);
let op_token = op_token.as_ref();
let args_len = args.len();

if native_only {
let hash = calc_fn_hash(None, fn_name, args_len);

return engine
.exec_native_fn_call(
global,
caches,
fn_name,
op_token,
hash,
args,
is_ref_mut,
false,
Position::NONE,
)
.map(|(r, ..)| r);
}

// Native or script

let hash = match is_method_call {
#[cfg(not(feature = "no_function"))]
true => FnCallHashes::from_script_and_native(
calc_fn_hash(None, fn_name, args_len - 1),
calc_fn_hash(None, fn_name, args_len),
),
#[cfg(feature = "no_function")]
true => FnCallHashes::from_native_only(calc_fn_hash(None, fn_name, args_len)),
_ => FnCallHashes::from_hash(calc_fn_hash(None, fn_name, args_len)),
};

engine
.exec_fn_call(
global,
caches,
Some(scope),
fn_name,
op_token,
hash,
args,
is_ref_mut,
is_method_call,
Position::NONE,
)
.map(|(r, ..)| r)
}
2 changes: 1 addition & 1 deletion src/func/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -930,7 +930,7 @@ impl Engine {

// Check if it is a map method call in OOP style
#[cfg(not(feature = "no_object"))]
if let Some(map) = target.as_ref().read_lock::<crate::Map>() {
if let Ok(map) = target.as_ref().as_map_ref() {
if let Some(val) = map.get(fn_name) {
if let Some(fn_ptr) = val.read_lock::<FnPtr>() {
// Remap the function name
Expand Down
8 changes: 4 additions & 4 deletions src/func/native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ impl<'a> NativeCallContext<'a> {
ERR::ErrorMismatchOutputType(
cast_type.into(),
result_type.into(),
Position::NONE,
self.position(),
)
.into()
})
Expand Down Expand Up @@ -345,7 +345,7 @@ impl<'a> NativeCallContext<'a> {
ERR::ErrorMismatchOutputType(
cast_type.into(),
result_type.into(),
Position::NONE,
self.position(),
)
.into()
})
Expand Down Expand Up @@ -445,7 +445,7 @@ impl<'a> NativeCallContext<'a> {
args,
is_ref_mut,
false,
Position::NONE,
self.position(),
)
.map(|(r, ..)| r);
}
Expand Down Expand Up @@ -474,7 +474,7 @@ impl<'a> NativeCallContext<'a> {
args,
is_ref_mut,
is_method_call,
Position::NONE,
self.position(),
)
.map(|(r, ..)| r)
}
Expand Down
2 changes: 1 addition & 1 deletion tests/fn_ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ fn test_fn_ptr_embed() {
panic!();
}
let y = args[1].as_int().unwrap();
let map = &mut *args[0].write_lock::<rhai::Map>().unwrap();
let map = &mut *args[0].as_map_mut().unwrap();
let x = &mut *map.get_mut("a").unwrap().write_lock::<INT>().unwrap();
*x += y;
Ok(Dynamic::UNIT)
Expand Down

0 comments on commit dc861c4

Please # to comment.