Skip to content

Commit dc861c4

Browse files
authored
Merge pull request #954 from schungx/master
Add EvalContext::call_fn_XXX API
2 parents 23964c8 + d7fafc0 commit dc861c4

File tree

5 files changed

+257
-8
lines changed

5 files changed

+257
-8
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ Bug fixes
1414
New Features
1515
------------
1616

17-
* 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.
17+
* 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)).
1818

1919
Enhancements
2020
------------
2121

22+
* 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)).
2223
* 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)).
2324

2425

src/eval/eval_context.rs

Lines changed: 249 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
//! Evaluation context.
22
33
use super::{Caches, GlobalRuntimeState};
4-
use crate::{expose_under_internals, Dynamic, Engine, Scope};
4+
use crate::ast::FnCallHashes;
5+
use crate::tokenizer::{is_valid_function_name, Token};
6+
use crate::types::dynamic::Variant;
7+
use crate::{
8+
calc_fn_hash, expose_under_internals, Dynamic, Engine, FnArgsVec, FuncArgs, Position,
9+
RhaiResult, RhaiResultOf, Scope, StaticVec, ERR,
10+
};
11+
use std::any::type_name;
512
#[cfg(feature = "no_std")]
613
use std::prelude::v1::*;
714

@@ -184,4 +191,245 @@ impl<'a, 's, 'ps, 'g, 'c, 't> EvalContext<'a, 's, 'ps, 'g, 'c, 't> {
184191
.eval_expr(self.global, self.caches, self.scope, this_ptr, expr),
185192
}
186193
}
194+
195+
/// Call a function inside the [evaluation context][`EvalContext`] with the provided arguments.
196+
pub fn call_fn<T: Variant + Clone>(
197+
&mut self,
198+
fn_name: impl AsRef<str>,
199+
args: impl FuncArgs,
200+
) -> RhaiResultOf<T> {
201+
let engine = self.engine();
202+
203+
let mut arg_values = StaticVec::new_const();
204+
args.parse(&mut arg_values);
205+
206+
let args = &mut arg_values.iter_mut().collect::<FnArgsVec<_>>();
207+
208+
let is_ref_mut = if let Some(this_ptr) = self.this_ptr.as_deref_mut() {
209+
args.insert(0, this_ptr);
210+
true
211+
} else {
212+
false
213+
};
214+
215+
_call_fn_raw(
216+
engine,
217+
self.global,
218+
self.caches,
219+
self.scope,
220+
fn_name,
221+
args,
222+
false,
223+
is_ref_mut,
224+
false,
225+
)
226+
.and_then(|result| {
227+
result.try_cast_result().map_err(|r| {
228+
let result_type = engine.map_type_name(r.type_name());
229+
let cast_type = match type_name::<T>() {
230+
typ if typ.contains("::") => engine.map_type_name(typ),
231+
typ => typ,
232+
};
233+
ERR::ErrorMismatchOutputType(cast_type.into(), result_type.into(), Position::NONE)
234+
.into()
235+
})
236+
})
237+
}
238+
/// Call a registered native Rust function inside the [evaluation context][`EvalContext`] with
239+
/// the provided arguments.
240+
///
241+
/// This is often useful because Rust functions typically only want to cross-call other
242+
/// registered Rust functions and not have to worry about scripted functions hijacking the
243+
/// process unknowingly (or deliberately).
244+
pub fn call_native_fn<T: Variant + Clone>(
245+
&mut self,
246+
fn_name: impl AsRef<str>,
247+
args: impl FuncArgs,
248+
) -> RhaiResultOf<T> {
249+
let engine = self.engine();
250+
251+
let mut arg_values = StaticVec::new_const();
252+
args.parse(&mut arg_values);
253+
254+
let args = &mut arg_values.iter_mut().collect::<FnArgsVec<_>>();
255+
256+
let is_ref_mut = if let Some(this_ptr) = self.this_ptr.as_deref_mut() {
257+
args.insert(0, this_ptr);
258+
true
259+
} else {
260+
false
261+
};
262+
263+
_call_fn_raw(
264+
engine,
265+
self.global,
266+
self.caches,
267+
self.scope,
268+
fn_name,
269+
args,
270+
true,
271+
is_ref_mut,
272+
false,
273+
)
274+
.and_then(|result| {
275+
result.try_cast_result().map_err(|r| {
276+
let result_type = engine.map_type_name(r.type_name());
277+
let cast_type = match type_name::<T>() {
278+
typ if typ.contains("::") => engine.map_type_name(typ),
279+
typ => typ,
280+
};
281+
ERR::ErrorMismatchOutputType(cast_type.into(), result_type.into(), Position::NONE)
282+
.into()
283+
})
284+
})
285+
}
286+
/// Call a function (native Rust or scripted) inside the [evaluation context][`EvalContext`].
287+
///
288+
/// If `is_method_call` is [`true`], the first argument is assumed to be the `this` pointer for
289+
/// a script-defined function (or the object of a method call).
290+
///
291+
/// # WARNING - Low Level API
292+
///
293+
/// This function is very low level.
294+
///
295+
/// # Arguments
296+
///
297+
/// All arguments may be _consumed_, meaning that they may be replaced by `()`. This is to avoid
298+
/// unnecessarily cloning the arguments.
299+
///
300+
/// **DO NOT** reuse the arguments after this call. If they are needed afterwards, clone them
301+
/// _before_ calling this function.
302+
///
303+
/// If `is_ref_mut` is [`true`], the first argument is assumed to be passed by reference and is
304+
/// not consumed.
305+
#[inline(always)]
306+
pub fn call_fn_raw(
307+
&mut self,
308+
fn_name: impl AsRef<str>,
309+
is_ref_mut: bool,
310+
is_method_call: bool,
311+
args: &mut [&mut Dynamic],
312+
) -> RhaiResult {
313+
let name = fn_name.as_ref();
314+
let native_only = !is_valid_function_name(name);
315+
#[cfg(not(feature = "no_function"))]
316+
let native_only = native_only && !crate::parser::is_anonymous_fn(name);
317+
318+
_call_fn_raw(
319+
self.engine(),
320+
self.global,
321+
self.caches,
322+
self.scope,
323+
fn_name,
324+
args,
325+
native_only,
326+
is_ref_mut,
327+
is_method_call,
328+
)
329+
}
330+
/// Call a registered native Rust function inside the [evaluation context][`EvalContext`].
331+
///
332+
/// This is often useful because Rust functions typically only want to cross-call other
333+
/// registered Rust functions and not have to worry about scripted functions hijacking the
334+
/// process unknowingly (or deliberately).
335+
///
336+
/// # WARNING - Low Level API
337+
///
338+
/// This function is very low level.
339+
///
340+
/// # Arguments
341+
///
342+
/// All arguments may be _consumed_, meaning that they may be replaced by `()`. This is to avoid
343+
/// unnecessarily cloning the arguments.
344+
///
345+
/// **DO NOT** reuse the arguments after this call. If they are needed afterwards, clone them
346+
/// _before_ calling this function.
347+
///
348+
/// If `is_ref_mut` is [`true`], the first argument is assumed to be passed by reference and is
349+
/// not consumed.
350+
#[inline(always)]
351+
pub fn call_native_fn_raw(
352+
&mut self,
353+
fn_name: impl AsRef<str>,
354+
is_ref_mut: bool,
355+
args: &mut [&mut Dynamic],
356+
) -> RhaiResult {
357+
_call_fn_raw(
358+
self.engine(),
359+
self.global,
360+
self.caches,
361+
self.scope,
362+
fn_name,
363+
args,
364+
true,
365+
is_ref_mut,
366+
false,
367+
)
368+
}
369+
}
370+
371+
/// Call a function (native Rust or scripted) inside the [evaluation context][`EvalContext`].
372+
fn _call_fn_raw(
373+
engine: &Engine,
374+
global: &mut GlobalRuntimeState,
375+
caches: &mut Caches,
376+
scope: &mut Scope,
377+
fn_name: impl AsRef<str>,
378+
args: &mut [&mut Dynamic],
379+
native_only: bool,
380+
is_ref_mut: bool,
381+
is_method_call: bool,
382+
) -> RhaiResult {
383+
defer! { let orig_level = global.level; global.level += 1 }
384+
385+
let fn_name = fn_name.as_ref();
386+
let op_token = Token::lookup_symbol_from_syntax(fn_name);
387+
let op_token = op_token.as_ref();
388+
let args_len = args.len();
389+
390+
if native_only {
391+
let hash = calc_fn_hash(None, fn_name, args_len);
392+
393+
return engine
394+
.exec_native_fn_call(
395+
global,
396+
caches,
397+
fn_name,
398+
op_token,
399+
hash,
400+
args,
401+
is_ref_mut,
402+
false,
403+
Position::NONE,
404+
)
405+
.map(|(r, ..)| r);
406+
}
407+
408+
// Native or script
409+
410+
let hash = match is_method_call {
411+
#[cfg(not(feature = "no_function"))]
412+
true => FnCallHashes::from_script_and_native(
413+
calc_fn_hash(None, fn_name, args_len - 1),
414+
calc_fn_hash(None, fn_name, args_len),
415+
),
416+
#[cfg(feature = "no_function")]
417+
true => FnCallHashes::from_native_only(calc_fn_hash(None, fn_name, args_len)),
418+
_ => FnCallHashes::from_hash(calc_fn_hash(None, fn_name, args_len)),
419+
};
420+
421+
engine
422+
.exec_fn_call(
423+
global,
424+
caches,
425+
Some(scope),
426+
fn_name,
427+
op_token,
428+
hash,
429+
args,
430+
is_ref_mut,
431+
is_method_call,
432+
Position::NONE,
433+
)
434+
.map(|(r, ..)| r)
187435
}

