Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EvalContext::call_fn_XXX API #954

Merged
merged 3 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -28,7 +28,7 @@

#[cfg(not(feature = "no_float"))]
#[cfg(feature = "no_std")]
use num_traits::Float;

Check warning on line 31 in src/func/call.rs

View workflow job for this annotation

GitHub Actions / NoStdBuild (windows-latest, --profile windows, true)

unused import: `num_traits::Float`

Check warning on line 31 in src/func/call.rs

View workflow job for this annotation

GitHub Actions / NoStdBuild (ubuntu-latest, --profile unix, false)

unused import: `num_traits::Float`

#[cfg(not(feature = "no_float"))]
use crate::FLOAT;
Expand Down Expand Up @@ -930,7 +930,7 @@

// 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
@@ -1,4 +1,4 @@
use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Scope, INT};

Check warning on line 1 in tests/fn_ptr.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,sync,no_time,no_function,no_float,no_position,no...

unused import: `EvalAltResult`

#[test]
fn test_fn_ptr() {
Expand Down Expand Up @@ -213,7 +213,7 @@
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
Loading