Add export to JSON.

This commit is contained in:
Stephen Chung 2020-12-20 12:27:47 +08:00
parent c6a3ce2cd5
commit 22039b24b3
9 changed files with 232 additions and 38 deletions

View File

@ -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_closure = [] # no automatic sharing and capture of anonymous functions to external variables
no_module = [] # no modules no_module = [] # no modules
internals = [] # expose internal data structures internals = [] # expose internal data structures
unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers.
metadata = [ "serde", "serde_json"] metadata = [ "serde", "serde_json"] # enables exporting functions metadata to JSON
# compiling for no-std # compiling for no-std
no_std = [ "smallvec/union", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ] no_std = [ "smallvec/union", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ]

View File

@ -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 are comments prefixed by either `///` or `/**`). Doc-comments allow third-party tools to
automatically generate documentation for functions defined in a Rhai script. 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 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 `/**`. * `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 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 Enhancements
------------ ------------

View File

@ -136,7 +136,9 @@ The Rhai Scripting Language
2. [Custom Operators](engine/custom-op.md) 2. [Custom Operators](engine/custom-op.md)
3. [Extending with Custom Syntax](engine/custom-syntax.md) 3. [Extending with Custom Syntax](engine/custom-syntax.md)
5. [Multiple Instantiation](patterns/multiple.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) 10. [Appendix](appendix/index.md)
1. [Keywords](appendix/keywords.md) 1. [Keywords](appendix/keywords.md)
2. [Operators and Symbols](appendix/operators.md) 2. [Operators and Symbols](appendix/operators.md)

View File

@ -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 */",
...
]
}
```

View File

@ -1,26 +1,29 @@
Get Function Signatures Generate Function Signatures
======================= ===========================
{{#include ../links.md}} {{#include ../../links.md}}
`Engine::gen_fn_signatures` `Engine::gen_fn_signatures`
-------------------------- --------------------------
As part of a _reflections_ API, `Engine::gen_fn_signatures` returns a list of function signatures As part of a _reflections_ API, `Engine::gen_fn_signatures` returns a list of function _signatures_
(`Vec<String>`), each corresponding to a particular function available to that [`Engine`] instance. (as `Vec<String>`), 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: Functions from the following sources are included, in order:
1) Functions registered into the global namespace via the `Engine::register_XXX` API, 1) Native Rust 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), 2) _Public_ (i.e. non-[`private`]) functions (native Rust or Rhai scripted) in global sub-modules registered via
3) Functions in registered [packages] (optional) [`Engine::register_module`]({{rootUrl}}/rust/modules/create.md)
3) Native Rust functions in registered [packages] (optional)
Included are both native Rust as well as script-defined functions (except [`private`] ones).
Function Metadata Functions Metadata
----------------- ------------------
Beware, however, that not all function signatures contain parameters and return value information. 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 the names of parameter and their actual types because Rust simply does not make such metadata
available natively. The return type is also undetermined. 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(_, _, _)` > `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. A [property setter][getters/setters] - again, unknown parameters and return type.
Notice that function names do not need to be valid identifiers. 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(_, _, _)` > `set$prop(_, _, _)`

View File

@ -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`

View File

@ -41,6 +41,7 @@
[plugin modules]: {{rootUrl}}/plugins/module.md [plugin modules]: {{rootUrl}}/plugins/module.md
[plugin function]: {{rootUrl}}/plugins/function.md [plugin function]: {{rootUrl}}/plugins/function.md
[plugin functions]: {{rootUrl}}/plugins/function.md [plugin functions]: {{rootUrl}}/plugins/function.md
[functions metadata]: {{rootUrl}}/engine/metadata/index.md
[`Scope`]: {{rootUrl}}/engine/scope.md [`Scope`]: {{rootUrl}}/engine/scope.md
[`serde`]: {{rootUrl}}/rust/serde.md [`serde`]: {{rootUrl}}/rust/serde.md
@ -90,6 +91,7 @@
[timestamp]: {{rootUrl}}/language/timestamps.md [timestamp]: {{rootUrl}}/language/timestamps.md
[timestamps]: {{rootUrl}}/language/timestamps.md [timestamps]: {{rootUrl}}/language/timestamps.md
[doc-comments]: {{rootUrl}}/language/comments.md#doc-comments
[function]: {{rootUrl}}/language/functions.md [function]: {{rootUrl}}/language/functions.md
[functions]: {{rootUrl}}/language/functions.md [functions]: {{rootUrl}}/language/functions.md
[function overloading]: {{rootUrl}}/rust/functions.md#function-overloading [function overloading]: {{rootUrl}}/rust/functions.md#function-overloading

