Correct speed claim and others in docs.

This commit is contained in:
Stephen Chung 2020-12-19 17:46:34 +08:00
parent d73f3a1d60
commit 0182b2d3f4
7 changed files with 80 additions and 24 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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