Merge branch 'master' into plugins_dev
This commit is contained in:
commit
d024f912e0
@ -9,6 +9,7 @@ New features
|
|||||||
|
|
||||||
* Adds `Engine::register_get_result`, `Engine::register_set_result`, `Engine::register_indexer_get_result`, `Engine::register_indexer_set_result` API.
|
* Adds `Engine::register_get_result`, `Engine::register_set_result`, `Engine::register_indexer_get_result`, `Engine::register_indexer_set_result` API.
|
||||||
* Adds `Module::combine` to combine two modules.
|
* Adds `Module::combine` to combine two modules.
|
||||||
|
* `Engine::parse_json` now also accepts a JSON object starting with `#{`.
|
||||||
|
|
||||||
|
|
||||||
Version 0.18.1
|
Version 0.18.1
|
||||||
|
@ -88,6 +88,7 @@ The Rhai Scripting Language
|
|||||||
4. [Create from AST](language/modules/ast.md)
|
4. [Create from AST](language/modules/ast.md)
|
||||||
5. [Module Resolvers](rust/modules/resolvers.md)
|
5. [Module Resolvers](rust/modules/resolvers.md)
|
||||||
1. [Custom Implementation](rust/modules/imp-resolver.md)
|
1. [Custom Implementation](rust/modules/imp-resolver.md)
|
||||||
|
18. [Eval Statement](language/eval.md)
|
||||||
6. [Safety and Protection](safety/index.md)
|
6. [Safety and Protection](safety/index.md)
|
||||||
1. [Checked Arithmetic](safety/checked.md)
|
1. [Checked Arithmetic](safety/checked.md)
|
||||||
2. [Sand-Boxing](safety/sandbox.md)
|
2. [Sand-Boxing](safety/sandbox.md)
|
||||||
@ -119,7 +120,7 @@ The Rhai Scripting Language
|
|||||||
1. [Disable Keywords and/or Operators](engine/disable.md)
|
1. [Disable Keywords and/or Operators](engine/disable.md)
|
||||||
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)
|
||||||
7. [Eval Statement](language/eval.md)
|
7. [Multiple Instantiation](patterns/multiple.md)
|
||||||
8. [Appendix](appendix/index.md)
|
8. [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)
|
||||||
|
@ -7,7 +7,7 @@ The syntax for an [object map] is extremely similar to JSON, with the exception
|
|||||||
technically be mapped to [`()`]. A valid JSON string does not start with a hash character `#` while a
|
technically be mapped to [`()`]. A valid JSON string does not start with a hash character `#` while a
|
||||||
Rhai [object map] does - that's the major difference!
|
Rhai [object map] does - that's the major difference!
|
||||||
|
|
||||||
Use the `Engine::parse_json` method to parse a piece of JSON into an object map:
|
Use the `Engine::parse_json` method to parse a piece of _simple_ JSON into an object map:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// JSON string - notice that JSON property names are always quoted
|
// JSON string - notice that JSON property names are always quoted
|
||||||
@ -45,3 +45,28 @@ the [`no_float`] feature is not used. Most common generators of JSON data disti
|
|||||||
integer and floating-point values by always serializing a floating-point number with a decimal point
|
integer and floating-point values by always serializing a floating-point number with a decimal point
|
||||||
(i.e. `123.0` instead of `123` which is assumed to be an integer). This style can be used successfully
|
(i.e. `123.0` instead of `123` which is assumed to be an integer). This style can be used successfully
|
||||||
with Rhai [object maps].
|
with Rhai [object maps].
|
||||||
|
|
||||||
|
|
||||||
|
Parse JSON with Sub-Objects
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
`Engine::parse_json` depends on the fact that the [object map] literal syntax in Rhai is _almost_
|
||||||
|
the same as a JSON object. However, it is _almost_ because the syntax for a sub-object in JSON
|
||||||
|
(i.e. "`{ ... }`") is different from a Rhai [object map] literal (i.e. "`#{ ... }`").
|
||||||
|
|
||||||
|
When `Engine::parse_json` encounters JSON with sub-objects, it fails with a syntax error.
|
||||||
|
|
||||||
|
If it is certain that no text string in the JSON will ever contain the character '`{`',
|
||||||
|
then it is possible to parse it by first replacing all occupance of '`{`' with "`#{`".
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// JSON with sub-object 'b'.
|
||||||
|
let json = r#"{"a":1, "b":{"x":true, "y":false}}"#;
|
||||||
|
|
||||||
|
let new_json = json.replace("{" "#{");
|
||||||
|
|
||||||
|
// The leading '{' will also be replaced to '#{', but parse_json can handle this.
|
||||||
|
let map = engine.parse_json(&new_json, false)?;
|
||||||
|
|
||||||
|
map.len() == 2; // 'map' contains two properties: 'a' and 'b'
|
||||||
|
```
|
||||||
|
89
doc/src/patterns/multiple.md
Normal file
89
doc/src/patterns/multiple.md
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
Multiple Instantiation
|
||||||
|
======================
|
||||||
|
|
||||||
|
{{#include ../links.md}}
|
||||||
|
|
||||||
|
|
||||||
|
Background
|
||||||
|
----------
|
||||||
|
|
||||||
|
Rhai's [features] are not strictly additive. This is easily deduced from the [`no_std`] feature
|
||||||
|
which prepares the crate for `no-std` builds. Obviously, turning on this feature has a material
|
||||||
|
impact on how Rhai behaves.
|
||||||
|
|
||||||
|
Many crates resolve this by going the opposite direction: build for `no-std` in default,
|
||||||
|
but add a `std` feature, included by default, which builds for the `stdlib`.
|
||||||
|
|
||||||
|
|
||||||
|
Rhai Language Features Are Not Additive
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
Rhai, however, is more complex. Language features cannot be easily made _additive_.
|
||||||
|
|
||||||
|
That is because the _lack_ of a language feature is a feature by itself.
|
||||||
|
|
||||||
|
For example, by including [`no_float`], a project sets the Rhai language to ignore floating-point math.
|
||||||
|
Floating-point numbers do not even parse under this case and will generate syntax errors.
|
||||||
|
Assume that the project expects this behavior (why? perhaps integers are all that make sense
|
||||||
|
within the project domain).
|
||||||
|
|
||||||
|
Now, assume that a dependent crate also depends on Rhai. Under such circumstances,
|
||||||
|
unless _exact_ versioning is used and the dependent crate depends on a _different_ version
|
||||||
|
of Rhai, Cargo automatically _merges_ both dependencies, with the [`no_float`] feature turned on
|
||||||
|
because Cargo features are _additive_.
|
||||||
|
|
||||||
|
This will break the dependent crate, which does not by itself specify [`no_float`]
|
||||||
|
and expects floating-point numbers and math to work normally.
|
||||||
|
|
||||||
|
There is no way out of this dilemma. Reversing the [features] set with a `float` feature
|
||||||
|
causes the project to break because floating-point numbers are not rejected as expected.
|
||||||
|
|
||||||
|
|
||||||
|
Multiple Instantiations of Rhai Within The Same Project
|
||||||
|
------------------------------------------------------
|
||||||
|
|
||||||
|
The trick is to differentiate between multiple identical copies of Rhai, each having
|
||||||
|
a different [features] set, by their _sources_:
|
||||||
|
|
||||||
|
* Different versions from [`crates.io`](https://crates.io/crates/rhai/) - The official crate.
|
||||||
|
|
||||||
|
* Different releases from [`GitHub`](https://github.com/jonathandturner/rhai) - Crate source on GitHub.
|
||||||
|
|
||||||
|
* Forked copy of [https://github.com/jonathandturner/rhai](https://github.com/jonathandturner/rhai) on GitHub.
|
||||||
|
|
||||||
|
* Local copy of [https://github.com/jonathandturner/rhai](https://github.com/jonathandturner/rhai) downloaded form GitHub.
|
||||||
|
|
||||||
|
Use the following configuration in `Cargo.toml` to pull in multiple copies of Rhai within the same project:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
rhai = { version = "{{version}}", features = [ "no_float" ] }
|
||||||
|
rhai_github = { git = "https://github.com/jonathandturner/rhai", features = [ "unchecked" ] }
|
||||||
|
rhai_my_github = { git = "https://github.com/my_github/rhai", branch = "variation1", features = [ "serde", "no_closure" ] }
|
||||||
|
rhai_local = { path = "../rhai_copy" }
|
||||||
|
```
|
||||||
|
|
||||||
|
The example above creates four different modules: `rhai`, `rhai_github`, `rhai_my_github` and
|
||||||
|
`rhai_local`, each referring to a different Rhai copy with the appropriate [features] set.
|
||||||
|
|
||||||
|
Only one crate of any particular version can be used from each source, because Cargo merges
|
||||||
|
all candidate cases within the same source, adding all [features] together.
|
||||||
|
|
||||||
|
If more than four different instantiations of Rhai is necessary (why?), create more local repositories
|
||||||
|
or GitHub forks or branches.
|
||||||
|
|
||||||
|
|
||||||
|
Caveat - No Way To Avoid Dependency Conflicts
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
Unfortunately, pulling in Rhai from different sources do not resolve the problem of
|
||||||
|
[features] conflict between dependencies. Even overriding `crates.io` via the `[patch]` manifest
|
||||||
|
section doesn't work - all dependencies will eventually find the only one copy.
|
||||||
|
|
||||||
|
What is necessary - multiple copies of Rhai, one for each dependent crate that requires it,
|
||||||
|
together with their _unique_ [features] set intact. In other words, turning off Cargo's
|
||||||
|
crate merging feature _just for Rhai_.
|
||||||
|
|
||||||
|
Unfortunately, as of this writing, there is no known method to achieve it.
|
||||||
|
|
||||||
|
Therefore, moral of the story: avoid pulling in multiple crates that depend on Rhai.
|
@ -52,3 +52,15 @@ no floating-point, is `Send + Sync` (so it can be safely used across threads), a
|
|||||||
nor loading external [modules].
|
nor loading external [modules].
|
||||||
|
|
||||||
This configuration is perfect for an expression parser in a 32-bit embedded system without floating-point hardware.
|
This configuration is perfect for an expression parser in a 32-bit embedded system without floating-point hardware.
|
||||||
|
|
||||||
|
|
||||||
|
Caveat - Features Are Not Additive
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
Rhai features are not strictly _additive_ - i.e. they do not only add optional functionalities.
|
||||||
|
|
||||||
|
In fact, most features are _subtractive_ - i.e. they _remove_ functionalities.
|
||||||
|
|
||||||
|
There is a reason for this design, because the _lack_ of a language feature by itself is a feature.
|
||||||
|
|
||||||
|
See [here]({{rootUrl}}/patterns/multiple.md) for more details.
|
||||||
|
32
src/api.rs
32
src/api.rs
@ -898,21 +898,34 @@ impl Engine {
|
|||||||
/// Set `has_null` to `true` in order to map `null` values to `()`.
|
/// Set `has_null` to `true` in order to map `null` values to `()`.
|
||||||
/// Setting it to `false` will cause a _variable not found_ error during parsing.
|
/// Setting it to `false` will cause a _variable not found_ error during parsing.
|
||||||
///
|
///
|
||||||
|
/// # JSON With Sub-Objects
|
||||||
|
///
|
||||||
|
/// This method assumes no sub-objects in the JSON string. That is because the syntax
|
||||||
|
/// of a JSON sub-object (or object hash), `{ .. }`, is different from Rhai's syntax, `#{ .. }`.
|
||||||
|
/// Parsing a JSON string with sub-objects will cause a syntax error.
|
||||||
|
///
|
||||||
|
/// If it is certain that the character `{` never appears in any text string within the JSON object,
|
||||||
|
/// then globally replace `{` with `#{` before calling this method.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
/// use rhai::Engine;
|
/// use rhai::{Engine, Map};
|
||||||
///
|
///
|
||||||
/// let engine = Engine::new();
|
/// let engine = Engine::new();
|
||||||
///
|
///
|
||||||
/// let map = engine.parse_json(r#"{"a":123, "b":42, "c":false, "d":null}"#, true)?;
|
/// let map = engine.parse_json(
|
||||||
|
/// r#"{"a":123, "b":42, "c":{"x":false, "y":true}, "d":null}"#
|
||||||
|
/// .replace("{", "#{").as_str(), true)?;
|
||||||
///
|
///
|
||||||
/// assert_eq!(map.len(), 4);
|
/// assert_eq!(map.len(), 4);
|
||||||
/// assert_eq!(map.get("a").cloned().unwrap().cast::<i64>(), 123);
|
/// assert_eq!(map["a"].as_int().unwrap(), 123);
|
||||||
/// assert_eq!(map.get("b").cloned().unwrap().cast::<i64>(), 42);
|
/// assert_eq!(map["b"].as_int().unwrap(), 42);
|
||||||
/// assert_eq!(map.get("c").cloned().unwrap().cast::<bool>(), false);
|
/// assert!(map["d"].is::<()>());
|
||||||
/// assert_eq!(map.get("d").cloned().unwrap().cast::<()>(), ());
|
///
|
||||||
|
/// let c = map["c"].read_lock::<Map>().unwrap();
|
||||||
|
/// assert_eq!(c["x"].as_bool().unwrap(), false);
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
@ -921,7 +934,12 @@ impl Engine {
|
|||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
// Trims the JSON string and add a '#' in front
|
// Trims the JSON string and add a '#' in front
|
||||||
let scripts = ["#", json.trim()];
|
let json = json.trim();
|
||||||
|
let scripts = if json.starts_with(Token::MapStart.syntax().as_ref()) {
|
||||||
|
[json, ""]
|
||||||
|
} else {
|
||||||
|
["#", json]
|
||||||
|
};
|
||||||
let stream = lex(
|
let stream = lex(
|
||||||
&scripts,
|
&scripts,
|
||||||
if has_null {
|
if has_null {
|
||||||
|
Loading…
Reference in New Issue
Block a user