Skip to content

Commit

Permalink
Merge pull request #952 from schungx/master
Browse files Browse the repository at this point in the history
Bind native function to FnPtr
  • Loading branch information
schungx authored Jan 18, 2025
2 parents fc83d6b + 7789ec4 commit 8ed4ea3
Show file tree
Hide file tree
Showing 13 changed files with 420 additions and 186 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ Bug fixes
* `get_fn_metadata_list` function is marked `volatile`.
* `no-std` plus `sync` should now build correctly (thanks [`stargazing-dino`](https://github.com/stargazing-dino) [947](https://github.com/rhaiscript/rhai/pull/947)).

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.

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

Expand Down
6 changes: 3 additions & 3 deletions src/api/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -792,14 +792,14 @@ impl Engine {
#[allow(dead_code)]
pub(crate) fn collect_fn_metadata_impl<T>(
&self,
ctx: Option<&NativeCallContext>,
_ctx: Option<&NativeCallContext>,
mapper: impl Fn(crate::module::FuncInfo) -> Option<T> + Copy,
include_standard_packages: bool,
) -> Vec<T> {
let mut list = Vec::new();

#[cfg(not(feature = "no_function"))]
if let Some(ctx) = ctx {
if let Some(ctx) = _ctx {
ctx.iter_namespaces()
.flat_map(Module::iter_fn)
.filter_map(|(func, f)| {
Expand Down Expand Up @@ -829,7 +829,7 @@ impl Engine {
.for_each(|v| list.push(v));

#[cfg(not(feature = "no_module"))]
if let Some(ctx) = ctx {
if let Some(ctx) = _ctx {
use crate::engine::NAMESPACE_SEPARATOR;
use crate::SmartString;

Expand Down
4 changes: 2 additions & 2 deletions src/eval/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ impl Engine {
let val: Dynamic = crate::FnPtr {
name: v.1.clone(),
curry: <_>::default(),
environ: None,
fn_def: Some(fn_def.clone()),
env: None,
typ: crate::types::fn_ptr::FnPtrType::Script(fn_def.clone()),
}
.into();
return Ok(val.into());
Expand Down
225 changes: 145 additions & 80 deletions src/func/call.rs

Large diffs are not rendered by default.

11 changes: 4 additions & 7 deletions src/func/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ pub enum RhaiFunc {
/// Shared reference to the [`ScriptFuncDef`][crate::ast::ScriptFuncDef] function definition.
fn_def: Shared<crate::ast::ScriptFuncDef>,
/// Encapsulated environment, if any.
environ: Option<Shared<EncapsulatedEnviron>>,
env: Option<Shared<EncapsulatedEnviron>>,
},
}

Expand Down Expand Up @@ -262,7 +262,7 @@ impl RhaiFunc {
| Self::Plugin { .. } => None,

#[cfg(not(feature = "no_function"))]
Self::Script { environ, .. } => environ.as_deref(),
Self::Script { env, .. } => env.as_deref(),
}
}
/// Get a reference to an iterator function.
Expand Down Expand Up @@ -297,7 +297,7 @@ impl From<crate::ast::ScriptFuncDef> for RhaiFunc {
fn from(fn_def: crate::ast::ScriptFuncDef) -> Self {
Self::Script {
fn_def: fn_def.into(),
environ: None,
env: None,
}
}
}
Expand All @@ -306,10 +306,7 @@ impl From<crate::ast::ScriptFuncDef> for RhaiFunc {
impl From<Shared<crate::ast::ScriptFuncDef>> for RhaiFunc {
#[inline(always)]
fn from(fn_def: Shared<crate::ast::ScriptFuncDef>) -> Self {
Self::Script {
fn_def,
environ: None,
}
Self::Script { fn_def, env: None }
}
}

Expand Down
7 changes: 3 additions & 4 deletions src/func/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ impl Engine {
caches: &mut Caches,
scope: &mut Scope,
mut this_ptr: Option<&mut Dynamic>,
_environ: Option<&EncapsulatedEnviron>,
_env: Option<&EncapsulatedEnviron>,
fn_def: &ScriptFuncDef,
args: &mut FnCallArgs,
rewind_scope: bool,
Expand Down Expand Up @@ -94,7 +94,7 @@ impl Engine {
let orig_fn_resolution_caches_len = caches.fn_resolution_caches_len();

#[cfg(not(feature = "no_module"))]
let orig_constants = _environ.map(|environ| {
let orig_constants = _env.map(|environ| {
let EncapsulatedEnviron {
lib,
imports,
Expand Down Expand Up @@ -144,8 +144,7 @@ impl Engine {
_ => Err(ERR::ErrorInFunctionCall(
fn_def.name.to_string(),
#[cfg(not(feature = "no_module"))]
_environ
.and_then(|environ| environ.lib.id())
_env.and_then(|env| env.lib.id())
.unwrap_or_else(|| global.source().unwrap_or(""))
.to_string(),
#[cfg(feature = "no_module")]
Expand Down
26 changes: 12 additions & 14 deletions src/module/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2350,7 +2350,8 @@ impl Module {
let _ = result?;

// Encapsulated environment
let environ = Shared::new(crate::ast::EncapsulatedEnviron {
#[cfg(not(feature = "no_function"))]
let env = Shared::new(crate::ast::EncapsulatedEnviron {
#[cfg(not(feature = "no_function"))]
lib: ast.shared_lib().clone(),
imports,
Expand All @@ -2363,17 +2364,18 @@ impl Module {
while i > 0 {
i -= 1;

let (mut value, mut aliases) = if i >= orig_scope_len {
let (mut _value, mut aliases) = if i >= orig_scope_len {
let (_, v, a) = scope.pop_entry().unwrap();
(v, a)
} else {
let (_, v, a) = scope.get_entry_by_index(i);
(v.clone(), a.to_vec())
};

value.deep_scan(|v| {
#[cfg(not(feature = "no_function"))]
_value.deep_scan(|v| {
if let Some(fn_ptr) = v.downcast_mut::<crate::FnPtr>() {
fn_ptr.environ = Some(environ.clone());
fn_ptr.env = Some(env.clone());
}
});

Expand All @@ -2382,7 +2384,7 @@ impl Module {
1 => {
let alias = aliases.pop().unwrap();
if !module.contains_var(&alias) {
module.set_var(alias, value);
module.set_var(alias, _value);
}
}
_ => {
Expand All @@ -2396,12 +2398,12 @@ impl Module {
if first_alias.is_none() {
first_alias = Some(alias);
} else {
module.set_var(alias, value.clone());
module.set_var(alias, _value.clone());
}
}

if let Some(alias) = first_alias {
module.set_var(alias, value);
module.set_var(alias, _value);
}
}
}
Expand All @@ -2416,15 +2418,11 @@ impl Module {
})
.for_each(|f| {
let hash = module.set_script_fn(f.clone());
if let (
RhaiFunc::Script {
environ: ref mut e, ..
},
_,
) = module.functions.as_mut().unwrap().get_mut(&hash).unwrap()
if let (RhaiFunc::Script { env: ref mut e, .. }, _) =
module.functions.as_mut().unwrap().get_mut(&hash).unwrap()
{
// Encapsulate AST environment
*e = Some(environ.clone());
*e = Some(env.clone());
}
});

Expand Down
5 changes: 3 additions & 2 deletions src/packages/array_basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::api::deprecated::deprecated_array_functions;
use crate::engine::OP_EQUALS;
use crate::eval::{calc_index, calc_offset_len};
use crate::plugin::*;
use crate::types::fn_ptr::FnPtrType;
use crate::{
def_package, Array, Dynamic, ExclusiveRange, FnPtr, InclusiveRange, NativeCallContext,
Position, RhaiResultOf, ERR, INT, MAX_USIZE_INT,
Expand Down Expand Up @@ -1271,9 +1272,9 @@ pub mod array_functions {
let comparer = FnPtr {
name: ctx.engine().get_interned_string(OP_EQUALS),
curry: <_>::default(),
environ: None,
#[cfg(not(feature = "no_function"))]
fn_def: None,
env: None,
typ: FnPtrType::Normal,
};
dedup_by_comparer(ctx, array, comparer);
}
Expand Down
5 changes: 3 additions & 2 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3677,6 +3677,7 @@ impl Engine {
skip_parameters: bool,
) -> ParseResult<Expr> {
// Build new parse state

let new_state = &mut ParseState::new(
state.external_constants,
state.input,
Expand Down Expand Up @@ -3810,9 +3811,9 @@ impl Engine {
let fn_ptr = crate::FnPtr {
name: fn_name,
curry: ThinVec::new(),
environ: None,
#[cfg(not(feature = "no_function"))]
fn_def: Some(fn_def.clone()),
env: None,
typ: crate::types::fn_ptr::FnPtrType::Normal,
};

let expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), new_settings.pos);
Expand Down
4 changes: 2 additions & 2 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ fn check_struct_sizes() {
assert_eq!(size_of::<Scope>(), 24);
assert_eq!(
size_of::<FnPtr>(),
32 - if cfg!(feature = "no_function") {
WORD_SIZE
48 - if cfg!(feature = "no_function") {
2 * WORD_SIZE
} else {
0
}
Expand Down
17 changes: 7 additions & 10 deletions src/types/dynamic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,8 @@ impl Hash for Dynamic {
Union::Blob(ref a, ..) => a.hash(state),
#[cfg(not(feature = "no_object"))]
Union::Map(ref m, ..) => m.hash(state),
Union::FnPtr(ref f, ..) if f.environ.is_some() => {
#[cfg(not(feature = "no_function"))]
Union::FnPtr(ref f, ..) if f.env.is_some() => {
unimplemented!("FnPtr with embedded environment cannot be hashed")
}
Union::FnPtr(ref f, ..) => {
Expand Down Expand Up @@ -789,11 +790,7 @@ impl fmt::Debug for Dynamic {
Union::FnPtr(ref fnptr, ..) => {
dict.insert(value);

f.write_str("Fn")?;
#[cfg(not(feature = "no_function"))]
if fnptr.fn_def.is_some() {
f.write_str("*")?;
}
fmt::Display::fmt(&fnptr.typ, f)?;
f.write_str("(")?;
fmt::Debug::fmt(fnptr.fn_name(), f)?;
for curry in &fnptr.curry {
Expand Down Expand Up @@ -1220,9 +1217,9 @@ impl Dynamic {
Union::Blob(..) => true,
#[cfg(not(feature = "no_object"))]
Union::Map(ref m, ..) => m.values().all(Self::is_hashable),
Union::FnPtr(ref f, ..) => {
f.environ.is_none() && f.curry().iter().all(Self::is_hashable)
}
#[cfg(not(feature = "no_function"))]
Union::FnPtr(ref f, ..) if f.env.is_some() => false,
Union::FnPtr(ref f, ..) => f.curry().iter().all(Self::is_hashable),
#[cfg(not(feature = "no_time"))]
Union::TimeStamp(..) => false,

Expand Down Expand Up @@ -1297,7 +1294,7 @@ impl Dynamic {
#[cfg(not(feature = "no_object"))]
Union::Map(ref m, ..) => m.values().all(|v| checked_is_hashable(v, dict)),
Union::FnPtr(ref f, ..) => {
f.environ.is_none()
f.env.is_none()
&& f.curry().iter().all(|v| checked_is_hashable(v, dict))
}
_ => value.is_hashable(),
Expand Down
Loading

0 comments on commit 8ed4ea3

Please sign in to comment.