Add get_fn_metadata_list.
This commit is contained in:
parent
8d410dfc71
commit
b43223a94f
@ -39,6 +39,7 @@ New features
|
|||||||
* `Module::set_getter_fn`, `Module::set_setter_fn`, `Module::set_indexer_get_fn`, `Module::set_indexer_set_fn` all expose the function to the global namespace by default. This is convenient when registering an API for a custom type.
|
* `Module::set_getter_fn`, `Module::set_setter_fn`, `Module::set_indexer_get_fn`, `Module::set_indexer_set_fn` all expose the function to the global namespace by default. This is convenient when registering an API for a custom type.
|
||||||
* New `Module::update_fn_metadata` to update a module function's parameter names and types.
|
* New `Module::update_fn_metadata` to update a module function's parameter names and types.
|
||||||
* New `#[rhai_fn(global)]` and `#[rhai_fn(internal)]` attributes to determine whether a function defined in a plugin module should be exposed to the global namespace. This is convenient when defining an API for a custom type.
|
* New `#[rhai_fn(global)]` and `#[rhai_fn(internal)]` attributes to determine whether a function defined in a plugin module should be exposed to the global namespace. This is convenient when defining an API for a custom type.
|
||||||
|
* New `get_fn_metadata_list` to get the metadata of all script-defined functions in scope.
|
||||||
|
|
||||||
Enhancements
|
Enhancements
|
||||||
------------
|
------------
|
||||||
|
@ -101,21 +101,6 @@ a statement in the script can freely call a function defined afterwards.
|
|||||||
This is similar to Rust and many other modern languages, such as JavaScript's `function` keyword.
|
This is similar to Rust and many other modern languages, such as JavaScript's `function` keyword.
|
||||||
|
|
||||||
|
|
||||||
`is_def_fn`
|
|
||||||
-----------
|
|
||||||
|
|
||||||
Use `is_def_fn` to detect if a Rhai function is defined (and therefore callable), based on its name
|
|
||||||
and the number of parameters.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
fn foo(x) { x + 1 }
|
|
||||||
|
|
||||||
is_def_fn("foo", 1) == true;
|
|
||||||
|
|
||||||
is_def_fn("bar", 1) == false;
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Arguments are Passed by Value
|
Arguments are Passed by Value
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
@ -159,3 +144,43 @@ x == 42; // 'x' is changed!
|
|||||||
|
|
||||||
change(); // <- error: `this` is unbound
|
change(); // <- error: `this` is unbound
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
`is_def_fn`
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Use `is_def_fn` to detect if a Rhai function is defined (and therefore callable), based on its name
|
||||||
|
and the number of parameters.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn foo(x) { x + 1 }
|
||||||
|
|
||||||
|
is_def_fn("foo", 1) == true;
|
||||||
|
|
||||||
|
is_def_fn("bar", 1) == false;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Metadata
|
||||||
|
--------
|
||||||
|
|
||||||
|
The function `get_fn_metadata_list` is a _reflection_ API that returns an array of the metadata
|
||||||
|
of all script-defined functions in scope.
|
||||||
|
|
||||||
|
Functions from the following sources are returned, in order:
|
||||||
|
|
||||||
|
1) Encapsulated script environment (e.g. when loading a [module] from a script file),
|
||||||
|
2) Current script,
|
||||||
|
3) [Modules] imported via the [`import`] statement (latest imports first),
|
||||||
|
4) [Modules] added via [`Engine::register_module`]({{rootUrl}}/rust/modules/create.md) (latest registrations first)
|
||||||
|
|
||||||
|
The return value is an [array] of [object maps] (so `get_fn_metadata_list` is not available under
|
||||||
|
[`no_index`] or [`no_object`]), containing the following fields:
|
||||||
|
|
||||||
|
| Field | Type | Optional? | Description |
|
||||||
|
| -------------- | :------------------: | :-------: | ---------------------------------------------------------------------- |
|
||||||
|
| `namespace` | [string] | yes | the module _namespace_ if the function is defined within a module |
|
||||||
|
| `access` | [string] | no | `"public"` if the function is public,<br/>`"private"` if it is private |
|
||||||
|
| `name` | [string] | no | function name |
|
||||||
|
| `params` | [array] of [strings] | no | parameter names |
|
||||||
|
| `is_anonymous` | `bool` | no | is this function an anonymous function? |
|
||||||
|
@ -97,11 +97,11 @@ impl Imports {
|
|||||||
}
|
}
|
||||||
/// Get an iterator to this stack of imported modules in reverse order.
|
/// Get an iterator to this stack of imported modules in reverse order.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn iter(&self) -> impl Iterator<Item = (&str, Shared<Module>)> {
|
pub fn iter<'a>(&'a self) -> impl Iterator<Item = (ImmutableString, Shared<Module>)> + 'a {
|
||||||
self.0.iter().flat_map(|lib| {
|
self.0.iter().flat_map(|lib| {
|
||||||
lib.iter()
|
lib.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.map(|(name, module)| (name.as_str(), module.clone()))
|
.map(|(name, module)| (name.clone(), module.clone()))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/// Get an iterator to this stack of imported modules in reverse order.
|
/// Get an iterator to this stack of imported modules in reverse order.
|
||||||
|
@ -1500,10 +1500,16 @@ impl Module {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get an iterator to the sub-modules in the module.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn iter_sub_modules(&self) -> impl Iterator<Item = (&str, Shared<Module>)> {
|
||||||
|
self.modules.iter().map(|(k, m)| (k.as_str(), m.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
/// Get an iterator to the variables in the module.
|
/// Get an iterator to the variables in the module.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn iter_var(&self) -> impl Iterator<Item = (&String, &Dynamic)> {
|
pub fn iter_var(&self) -> impl Iterator<Item = (&str, &Dynamic)> {
|
||||||
self.variables.iter()
|
self.variables.iter().map(|(k, v)| (k.as_str(), v))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an iterator to the functions in the module.
|
/// Get an iterator to the functions in the module.
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
|
use crate::module::SharedScriptFnDef;
|
||||||
use crate::plugin::*;
|
use crate::plugin::*;
|
||||||
use crate::{def_package, FnPtr};
|
use crate::stdlib::iter::empty;
|
||||||
|
use crate::{calc_script_fn_hash, def_package, FnPtr, ImmutableString, NativeCallContext, INT};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
use crate::{stdlib::collections::HashMap, Array, Map};
|
||||||
|
|
||||||
def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, {
|
def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, {
|
||||||
combine_with_exported_module!(lib, "FnPtr", fn_ptr_functions);
|
combine_with_exported_module!(lib, "FnPtr", fn_ptr_functions);
|
||||||
@ -13,10 +20,111 @@ mod fn_ptr_functions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub mod anonymous {
|
pub mod functions {
|
||||||
#[rhai_fn(name = "is_anonymous", get = "is_anonymous")]
|
#[rhai_fn(name = "is_anonymous", get = "is_anonymous")]
|
||||||
pub fn is_anonymous(f: &mut FnPtr) -> bool {
|
pub fn is_anonymous(f: &mut FnPtr) -> bool {
|
||||||
f.is_anonymous()
|
f.is_anonymous()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_def_fn(ctx: NativeCallContext, fn_name: &str, num_params: INT) -> bool {
|
||||||
|
if num_params < 0 {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
let hash_script = calc_script_fn_hash(empty(), fn_name, num_params as usize);
|
||||||
|
ctx.engine()
|
||||||
|
.has_override(ctx.mods, ctx.lib, 0, hash_script, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
pub mod functions_and_maps {
|
||||||
|
pub fn get_fn_metadata_list(ctx: NativeCallContext) -> Array {
|
||||||
|
collect_fn_metadata(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
fn collect_fn_metadata(ctx: NativeCallContext) -> Array {
|
||||||
|
// Create a metadata record for a function.
|
||||||
|
fn make_metadata(
|
||||||
|
dict: &HashMap<String, ImmutableString>,
|
||||||
|
namespace: Option<ImmutableString>,
|
||||||
|
f: SharedScriptFnDef,
|
||||||
|
) -> Map {
|
||||||
|
let mut map = Map::with_capacity(6);
|
||||||
|
|
||||||
|
if let Some(ns) = namespace {
|
||||||
|
map.insert(dict["namespace"].clone(), ns.into());
|
||||||
|
}
|
||||||
|
map.insert(dict["name"].clone(), f.name.clone().into());
|
||||||
|
map.insert(
|
||||||
|
dict["access"].clone(),
|
||||||
|
match f.access {
|
||||||
|
FnAccess::Public => dict["public"].clone(),
|
||||||
|
FnAccess::Private => dict["private"].clone(),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
map.insert(
|
||||||
|
dict["is_anonymous"].clone(),
|
||||||
|
f.name.starts_with(crate::engine::FN_ANONYMOUS).into(),
|
||||||
|
);
|
||||||
|
map.insert(
|
||||||
|
dict["params"].clone(),
|
||||||
|
f.params
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(Into::<Dynamic>::into)
|
||||||
|
.collect::<Array>()
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
map.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively scan modules for script-defined functions.
|
||||||
|
fn scan_module(
|
||||||
|
list: &mut Array,
|
||||||
|
dict: &HashMap<String, ImmutableString>,
|
||||||
|
namespace: ImmutableString,
|
||||||
|
module: &Module,
|
||||||
|
) {
|
||||||
|
module.iter_script_fn().for_each(|(_, _, _, _, f)| {
|
||||||
|
list.push(make_metadata(dict, Some(namespace.clone()), f).into())
|
||||||
|
});
|
||||||
|
module.iter_sub_modules().for_each(|(ns, m)| {
|
||||||
|
let ns: ImmutableString = format!("{}::{}", namespace, ns).into();
|
||||||
|
scan_module(list, dict, ns, m.as_ref())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intern strings
|
||||||
|
let mut dict = HashMap::<String, ImmutableString>::with_capacity(8);
|
||||||
|
dict.insert("namespace".into(), "namespace".into());
|
||||||
|
dict.insert("name".into(), "name".into());
|
||||||
|
dict.insert("access".into(), "access".into());
|
||||||
|
dict.insert("public".into(), "public".into());
|
||||||
|
dict.insert("private".into(), "private".into());
|
||||||
|
dict.insert("is_anonymous".into(), "is_anonymous".into());
|
||||||
|
dict.insert("params".into(), "params".into());
|
||||||
|
|
||||||
|
let mut list: Array = Default::default();
|
||||||
|
|
||||||
|
ctx.lib
|
||||||
|
.iter()
|
||||||
|
.flat_map(|m| m.iter_script_fn())
|
||||||
|
.for_each(|(_, _, _, _, f)| list.push(make_metadata(&dict, None, f).into()));
|
||||||
|
|
||||||
|
if let Some(mods) = ctx.mods {
|
||||||
|
mods.iter()
|
||||||
|
.for_each(|(ns, m)| scan_module(&mut list, &dict, ns, m.as_ref()));
|
||||||
|
}
|
||||||
|
|
||||||
|
list
|
||||||
|
}
|
||||||
|
@ -4,34 +4,9 @@ use super::iter_basic::BasicIteratorPackage;
|
|||||||
use super::logic::LogicPackage;
|
use super::logic::LogicPackage;
|
||||||
use super::string_basic::BasicStringPackage;
|
use super::string_basic::BasicStringPackage;
|
||||||
|
|
||||||
use crate::fn_native::{CallableFunction, FnCallArgs};
|
use crate::def_package;
|
||||||
use crate::stdlib::{any::TypeId, boxed::Box, iter::empty};
|
|
||||||
use crate::{
|
|
||||||
calc_script_fn_hash, def_package, FnAccess, FnNamespace, ImmutableString, NativeCallContext,
|
|
||||||
INT,
|
|
||||||
};
|
|
||||||
|
|
||||||
def_package!(crate:CorePackage:"_Core_ package containing basic facilities.", lib, {
|
def_package!(crate:CorePackage:"_Core_ package containing basic facilities.", lib, {
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
{
|
|
||||||
let f = |ctx: NativeCallContext, args: &mut FnCallArgs| {
|
|
||||||
let num_params = args[1].clone().cast::<INT>();
|
|
||||||
let fn_name = args[0].as_str().unwrap();
|
|
||||||
|
|
||||||
Ok(if num_params < 0 {
|
|
||||||
false.into()
|
|
||||||
} else {
|
|
||||||
let hash_script = calc_script_fn_hash(empty(), fn_name, num_params as usize);
|
|
||||||
ctx.engine().has_override(ctx.mods, ctx.lib, 0, hash_script, true).into()
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
lib.set_fn("is_def_fn", FnNamespace::Global, FnAccess::Public,
|
|
||||||
Some(&["fn_name: &str", "num_params: INT"]),
|
|
||||||
&[TypeId::of::<ImmutableString>(), TypeId::of::<INT>()],
|
|
||||||
CallableFunction::from_method(Box::new(f)));
|
|
||||||
}
|
|
||||||
|
|
||||||
ArithmeticPackage::init(lib);
|
ArithmeticPackage::init(lib);
|
||||||
LogicPackage::init(lib);
|
LogicPackage::init(lib);
|
||||||
BasicStringPackage::init(lib);
|
BasicStringPackage::init(lib);
|
||||||
|
Loading…
Reference in New Issue
Block a user