Implement function pointers.

This commit is contained in:
Stephen Chung 2020-06-25 18:07:57 +08:00
parent aeb47efce8
commit fe640e0e13
18 changed files with 269 additions and 31 deletions

View File

@ -9,6 +9,11 @@ Breaking changes
* The trait function `ModuleResolver::resolve` no longer takes a `Scope` as argument.
New features
------------
* Support for _function pointers_ via `Fn(name)` and `Fn.call(...)` syntax - a poor man's first-class function.
Enhancements
------------

View File

@ -71,6 +71,7 @@ The Rhai Scripting Language
14. [Functions](language/functions.md)
1. [Function Overloading](language/overload.md)
2. [Call Method as Function](language/method.md)
3. [Function Pointers](language/fn-ptr.md)
15. [Print and Debug](language/print-debug.md)
16. [Modules](language/modules/index.md)
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)

View File

@ -35,6 +35,8 @@ Dynamic
* Organize code base with dynamically-loadable [modules].
* [Function pointers].
Safe
----

View File

@ -11,10 +11,13 @@ It doesn't attempt to be a new language. For example:
* No traits... so it is also not Rust. Do your Rusty stuff in Rust.
* No structures/records - define your types in Rust instead; Rhai can seamlessly work with _any Rust type_.
There is, however, a built-in [object map] type which is adequate for most uses.
* No first-class functions - Code your functions in Rust instead, and register them with Rhai.
There is, however, support for simple [function pointers] allowing runtime dispatch by function name.
* No garbage collection - this should be expected, so...
* No closures - do your closure magic in Rust instead; [turn a Rhai scripted function into a Rust closure]({{rootUrl}}/engine/call-fn.md).

View File