src/func/call.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -930,7 +930,7 @@ impl Engine {
930930

931931
// Check if it is a map method call in OOP style
932932
#[cfg(not(feature = "no_object"))]
933-
if let Some(map) = target.as_ref().read_lock::<crate::Map>() {
933+
if let Ok(map) = target.as_ref().as_map_ref() {
934934
if let Some(val) = map.get(fn_name) {
935935
if let Some(fn_ptr) = val.read_lock::<FnPtr>() {
936936
// Remap the function name

src/func/native.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ impl<'a> NativeCallContext<'a> {
312312
ERR::ErrorMismatchOutputType(
313313
cast_type.into(),
314314
result_type.into(),
315-
Position::NONE,
315+
self.position(),
316316
)
317317
.into()
318318
})
@@ -345,7 +345,7 @@ impl<'a> NativeCallContext<'a> {
345345
ERR::ErrorMismatchOutputType(
346346
cast_type.into(),
347347
result_type.into(),
348-
Position::NONE,
348+
self.position(),
349349
)
350350
.into()
351351
})
@@ -445,7 +445,7 @@ impl<'a> NativeCallContext<'a> {
445445
args,
446446
is_ref_mut,
447447
false,
448-
Position::NONE,
448+
self.position(),
449449
)
450450
.map(|(r, ..)| r);
451451
}
@@ -474,7 +474,7 @@ impl<'a> NativeCallContext<'a> {
474474
args,
475475
is_ref_mut,
476476
is_method_call,
477-
Position::NONE,
477+
self.position(),
478478
)
479479
.map(|(r, ..)| r)
480480
}

tests/fn_ptr.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ fn test_fn_ptr_embed() {
213213
panic!();
214214
}
215215
let y = args[1].as_int().unwrap();
216-
let map = &mut *args[0].write_lock::<rhai::Map>().unwrap();
216+
let map = &mut *args[0].as_map_mut().unwrap();
217217
let x = &mut *map.get_mut("a").unwrap().write_lock::<INT>().unwrap();
218218
*x += y;
219219
Ok(Dynamic::UNIT)

0 commit comments

Comments
 (0)