Correct speed claim and others in docs.
This commit is contained in:
parent
d73f3a1d60
commit
0182b2d3f4
@ -30,6 +30,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.
|
||||||
|
|
||||||
Enhancements
|
Enhancements
|
||||||
------------
|
------------
|
||||||
|
@ -27,7 +27,6 @@ Fast
|
|||||||
* Fairly low compile-time overhead.
|
* Fairly low compile-time overhead.
|
||||||
|
|
||||||
* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM).
|
* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM).
|
||||||
An unofficial Fibonacci benchmark puts Rhai somewhere between Wren and Python.
|
|
||||||
|
|
||||||
* Scripts are [optimized][script optimization] (useful for template-based machine-generated scripts) for repeated evaluations.
|
* Scripts are [optimized][script optimization] (useful for template-based machine-generated scripts) for repeated evaluations.
|
||||||
|
|
||||||
|
@ -27,8 +27,8 @@ It doesn't attempt to be a new language. For example:
|
|||||||
There is, however, support for simulated [closures] via [currying] a [function pointer] with
|
There is, however, support for simulated [closures] via [currying] a [function pointer] with
|
||||||
captured shared variables.
|
captured shared variables.
|
||||||
|
|
||||||
* **No byte-codes/JIT** - Rhai has an optimized AST-walking interpreter which is fast enough for most usage scenarios.
|
* **No byte-codes/JIT** - Rhai has an optimized AST-walking interpreter which is fast enough for most casual
|
||||||
Essential AST data structures are packed and kept together to maximize cache friendliness.
|
usage scenarios. Essential AST data structures are packed and kept together to maximize cache friendliness.
|
||||||
|
|
||||||
Functions are dispatched based on pre-calculated hashes and accessing variables are mostly through pre-calculated
|
Functions are dispatched based on pre-calculated hashes and accessing variables are mostly through pre-calculated
|
||||||
offsets to the variables file (a [`Scope`]), so it is seldom necessary to look something up by text name.
|
offsets to the variables file (a [`Scope`]), so it is seldom necessary to look something up by text name.
|
||||||
|
@ -52,7 +52,7 @@ the return value, are [`Dynamic`] the types are simply not shown.
|
|||||||
|
|
||||||
A script-defined function always takes dynamic arguments, and the return type is also dynamic:
|
A script-defined function always takes dynamic arguments, and the return type is also dynamic:
|
||||||
|
|
||||||
> `foo(x, y, z)`
|
> `foo(x, y, z) -> Dynamic`
|
||||||
|
|
||||||
probably defined as:
|
probably defined as:
|
||||||
|
|
||||||
@ -71,17 +71,17 @@ is the same as:
|
|||||||
Functions defined in [plugin modules] are the best. They contain all the metadata
|
Functions defined in [plugin modules] are the best. They contain all the metadata
|
||||||
describing the functions.
|
describing the functions.
|
||||||
|
|
||||||
A plugin function `merge`:
|
For example, a plugin function `merge`:
|
||||||
|
|
||||||
> `merge(list: &mut MyStruct<i64>, num: usize, name: &str) -> Option<bool>`
|
> `merge(list: &mut MyStruct<i64>, num: usize, name: &str) -> Option<bool>`
|
||||||
|
|
||||||
Notice that function names do not need to be valid identifiers.
|
Notice that function names do not need to be valid identifiers.
|
||||||
|
|
||||||
An operator defined as a [fallible function] in a [plugin module] via `#[rhai_fn(name="+=", return_raw)]`
|
For example, an operator defined as a [fallible function] in a [plugin module] via
|
||||||
returns `Result<bool, Box<EvalAltResult>>`:
|
`#[rhai_fn(name="+=", return_raw)]` returns `Result<bool, Box<EvalAltResult>>`:
|
||||||
|
|
||||||
> `+=(list: &mut MyStruct<i64>, num: usize, name: &str) -> Result<bool, Box<EvalAltResult>>`
|
> `+=(list: &mut MyStruct<i64>, num: usize, name: &str) -> Result<bool, Box<EvalAltResult>>`
|
||||||
|
|
||||||
A [property getter][getters/setters] defined in a [plugin module]:
|
For example, a [property getter][getters/setters] defined in a [plugin module]:
|
||||||
|
|
||||||
> `get$prop(obj: &mut MyStruct<i64>) -> String`
|
> `get$prop(obj: &mut MyStruct<i64>) -> String`
|
||||||
|
@ -178,22 +178,21 @@ Use `switch` Through Arrays
|
|||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
Another way to work with Rust enums in a `switch` expression is through exposing the internal data
|
Another way to work with Rust enums in a `switch` expression is through exposing the internal data
|
||||||
of each enum variant as a variable-length [array], usually with the name of the variant as
|
(or at least those that act as effective _discriminants_) of each enum variant as a variable-length
|
||||||
the first item for convenience:
|
[array], usually with the name of the variant as the first item for convenience:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::Array;
|
use rhai::Array;
|
||||||
|
|
||||||
engine.register_get("enum_data", |x: &mut Enum| {
|
engine.register_get("enum_data", |x: &mut Enum| {
|
||||||
match x {
|
match x {
|
||||||
Enum::Foo => vec![
|
Enum::Foo => vec![ "Foo".into() ] as Array,
|
||||||
"Foo".into()
|
|
||||||
] as Array,
|
|
||||||
|
|
||||||
Enum::Bar(value) => vec![
|
// Say, skip the data field because it is not
|
||||||
"Bar".into(), (*value).into()
|
// used as a discriminant
|
||||||
] as Array,
|
Enum::Bar(value) => vec![ "Bar".into() ] as Array,
|
||||||
|
|
||||||
|
// Say, all fields act as discriminants
|
||||||
Enum::Baz(val1, val2) => vec![
|
Enum::Baz(val1, val2) => vec![
|
||||||
"Baz".into(), val1.clone().into(), (*val2).into()
|
"Baz".into(), val1.clone().into(), (*val2).into()
|
||||||
] as Array
|
] as Array
|
||||||
@ -208,8 +207,7 @@ Then it is a simple matter to match an enum via the `switch` expression:
|
|||||||
// 'enum_data' creates a variable-length array with 'MyEnum' data
|
// 'enum_data' creates a variable-length array with 'MyEnum' data
|
||||||
let x = switch value.enum_data {
|
let x = switch value.enum_data {
|
||||||
["Foo"] => 1,
|
["Foo"] => 1,
|
||||||
["Bar", 42] => 2,
|
["Bar"] => value.field_1,
|
||||||
["Bar", 123] => 3,
|
|
||||||
["Baz", "hello", false] => 4,
|
["Baz", "hello", false] => 4,
|
||||||
["Baz", "hello", true] => 5,
|
["Baz", "hello", true] => 5,
|
||||||
_ => 9
|
_ => 9
|
||||||
@ -220,10 +218,20 @@ x == 5;
|
|||||||
// Which is essentially the same as:
|
// Which is essentially the same as:
|
||||||
let x = switch [value.type, value.field_0, value.field_1] {
|
let x = switch [value.type, value.field_0, value.field_1] {
|
||||||
["Foo", (), ()] => 1,
|
["Foo", (), ()] => 1,
|
||||||
["Bar", 42, ()] => 2,
|
["Bar", 42, ()] => 42,
|
||||||
["Bar", 123, ()] => 3,
|
["Bar", 123, ()] => 123,
|
||||||
|
:
|
||||||
["Baz", "hello", false] => 4,
|
["Baz", "hello", false] => 4,
|
||||||
["Baz", "hello", true] => 5,
|
["Baz", "hello", true] => 5,
|
||||||
_ => 9
|
_ => 9
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Usually, a helper method returns an array of values that can uniquely determine
|
||||||
|
the switch case based on actual usage requirements - which means that it probably
|
||||||
|
skips fields that contain data instead of discriminants.
|
||||||
|
|
||||||
|
Then `switch` is used to very quickly match through a large number of array shapes
|
||||||
|
and jump to the appropriate case implementation.
|
||||||
|
|
||||||
|
Data fields can then be extracted from the enum independently.
|
||||||
|
@ -26,10 +26,10 @@ Use Closures to Define Methods
|
|||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
[Anonymous functions] or [closures] defined as values for [object map] properties take on
|
[Anonymous functions] or [closures] defined as values for [object map] properties take on
|
||||||
a syntactic shape that resembles very closely that of class methods in an OOP language.
|
a syntactic shape which resembles very closely that of class methods in an OOP language.
|
||||||
|
|
||||||
Closures also _[capture][automatic currying]_ variables from the defining environment, which is a very
|
Closures also _[capture][automatic currying]_ variables from the defining environment, which is a very
|
||||||
common OOP pattern. Capturing is accomplished via a feature called _[automatic currying]_ and
|
common language feature. Capturing is accomplished via a feature called _[automatic currying]_ and
|
||||||
can be turned off via the [`no_closure`] feature.
|
can be turned off via the [`no_closure`] feature.
|
||||||
|
|
||||||
|
|
||||||
@ -59,3 +59,51 @@ factor = 2;
|
|||||||
obj.update(42);
|
obj.update(42);
|
||||||
obj.action(); // prints 84
|
obj.action(); // prints 84
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Simulating Inheritance With Mixin
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
The `fill_with` method of [object maps] can be conveniently used to _polyfill_ default
|
||||||
|
method implementations from a _base class_, as per OOP lingo.
|
||||||
|
|
||||||
|
Do not use the `mixin` method because it _overwrites_ existing fields.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Define base class
|
||||||
|
let BaseClass = #{
|
||||||
|
factor: 1,
|
||||||
|
data: 42,
|
||||||
|
|
||||||
|
get_data: || this.data * 2,
|
||||||
|
update: |x| this.data += x * this.factor
|
||||||
|
};
|
||||||
|
|
||||||
|
let obj = #{
|
||||||
|
// Override base class field
|
||||||
|
factor: 100,
|
||||||
|
|
||||||
|
// Override base class method
|
||||||
|
// Notice that the base class can also be accessed, if in scope
|
||||||
|
get_data: || this.call(BaseClass.get_data) * 999,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill missing fields/methods
|
||||||
|
obj.fill_with(BaseClass);
|
||||||
|
|
||||||
|
// By this point, 'obj' has the following:
|
||||||
|
//
|
||||||
|
// #{
|
||||||
|
// factor: 100
|
||||||
|
// data: 42,
|
||||||
|
// get_data: || this.call(BaseClass.get_data) * 999,
|
||||||
|
// update: |x| this.data += x * this.factor
|
||||||
|
// }
|
||||||
|
|
||||||
|
// obj.get_data() => (this.data (42) * 2) * 999
|
||||||
|
obj.get_data() == 83916;
|
||||||
|
|
||||||
|
obj.update(1);
|
||||||
|
|
||||||
|
obj.data == 142
|
||||||
|
```
|
||||||
|
@ -649,7 +649,7 @@ impl Engine {
|
|||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
script: &str,
|
script: &str,
|
||||||
pos: Position,
|
_pos: Position,
|
||||||
_level: usize,
|
_level: usize,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
self.inc_operations(state)?;
|
self.inc_operations(state)?;
|
||||||
@ -663,7 +663,7 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
if _level > self.max_call_levels() {
|
if _level > self.max_call_levels() {
|
||||||
return Err(Box::new(EvalAltResult::ErrorStackOverflow(pos)));
|
return Err(Box::new(EvalAltResult::ErrorStackOverflow(_pos)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile the script text
|
// Compile the script text
|
||||||
|
Loading…
Reference in New Issue
Block a user