@ -27,6 +27,8 @@ if type_of(mystery) == "i64" {
print("Hey, I got an array here!");
} else if type_of(mystery) == "map" {
print("Hey, I got an object map here!");
} else if type_of(mystery) == "Fn" {
print("Hey, I got a function pointer here!");
} else if type_of(mystery) == "TestStruct" {
print("Hey, I got the TestStruct custom type here!");
} else {

View File

@ -28,7 +28,7 @@ eval("{ let z = y }"); // to keep a variable local, use a statement blo
print("z = " + z); // <- error: variable 'z' not found
"print(42)".eval(); // <- nope... method-call style doesn't work
"print(42)".eval(); // <- nope... method-call style doesn't work with 'eval'
```
Script segments passed to `eval` execute inside the current [`Scope`], so they can access and modify _everything_,

View File

@ -0,0 +1,56 @@
Function Pointers
=================
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.
Call a function pointer using the `call` method, which needs to be called in method-call style.
Built-in Functions
------------------
The following standard methods (mostly defined in the [`BasicFnPackage`]({{rootUrl}}/rust/packages.md) but excluded if
using a [raw `Engine`]) operate on [strings]:
| Function | Parameter(s) | Description |
| -------------------------- | ------------ | --------------------------------------------------------------------- |
| `name` method and property | _none_ | returns the name of the function encapsulated by the function pointer |
Examples
--------
```rust
fn foo(x) { 41 + x }
let func = Fn("foo"); // use the 'Fn' function to create a function pointer
print(func); // prints 'Fn(foo)'
let func = fn_name.Fn(); // <- error: 'Fn' cannot be called in method-call style
func.type_of() == "Fn"; // type_of() as function pointer is 'Fn'
func.name == "foo";
func.call(1) == 42; // call a function pointer with the 'call' method
foo(1) == 42; // <- the above de-sugars to this
call(func, 1); //<- error: 'call (Fn, i64)' is not a registered function
let len = Fn("len"); // 'Fn' also works with registered native Rust functions
len.call("hello") == 5;
let add = Fn("+"); // 'Fn' works with built-in operators also
add.call(40, 2) == 42;
let fn_name = "hello"; // the function name does not have to exist yet
let hello = Fn(fn_name + "_world");
hello.call(0); // error: function not found - "hello_world (i64)"
```

View File

@ -5,20 +5,21 @@ Values and Types
The following primitive types are supported natively:
| Category | Equivalent Rust types | [`type_of()`] | `to_string()` |
| --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------- | --------------------- |
| **Integer number** | `u8`, `i8`, `u16`, `i16`, <br/>`u32`, `i32` (default for [`only_i32`]),<br/>`u64`, `i64` _(default)_ | `"i32"`, `"u64"` etc. | `"42"`, `"123"` etc. |
| **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. |
| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` |
| **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. |
| **Immutable Unicode string** | `rhai::ImmutableString` (implemented as `Rc<String>` or `Arc<String>`) | `"string"` | `"hello"` etc. |
| **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` |
| **Object map** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `#{ "a": 1, "b": 2 }` |
| **Timestamp** (implemented in the [`BasicTimePackage`]({{rootUrl}}/rust/packages.md), disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`](https://crates.io/crates/instant) if not [WASM] build) | `"timestamp"` | _not supported_ |
| **Dynamic value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ |
| **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. |
| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. |
| **Nothing/void/nil/null/Unit** (or whatever it is called) | `()` | `"()"` | `""` _(empty string)_ |
| Category | Equivalent Rust types | [`type_of()`] | `to_string()` |
| ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------- | ----------------------- |
| **Integer number** | `u8`, `i8`, `u16`, `i16`, <br/>`u32`, `i32` (default for [`only_i32`]),<br/>`u64`, `i64` _(default)_ | `"i32"`, `"u64"` etc. | `"42"`, `"123"` etc. |
| **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. |
| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` |
| **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. |
| **Immutable Unicode [string]** | `rhai::ImmutableString` (implemented as `Rc<String>` or `Arc<String>`) | `"string"` | `"hello"` etc. |
| **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` |
| **[Object map]** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `"#{ "a": 1, "b": 2 }"` |
| **[Timestamp]** (implemented in the [`BasicTimePackage`]({{rootUrl}}/rust/packages.md), disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`](https://crates.io/crates/instant) if not [WASM] build) | `"timestamp"` | _not supported_ |
| **[Function pointer]** (disabled with [`no_function`]) | _None_ | `Fn` | `"Fn(foo)"` |
| **[`Dynamic`] value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ |
| **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. |
| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. |
| **Nothing/void/nil/null/Unit** (or whatever it is called) | `()` | `"()"` | `""` _(empty string)_ |
All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different -
they even cannot be added together. This is very similar to Rust.

View File

@ -65,6 +65,8 @@
[function]: {{rootUrl}}/language/functions.md
[functions]: {{rootUrl}}/language/functions.md
[function pointer]: {{rootUrl}}/language/fn-ptr.md
[function pointers]: {{rootUrl}}/language/fn-ptr.md
[`Module`]: {{rootUrl}}/language/modules/index.md
[module]: {{rootUrl}}/language/modules/index.md

View File

@ -18,6 +18,7 @@ Built-In Packages
| `BasicMathPackage` | Basic math functions (e.g. `sin`, `sqrt`) | No | Yes |
| `BasicArrayPackage` | Basic [array] functions (not available under `no_index`) | No | Yes |
| `BasicMapPackage` | Basic [object map] functions (not available under `no_object`) | No | Yes |
| `BasicFnPackage` | Basic methods for [function pointers]. | Yes | Yes |
| `EvalPackage` | Disable [`eval`] | No | No |
| `CorePackage` | Basic essentials | Yes | Yes |
| `StandardPackage` | Standard library (default for `Engine::new`) | No | Yes |

View File

@ -1,6 +1,6 @@
//! Helper module which defines the `Any` trait to to allow dynamic value handling.
use crate::fn_native::SendSync;
use crate::fn_native::{FnPtr, SendSync};
use crate::module::Module;
use crate::parser::{ImmutableString, INT};
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
@ -139,6 +139,8 @@ pub enum Union {
#[cfg(not(feature = "no_object"))]
Map(Box<Map>),
Module(Box<Module>),
#[cfg(not(feature = "no_function"))]
FnPtr(FnPtr),
Variant(Box<Box<dyn Variant>>),
}
@ -176,6 +178,8 @@ impl Dynamic {
#[cfg(not(feature = "no_object"))]
Union::Map(_) => TypeId::of::<Map>(),
Union::Module(_) => TypeId::of::<Module>(),
#[cfg(not(feature = "no_function"))]
Union::FnPtr(_) => TypeId::of::<FnPtr>(),
Union::Variant(value) => (***value).type_id(),
}
}
@ -195,6 +199,8 @@ impl Dynamic {
#[cfg(not(feature = "no_object"))]
Union::Map(_) => "map",
Union::Module(_) => "sub-scope",
#[cfg(not(feature = "no_function"))]
Union::FnPtr(_) => "Fn",
#[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
@ -219,6 +225,8 @@ impl fmt::Display for Dynamic {
#[cfg(not(feature = "no_object"))]
Union::Map(value) => write!(f, "#{:?}", value),
Union::Module(value) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_function"))]
Union::FnPtr(value) => fmt::Display::fmt(value, f),
#[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
@ -243,6 +251,8 @@ impl fmt::Debug for Dynamic {
#[cfg(not(feature = "no_object"))]
Union::Map(value) => write!(f, "#{:?}", value),
Union::Module(value) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_function"))]
Union::FnPtr(value) => fmt::Display::fmt(value, f),
#[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))]
@ -267,6 +277,8 @@ impl Clone for Dynamic {
#[cfg(not(feature = "no_object"))]
Union::Map(ref value) => Self(Union::Map(value.clone())),
Union::Module(ref value) => Self(Union::Module(value.clone())),
#[cfg(not(feature = "no_function"))]
Union::FnPtr(ref value) => Self(Union::FnPtr(value.clone())),
Union::Variant(ref value) => (***value).clone_into_dynamic(),
}
}
@ -396,6 +408,8 @@ impl Dynamic {
#[cfg(not(feature = "no_object"))]
Union::Map(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
Union::Module(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
#[cfg(not(feature = "no_function"))]
Union::FnPtr(value) => unsafe_try_cast(value),
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(),
}
}
@ -439,6 +453,8 @@ impl Dynamic {
#[cfg(not(feature = "no_object"))]
Union::Map(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
Union::Module(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
#[cfg(not(feature = "no_function"))]
Union::FnPtr(value) => unsafe_try_cast(value).unwrap(),
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).unwrap(),
}
}
@ -454,8 +470,7 @@ impl Dynamic {
match &self.0 {
Union::Unit(value) => <dyn Any>::downcast_ref::<T>(value),
Union::Bool(value) => <dyn Any>::downcast_ref::<T>(value),
Union::Str(value) => (value as &dyn Any)
.downcast_ref::<T>()
Union::Str(value) => <dyn Any>::downcast_ref::<T>(value)
.or_else(|| <dyn Any>::downcast_ref::<T>(value.as_ref())),
Union::Char(value) => <dyn Any>::downcast_ref::<T>(value),
Union::Int(value) => <dyn Any>::downcast_ref::<T>(value),
@ -466,6 +481,8 @@ impl Dynamic {
#[cfg(not(feature = "no_object"))]
Union::Map(value) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
Union::Module(value) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
#[cfg(not(feature = "no_function"))]
Union::FnPtr(value) => <dyn Any>::downcast_ref::<T>(value),
Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::<T>(),
}
}
@ -491,6 +508,8 @@ impl Dynamic {
#[cfg(not(feature = "no_object"))]
Union::Map(value) => <dyn Any>::downcast_mut::<T>(value.as_mut()),
Union::Module(value) => <dyn Any>::downcast_mut::<T>(value.as_mut()),
#[cfg(not(feature = "no_function"))]
Union::FnPtr(value) => <dyn Any>::downcast_mut::<T>(value),
Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::<T>(),
}
}
@ -556,6 +575,15 @@ impl Dynamic {
_ => Err(self.type_name()),
}
}
/// Cast the `Dynamic` as a `FnPtr` and return the function name.
/// Returns the name of the actual type if the cast fails.
pub(crate) fn as_fn_name(&self) -> Result<&str, &'static str> {
match &self.0 {
Union::FnPtr(f) => Ok(f.fn_name()),
_ => Err(self.type_name()),
}
}
}
impl From<()> for Dynamic {
@ -616,6 +644,12 @@ impl<K: Into<ImmutableString>, T: Variant + Clone> From<HashMap<K, T>> for Dynam
)))
}
}
#[cfg(not(feature = "no_function"))]
impl From<FnPtr> for Dynamic {
fn from(value: FnPtr) -> Self {
Self(Union::FnPtr(value))
}
}
/// Private type which ensures that `rhai::Any` and `rhai::AnyExt` can only
/// be implemented by this crate.

