diff --git a/RELEASES.md b/RELEASES.md index bc08d9dc..5ac71093 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -30,6 +30,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. Enhancements ------------ diff --git a/doc/src/about/features.md b/doc/src/about/features.md index 1f5ca2ef..82bfc65a 100644 --- a/doc/src/about/features.md +++ b/doc/src/about/features.md @@ -27,7 +27,6 @@ Fast * Fairly low compile-time overhead. * 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. diff --git a/doc/src/about/non-design.md b/doc/src/about/non-design.md index 4f01b14a..b0c0cbe9 100644 --- a/doc/src/about/non-design.md +++ b/doc/src/about/non-design.md @@ -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 captured shared variables. -* **No byte-codes/JIT** - Rhai has an optimized AST-walking interpreter which is fast enough for most usage scenarios. - Essential AST data structures are packed and kept together to maximize cache friendliness. +* **No byte-codes/JIT** - Rhai has an optimized AST-walking interpreter which is fast enough for most casual + 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 offsets to the variables file (a [`Scope`]), so it is seldom necessary to look something up by text name. diff --git a/doc/src/engine/get_fn_sig.md b/doc/src/engine/get_fn_sig.md index 69894633..2e54a430 100644 --- a/doc/src/engine/get_fn_sig.md +++ b/doc/src/engine/get_fn_sig.md @@ -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: -> `foo(x, y, z)` +> `foo(x, y, z) -> Dynamic` probably defined as: @@ -71,17 +71,17 @@ is the same as: Functions defined in [plugin modules] are the best. They contain all the metadata describing the functions. -A plugin function `merge`: +For example, a plugin function `merge`: > `merge(list: &mut MyStruct, num: usize, name: &str) -> Option` 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)]` -returns `Result>`: +For example, an operator defined as a [fallible function] in a [plugin module] via +`#[rhai_fn(name="+=", return_raw)]` returns `Result>`: > `+=(list: &mut MyStruct, num: usize, name: &str) -> Result>` -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) -> String` diff --git a/doc/src/patterns/enums.md b/doc/src/patterns/enums.md index a4c9bc0b..c061bab9 100644 --- a/doc/src/patterns/enums.md +++ b/doc/src/patterns/enums.md @@ -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 -of each enum variant as a variable-length [array], usually with the name of the variant as -the first item for convenience: +(or at least those that act as effective _discriminants_) of each enum variant as a variable-length +[array], usually with the name of the variant as the first item for convenience: ```rust use rhai::Array; engine.register_get("enum_data", |x: &mut Enum| { match x { - Enum::Foo => vec![ - "Foo".into() - ] as Array, + Enum::Foo => vec![ "Foo".into() ] as Array, - Enum::Bar(value) => vec![ - "Bar".into(), (*value).into() - ] as Array, + // Say, skip the data field because it is not + // used as a discriminant + Enum::Bar(value) => vec![ "Bar".into() ] as Array, + // Say, all fields act as discriminants Enum::Baz(val1, val2) => vec![ "Baz".into(), val1.clone().into(), (*val2).into() ] 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 let x = switch value.enum_data { ["Foo"] => 1, - ["Bar", 42] => 2, - ["Bar", 123] => 3, + ["Bar"] => value.field_1, ["Baz", "hello", false] => 4, ["Baz", "hello", true] => 5, _ => 9 @@ -220,10 +218,20 @@ x == 5; // Which is essentially the same as: let x = switch [value.type, value.field_0, value.field_1] { ["Foo", (), ()] => 1, - ["Bar", 42, ()] => 2, - ["Bar", 123, ()] => 3, + ["Bar", 42, ()] => 42, + ["Bar", 123, ()] => 123, + : ["Baz", "hello", false] => 4, ["Baz", "hello", true] => 5, _ => 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. diff --git a/doc/src/patterns/oop.md b/doc/src/patterns/oop.md index b0e454c6..7b225c8c 100644 --- a/doc/src/patterns/oop.md +++ b/doc/src/patterns/oop.md @@ -26,10 +26,10 @@ Use Closures to Define Methods ----------------------------- [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 -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. @@ -59,3 +59,51 @@ factor = 2; obj.update(42); 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 +``` diff --git a/src/fn_call.rs b/src/fn_call.rs index 9b1500ef..4fdb78a0 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -649,7 +649,7 @@ impl Engine { state: &mut State, lib: &[&Module], script: &str, - pos: Position, + _pos: Position, _level: usize, ) -> Result> { self.inc_operations(state)?; @@ -663,7 +663,7 @@ impl Engine { #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "unchecked"))] if _level > self.max_call_levels() { - return Err(Box::new(EvalAltResult::ErrorStackOverflow(pos))); + return Err(Box::new(EvalAltResult::ErrorStackOverflow(_pos))); } // Compile the script text