View File

@ -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 Excluding unneeded functionalities can result in smaller, faster builds as well as
more control over what a script can (or cannot) do. more control over what a script can (or cannot) do.
| Feature | Additive? | Description | | 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.<br/>Beware that a bad script may panic the entire system! | | `unchecked` | no | disables arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit.<br/>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` | | `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_optimize` | no | disables [script optimization] |
| `no_float` | no | disables floating-point numbers and math | | `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` | | `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_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` | | `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_index` | no | disables [arrays] and indexing features |
| `no_object` | no | disables support for [custom types] and [object maps] | | `no_object` | no | disables support for [custom types] and [object maps] |
| `no_function` | no | disables script-defined [functions] | | `no_function` | no | disables script-defined [functions] (implies `no_closure`) |
| `no_module` | no | disables loading external [modules] | | `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_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 | | `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) | | `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 | | `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) | | `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 | | `internals` | yes | exposes internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version |
Example Example

View File

@ -1,4 +1,5 @@
use crate::stdlib::{ use crate::stdlib::{
cmp::Ordering,
collections::BTreeMap, collections::BTreeMap,
string::{String, ToString}, string::{String, ToString},
vec, vec,
@ -46,7 +47,7 @@ impl From<crate::FnAccess> for FnAccess {
} }
} }
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct FnParam { struct FnParam {
pub name: String, pub name: String,
@ -54,6 +55,26 @@ struct FnParam {
pub typ: Option<String>, pub typ: Option<String>,
} }
impl PartialOrd for FnParam {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
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)] #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct FnMetadata { struct FnMetadata {
@ -67,10 +88,31 @@ struct FnMetadata {
pub params: Vec<FnParam>, pub params: Vec<FnParam>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub return_type: Option<String>, pub return_type: Option<String>,
pub signature: String,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub doc_comments: Option<Vec<String>>, pub doc_comments: Option<Vec<String>>,
} }
impl PartialOrd for FnMetadata {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
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 { impl From<&crate::module::FuncInfo> for FnMetadata {
fn from(info: &crate::module::FuncInfo) -> Self { fn from(info: &crate::module::FuncInfo) -> Self {
Self { Self {
@ -108,6 +150,7 @@ impl From<&crate::module::FuncInfo> for FnMetadata {
} else { } else {
None None
}, },
signature: info.gen_signature(),
doc_comments: if info.func.is_script() { doc_comments: if info.func.is_script() {
Some(info.func.get_fn_def().comments.clone()) Some(info.func.get_fn_def().comments.clone())
} else { } else {
@ -134,6 +177,7 @@ impl From<crate::ScriptFnMetadata<'_>> for FnMetadata {
}) })
.collect(), .collect(),
return_type: Some("Dynamic".to_string()), return_type: Some("Dynamic".to_string()),
signature: info.to_string(),
doc_comments: if info.comments.is_empty() { doc_comments: if info.comments.is_empty() {
None None
} else { } else {
@ -154,12 +198,15 @@ struct ModuleMetadata {
impl From<&crate::Module> for ModuleMetadata { impl From<&crate::Module> for ModuleMetadata {
fn from(module: &crate::Module) -> Self { fn from(module: &crate::Module) -> Self {
let mut functions: Vec<_> = module.iter_fn().map(|f| f.into()).collect();
functions.sort();
Self { Self {
modules: module modules: module
.iter_sub_modules() .iter_sub_modules()
.map(|(name, m)| (name.to_string(), m.as_ref().into())) .map(|(name, m)| (name.to_string(), m.as_ref().into()))
.collect(), .collect(),
functions: module.iter_fn().map(|f| f.into()).collect(), functions,
} }
} }
} }
@ -203,6 +250,8 @@ impl Engine {
.for_each(|info| global.functions.push(info)); .for_each(|info| global.functions.push(info));
} }
global.functions.sort();
serde_json::to_string_pretty(&global) serde_json::to_string_pretty(&global)
} }
} }