View File

@ -3,7 +3,8 @@
use crate::any::{Dynamic, Union, Variant};
use crate::calc_fn_hash;
use crate::error::ParseErrorType;
use crate::fn_native::{CallableFunction, Callback, FnCallArgs};
use crate::fn_native::{CallableFunction, Callback, FnCallArgs, FnPtr};
use crate::fn_register::RegisterResultFn;
use crate::module::{resolvers, Module, ModuleResolver};
use crate::optimize::OptimizationLevel;
use crate::packages::{Package, PackageLibrary, PackagesCollection, StandardPackage};
@ -11,7 +12,7 @@ use crate::parser::{Expr, FnAccess, ImmutableString, ReturnType, ScriptFnDef, St
use crate::r#unsafe::{unsafe_cast_var_name_to_lifetime, unsafe_mut_cast_to_lifetime};
use crate::result::EvalAltResult;
use crate::scope::{EntryType as ScopeEntryType, Scope};
use crate::token::Position;
use crate::token::{Position, Token};
use crate::utils::StaticVec;
#[cfg(not(feature = "no_float"))]
@ -71,11 +72,13 @@ pub const KEYWORD_PRINT: &str = "print";
pub const KEYWORD_DEBUG: &str = "debug";
pub const KEYWORD_TYPE_OF: &str = "type_of";
pub const KEYWORD_EVAL: &str = "eval";
pub const KEYWORD_FN_PTR_CALL: &str = "call";
pub const FN_TO_STRING: &str = "to_string";
pub const FN_GET: &str = "get$";
pub const FN_SET: &str = "set$";
pub const FN_IDX_GET: &str = "$index$get$";
pub const FN_IDX_SET: &str = "$index$set$";
pub const FN_FN_PTR: &str = "Fn";
/// A type specifying the method of chaining.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
@ -954,6 +957,14 @@ impl Engine {
false,
)),
// Fn
FN_FN_PTR if args.len() == 1 && !self.has_override(lib, hashes) => {
Err(Box::new(EvalAltResult::ErrorRuntime(
"'Fn' should not be called in method style. Try Fn(...);".into(),
Position::none(),
)))
}
// eval - reaching this point it must be a method-style call
KEYWORD_EVAL if args.len() == 1 && !self.has_override(lib, hashes) => {
Err(Box::new(EvalAltResult::ErrorRuntime(
@ -1124,18 +1135,31 @@ impl Engine {
// Get a reference to the mutation target Dynamic
let (result, updated) = {
let obj = target.as_mut();
let mut arg_values: StaticVec<_> = once(obj)
.chain(
idx_val
.downcast_mut::<StaticVec<Dynamic>>()
.unwrap()
.iter_mut(),
)
.collect();
let idx = idx_val.downcast_mut::<StaticVec<Dynamic>>().unwrap();
// Check if it is a FnPtr call
let (fn_name, mut arg_values, hash_fn, is_native) =
if name == KEYWORD_FN_PTR_CALL && obj.is::<FnPtr>() {
// Redirect function name
let name = obj.as_fn_name().unwrap();
// Recalculate hash
let hash = calc_fn_hash(empty(), name, idx.len(), empty());
// Arguments are passed as-is
(name, idx.iter_mut().collect::<StaticVec<_>>(), hash, false)
} else {
// Attached object pointer in front of the arguments
(
name.as_ref(),
once(obj).chain(idx.iter_mut()).collect::<StaticVec<_>>(),
*hash,
*native,
)
};
let args = arg_values.as_mut();
self.exec_fn_call(
state, lib, name, *native, *hash, args, is_ref, def_val, 0,
state, lib, fn_name, is_native, hash_fn, args, is_ref, def_val, 0,
)
.map_err(|err| EvalAltResult::new_position(err, *pos))?
};
@ -1697,6 +1721,27 @@ impl Engine {
let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref();
let def_val = def_val.as_ref();
// Handle Fn
if name == FN_FN_PTR && args_expr.len() == 1 {
let hash_fn =
calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
if !self.has_override(lib, (hash_fn, *hash)) {
// Fn - only in function call style
let expr = args_expr.get(0);
let arg_value = self.eval_expr(scope, state, lib, expr, level)?;
return arg_value
.take_immutable_string()
.map(|s| FnPtr::from(s).into())
.map_err(|type_name| {
Box::new(EvalAltResult::ErrorMismatchOutputType(
type_name.into(),
Position::none(),
))
});
}
}
// Handle eval
if name == KEYWORD_EVAL && args_expr.len() == 1 {
let hash_fn =

View File

@ -2,6 +2,7 @@ use crate::any::Dynamic;
use crate::engine::Engine;
use crate::parser::ScriptFnDef;
use crate::result::EvalAltResult;
use crate::utils::ImmutableString;
use crate::stdlib::{boxed::Box, fmt, rc::Rc, sync::Arc};
@ -53,8 +54,37 @@ pub fn shared_take<T: Clone>(value: Shared<T>) -> T {
pub type FnCallArgs<'a> = [&'a mut Dynamic];
/// A general function pointer.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
pub struct FnPtr(ImmutableString);
impl FnPtr {
/// Get the name of the function.
pub fn fn_name(&self) -> &str {
self.get_fn_name().as_ref()
}
/// Get the name of the function.
pub(crate) fn get_fn_name(&self) -> &ImmutableString {
&self.0
}
}
impl fmt::Display for FnPtr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Fn({})", self.0)
}
}
impl<S: Into<ImmutableString>> From<S> for FnPtr {
fn from(value: S) -> Self {
Self(value.into())
}
}
/// A general function trail object.
#[cfg(not(feature = "sync"))]
pub type FnAny = dyn Fn(&Engine, &mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>>;
/// A general function trail object.
#[cfg(feature = "sync")]
pub type FnAny =
dyn Fn(&Engine, &mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync;

8
src/packages/fn_basic.rs Normal file
View File

@ -0,0 +1,8 @@
use crate::def_package;
use crate::fn_native::FnPtr;
def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, {
lib.set_fn_1_mut("name", |f: &mut FnPtr| Ok(f.get_fn_name().clone()));
lib.set_getter_fn("name", |f: &mut FnPtr| Ok(f.get_fn_name().clone()));
});

View File

@ -9,6 +9,7 @@ use crate::stdlib::any::TypeId;
pub(crate) mod arithmetic;
mod array_basic;
mod eval;
mod fn_basic;
mod iter_basic;
mod logic;
mod map_basic;
@ -23,6 +24,8 @@ pub use arithmetic::ArithmeticPackage;
#[cfg(not(feature = "no_index"))]
pub use array_basic::BasicArrayPackage;
pub use eval::EvalPackage;
#[cfg(not(feature = "no_function"))]
pub use fn_basic::BasicFnPackage;
pub use iter_basic::BasicIteratorPackage;
pub use logic::LogicPackage;
#[cfg(not(feature = "no_object"))]

View File

@ -1,4 +1,5 @@
use super::arithmetic::ArithmeticPackage;
use super::fn_basic::BasicFnPackage;
use super::iter_basic::BasicIteratorPackage;
use super::logic::LogicPackage;
use super::string_basic::BasicStringPackage;
@ -10,4 +11,5 @@ def_package!(crate:CorePackage:"_Core_ package containing basic facilities.", li
LogicPackage::init(lib);
BasicStringPackage::init(lib);
BasicIteratorPackage::init(lib);
BasicFnPackage::init(lib);
});

View File

@ -1,5 +1,6 @@
use crate::def_package;
use crate::engine::{FN_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT};
use crate::fn_native::FnPtr;
use crate::module::FuncReturn;
use crate::parser::{ImmutableString, INT};
@ -34,8 +35,9 @@ macro_rules! reg_op {
}
def_package!(crate:BasicStringPackage:"Basic string utilities, including printing.", lib, {
reg_op!(lib, KEYWORD_PRINT, to_string, INT, bool, char);
reg_op!(lib, FN_TO_STRING, to_string, INT, bool, char);
reg_op!(lib, KEYWORD_PRINT, to_string, INT, bool, char, FnPtr);
reg_op!(lib, FN_TO_STRING, to_string, INT, bool, char, FnPtr);
lib.set_fn_1_mut(KEYWORD_DEBUG, |f: &mut FnPtr| Ok(f.to_string()));
lib.set_fn_0(KEYWORD_PRINT, || Ok("".to_string()));
lib.set_fn_1(KEYWORD_PRINT, |_: ()| Ok("".to_string()));

View File

@ -45,3 +45,44 @@ fn test_functions() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[test]
fn test_function_pointers() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert_eq!(engine.eval::<String>(r#"type_of(Fn("abc"))"#)?, "Fn");
assert_eq!(
engine.eval::<INT>(
r#"
fn foo(x) { 40 + x }
let fn_name = "f";
fn_name += "oo";
let f = Fn(fn_name);
f.call(2)
"#
)?,
42
);
assert!(matches!(
*engine.eval::<INT>(r#"let f = Fn("abc"); f.call(0)"#).expect_err("should error"),
EvalAltResult::ErrorFunctionNotFound(f, _) if f.starts_with("abc (")
));
assert_eq!(
engine.eval::<INT>(
r#"
fn foo(x) { 40 + x }
let x = #{ action: Fn("foo") };
x.action.call(2)
"#
)?,
42
);
Ok(())
}