diff --git a/Cargo.toml b/Cargo.toml index 39532b69..b785865d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,8 +41,8 @@ no_function = [ "no_closure" ] # no script-defined functions (meaning no closur no_closure = [] # no automatic sharing and capture of anonymous functions to external variables no_module = [] # no modules internals = [] # expose internal data structures -unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. -metadata = [ "serde", "serde_json"] +unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. +metadata = [ "serde", "serde_json"] # enables exporting functions metadata to JSON # compiling for no-std no_std = [ "smallvec/union", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ] diff --git a/RELEASES.md b/RELEASES.md index 5ac71093..1564a006 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -10,6 +10,9 @@ Each function defined in an `AST` can optionally attach _doc-comments_ (which, a are comments prefixed by either `///` or `/**`). Doc-comments allow third-party tools to automatically generate documentation for functions defined in a Rhai script. +A new API, `Engine::gen_fn_metadata_to_json`, paired with the new `metadata` feature, +exports the full list of functions metadata (including those in an `AST`) as a JSON document. + Bug fixes --------- @@ -31,6 +34,7 @@ New features * `AST::iter_functions` now returns `ScriptFnMetadata` which includes, among others, _doc-comments_ for functions prefixed by `///` or `/**`. * A functions lookup cache is added to make function call resolution faster. +* A new feature `metadata` is added that pulls in `serde_json` and enables `Engine::gen_fn_metadata_to_json` which exports the full list of functions metadata (including those inside an `AST`) in JSON format. Enhancements ------------ diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 556a60fb..80652df2 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -136,7 +136,9 @@ The Rhai Scripting Language 2. [Custom Operators](engine/custom-op.md) 3. [Extending with Custom Syntax](engine/custom-syntax.md) 5. [Multiple Instantiation](patterns/multiple.md) - 6. [Get Function Signatures](engine/get_fn_sig.md) + 6. [Functions Metadata](engine/metadata/index.md) + 1. [Generate Function Signatures](engine/metadata/gen_fn_sig.md) + 2. [Export Metadata to JSON](engine/metadata/export_to_json.md) 10. [Appendix](appendix/index.md) 1. [Keywords](appendix/keywords.md) 2. [Operators and Symbols](appendix/operators.md) diff --git a/doc/src/engine/metadata/export_to_json.md b/doc/src/engine/metadata/export_to_json.md new file mode 100644 index 00000000..c6476df3 --- /dev/null +++ b/doc/src/engine/metadata/export_to_json.md @@ -0,0 +1,106 @@ +Export Functions Metadata to JSON +================================ + +{{#include ../../links.md}} + + +`Engine::gen_fn_metadata_to_json` +-------------------------------- + +As part of a _reflections_ API, `Engine::gen_fn_metadata_to_json` exports the full list +of [functions metadata] in JSON format. + +The [`metadata`] feature must be used to turn on this method, which requires +the [`serde_json`](https://crates.io/crates/serde_json) crate. + +### Sources + +Functions from the following sources are included: + +1) Script-defined functions in an [`AST`], if provided +2) Native Rust functions registered into the global namespace via the `Engine::register_XXX` API +3) _Public_ (i.e. non-[`private`]) functions (native Rust or Rhai scripted) in global sub-modules registered via + [`Engine::register_module`]({{rootUrl}}/rust/modules/create.md) +4) Native Rust functions in registered [packages] (optional) + +Notice that if a function has been [overloaded][function overloading], only the overriding function's +metadata is included. + + +JSON Schema +----------- + +The JSON schema used to hold functions metadata is very simple, containing a nested structure of +`modules` and a list of `functions`. + +### Modules Schema + +```json +{ + "modules": + { + "sub_module_1": + { + "modules": + { + "sub_sub_module_A": + { + "functions": + [ + { ... function metadata ... }, + { ... function metadata ... }, + { ... function metadata ... }, + { ... function metadata ... }, + ... + ] + }, + "sub_sub_module_B": + { + ... + } + } + }, + "sub_module_2": + { + ... + }, + ... + }, + "functions": + [ + { ... function metadata ... }, + { ... function metadata ... }, + { ... function metadata ... }, + { ... function metadata ... }, + ... + ] +} +``` + +### Function Metadata Schema + +```json +{ + "namespace": "internal" | "global", + "access": "public" | "private", + "name": "fn_name", + "type": "native" | "script", + "numParams": 42, /* number of parameters */ + "params": /* omitted if no parameters */ + [ + { "name": "param_1", "type": "type_1" }, + { "name": "param_2" }, /* no type info */ + { "name": "_", "type": "type_3" }, + ... + ], + "returnType": "ret_type", /* omitted if unknown */ + "signature": "[private] fn_name(param_1: type_1, param_2, _: type_3) -> ret_type", + "docComments": /* omitted if none */ + [ + "/// doc-comment line 1", + "/// doc-comment line 2", + "/** doc-comment block */", + ... + ] +} +``` diff --git a/doc/src/engine/get_fn_sig.md b/doc/src/engine/metadata/gen_fn_sig.md similarity index 70% rename from doc/src/engine/get_fn_sig.md rename to doc/src/engine/metadata/gen_fn_sig.md index 2e54a430..5cea197e 100644 --- a/doc/src/engine/get_fn_sig.md +++ b/doc/src/engine/metadata/gen_fn_sig.md @@ -1,26 +1,29 @@ -Get Function Signatures -======================= +Generate Function Signatures +=========================== -{{#include ../links.md}} +{{#include ../../links.md}} `Engine::gen_fn_signatures` -------------------------- -As part of a _reflections_ API, `Engine::gen_fn_signatures` returns a list of function signatures -(`Vec`), each corresponding to a particular function available to that [`Engine`] instance. +As part of a _reflections_ API, `Engine::gen_fn_signatures` returns a list of function _signatures_ +(as `Vec`), each corresponding to a particular function available to that [`Engine`] instance. + +> `fn_name ( param_1: type_1, param_2: type_2, ... , param_n : type_n ) -> return_type` + +### Sources Functions from the following sources are included, in order: -1) Functions registered into the global namespace via the `Engine::register_XXX` API, -2) Functions in global sub-modules registered via [`Engine::register_module`]({{rootUrl}}/rust/modules/create.md), -3) Functions in registered [packages] (optional) - -Included are both native Rust as well as script-defined functions (except [`private`] ones). +1) Native Rust functions registered into the global namespace via the `Engine::register_XXX` API +2) _Public_ (i.e. non-[`private`]) functions (native Rust or Rhai scripted) in global sub-modules registered via + [`Engine::register_module`]({{rootUrl}}/rust/modules/create.md) +3) Native Rust functions in registered [packages] (optional) -Function Metadata ------------------ +Functions Metadata +------------------ Beware, however, that not all function signatures contain parameters and return value information. @@ -30,7 +33,7 @@ For instance, functions registered via `Engine::register_XXX` contain no informa the names of parameter and their actual types because Rust simply does not make such metadata available natively. The return type is also undetermined. -A function registered under the name 'foo' with three parameters and unknown return type: +A function registered under the name `foo` with three parameters and unknown return type: > `foo(_, _, _)` @@ -41,7 +44,7 @@ Notice that function names do not need to be valid identifiers. A [property setter][getters/setters] - again, unknown parameters and return type. Notice that function names do not need to be valid identifiers. -In this case, the first parameter should be '&mut T' of the custom type and the return value is '()': +In this case, the first parameter should be `&mut T` of the custom type and the return value is `()`: > `set$prop(_, _, _)` diff --git a/doc/src/engine/metadata/index.md b/doc/src/engine/metadata/index.md new file mode 100644 index 00000000..227b1fcf --- /dev/null +++ b/doc/src/engine/metadata/index.md @@ -0,0 +1,28 @@ +Functions Metadata +================== + +{{#include ../../links.md}} + +The _metadata_ of a [function] means all relevant information related to a function's +definition including: + +1. Its callable name + +2. Its access mode (public or [private][`private`]) + +3. Its parameters and types (if any) + +4. Its return value and type (if any) + +5. Its nature (i.e. native Rust-based or Rhai script-based) + +6. Its [namespace][function namespace] (module or global) + +7. Its purpose, in the form of [doc-comments] + +8. Usage notes, warnings, etc., in the form of [doc-comments] + +A function's _signature_ encapsulates the first four pieces of information in a single +concise line of definition: + +> `[private] fn_name ( param_1: type_1, param_2: type_2, ... , param_n : type_n ) -> return_type` diff --git a/doc/src/links.md b/doc/src/links.md index 3eee2ae7..8b6428c6 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -41,6 +41,7 @@ [plugin modules]: {{rootUrl}}/plugins/module.md [plugin function]: {{rootUrl}}/plugins/function.md [plugin functions]: {{rootUrl}}/plugins/function.md +[functions metadata]: {{rootUrl}}/engine/metadata/index.md [`Scope`]: {{rootUrl}}/engine/scope.md [`serde`]: {{rootUrl}}/rust/serde.md @@ -90,6 +91,7 @@ [timestamp]: {{rootUrl}}/language/timestamps.md [timestamps]: {{rootUrl}}/language/timestamps.md +[doc-comments]: {{rootUrl}}/language/comments.md#doc-comments [function]: {{rootUrl}}/language/functions.md [functions]: {{rootUrl}}/language/functions.md [function overloading]: {{rootUrl}}/rust/functions.md#function-overloading diff --git a/doc/src/start/features.md b/doc/src/start/features.md index ee7c8fa9..06e868db 100644 --- a/doc/src/start/features.md +++ b/doc/src/start/features.md @@ -11,25 +11,25 @@ Notice that this deviates from Rust norm where features are _additive_. Excluding unneeded functionalities can result in smaller, faster builds as well as more control over what a script can (or cannot) do. -| Feature | Additive? | Description | -| ------------------- | :-------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `unchecked` | no | disables arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit.
Beware that a bad script may panic the entire system! | -| `sync` | no | restricts all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and [`AST`], are all `Send + Sync` | -| `no_optimize` | no | disables [script optimization] | -| `no_float` | no | disables floating-point numbers and math | -| `f32_float` | no | sets the system floating-point type to `f32` instead of `f64`. `FLOAT` is set to `f32` | -| `only_i32` | no | sets the system integer type to `i32` and disable all other integer types. `INT` is set to `i32` | -| `only_i64` | no | sets the system integer type to `i64` and disable all other integer types. `INT` is set to `i64` | -| `no_index` | no | disables [arrays] and indexing features | -| `no_object` | no | disables support for [custom types] and [object maps] | -| `no_function` | no | disables script-defined [functions] | -| `no_module` | no | disables loading external [modules] | -| `no_closure` | no | disables [capturing][automatic currying] external variables in [anonymous functions] to simulate _closures_, or [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls | -| `no_std` | no | builds for `no-std` (implies `no_closure`). Notice that additional dependencies will be pulled in to replace `std` features | -| `serde` | yes | enables serialization/deserialization via `serde` (requires the [`serde`](https://crates.io/crates/serde) crate) | -| `unicode-xid-ident` | no | allows [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) as identifiers | -| `metadata` | yes | enables exporting functions metadata to JSON format (requires the [`serde`](https://crates.io/crates/serde) and [`serde_json`](https://crates.io/crates/serde_json) crates) | -| `internals` | yes | exposes internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version | +| Feature | Additive? | Description | +| ------------------- | :-------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `unchecked` | no | disables arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit.
Beware that a bad script may panic the entire system! | +| `sync` | no | restricts all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and [`AST`], are all `Send + Sync` | +| `no_optimize` | no | disables [script optimization] | +| `no_float` | no | disables floating-point numbers and math | +| `f32_float` | no | sets the system floating-point type to `f32` instead of `f64`. `FLOAT` is set to `f32` | +| `only_i32` | no | sets the system integer type to `i32` and disable all other integer types. `INT` is set to `i32` | +| `only_i64` | no | sets the system integer type to `i64` and disable all other integer types. `INT` is set to `i64` | +| `no_index` | no | disables [arrays] and indexing features | +| `no_object` | no | disables support for [custom types] and [object maps] | +| `no_function` | no | disables script-defined [functions] (implies `no_closure`) | +| `no_module` | no | disables loading external [modules] | +| `no_closure` | no | disables [capturing][automatic currying] external variables in [anonymous functions] to simulate _closures_, or [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls | +| `no_std` | no | builds for `no-std` (implies `no_closure`). Notice that additional dependencies will be pulled in to replace `std` features | +| `serde` | yes | enables serialization/deserialization via `serde` (requires the [`serde`](https://crates.io/crates/serde) crate) | +| `unicode-xid-ident` | no | allows [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) as identifiers | +| `metadata` | yes | enables exporting [functions metadata] to [JSON format]({{rootUrl}}/engine/metadata/export_to_json.md) (implies `serde` and additionally requires the [`serde_json`](https://crates.io/crates/serde_json) crate) | +| `internals` | yes | exposes internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version | Example diff --git a/src/serde_impl/metadata.rs b/src/serde_impl/metadata.rs index 0d6b710a..ef291705 100644 --- a/src/serde_impl/metadata.rs +++ b/src/serde_impl/metadata.rs @@ -1,4 +1,5 @@ use crate::stdlib::{ + cmp::Ordering, collections::BTreeMap, string::{String, ToString}, vec, @@ -46,7 +47,7 @@ impl From for FnAccess { } } -#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq, Ord, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct FnParam { pub name: String, @@ -54,6 +55,26 @@ struct FnParam { pub typ: Option, } +impl PartialOrd for FnParam { + fn partial_cmp(&self, other: &Self) -> Option { + Some(match self.name.partial_cmp(&other.name).unwrap() { + Ordering::Less => Ordering::Less, + Ordering::Greater => Ordering::Greater, + Ordering::Equal => match (self.typ.is_none(), other.typ.is_none()) { + (true, true) => Ordering::Equal, + (true, false) => Ordering::Greater, + (false, true) => Ordering::Less, + (false, false) => self + .typ + .as_ref() + .unwrap() + .partial_cmp(other.typ.as_ref().unwrap()) + .unwrap(), + }, + }) + } +} + #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct FnMetadata { @@ -67,10 +88,31 @@ struct FnMetadata { pub params: Vec, #[serde(default, skip_serializing_if = "Option::is_none")] pub return_type: Option, + pub signature: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub doc_comments: Option>, } +impl PartialOrd for FnMetadata { + fn partial_cmp(&self, other: &Self) -> Option { + Some(match self.name.partial_cmp(&other.name).unwrap() { + Ordering::Less => Ordering::Less, + Ordering::Greater => Ordering::Greater, + Ordering::Equal => match self.num_params.partial_cmp(&other.num_params).unwrap() { + Ordering::Less => Ordering::Less, + Ordering::Greater => Ordering::Greater, + Ordering::Equal => self.params.partial_cmp(&other.params).unwrap(), + }, + }) + } +} + +impl Ord for FnMetadata { + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other).unwrap() + } +} + impl From<&crate::module::FuncInfo> for FnMetadata { fn from(info: &crate::module::FuncInfo) -> Self { Self { @@ -108,6 +150,7 @@ impl From<&crate::module::FuncInfo> for FnMetadata { } else { None }, + signature: info.gen_signature(), doc_comments: if info.func.is_script() { Some(info.func.get_fn_def().comments.clone()) } else { @@ -134,6 +177,7 @@ impl From> for FnMetadata { }) .collect(), return_type: Some("Dynamic".to_string()), + signature: info.to_string(), doc_comments: if info.comments.is_empty() { None } else { @@ -154,12 +198,15 @@ struct ModuleMetadata { impl From<&crate::Module> for ModuleMetadata { fn from(module: &crate::Module) -> Self { + let mut functions: Vec<_> = module.iter_fn().map(|f| f.into()).collect(); + functions.sort(); + Self { modules: module .iter_sub_modules() .map(|(name, m)| (name.to_string(), m.as_ref().into())) .collect(), - functions: module.iter_fn().map(|f| f.into()).collect(), + functions, } } } @@ -203,6 +250,8 @@ impl Engine { .for_each(|info| global.functions.push(info)); } + global.functions.sort(); + serde_json::to_string_pretty(&global) } }