Refactor.
This commit is contained in:
141
doc/src/language/fn-namespaces.md
Normal file
141
doc/src/language/fn-namespaces.md
Normal file
@@ -0,0 +1,141 @@
|
||||
Function Namespaces
|
||||
==================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Each Function is a Separate Compilation Unit
|
||||
-------------------------------------------
|
||||
|
||||
[Functions] in Rhai are _pure_ and they form individual _compilation units_.
|
||||
This means that individual functions can be separated, exported, re-grouped, imported,
|
||||
and generally mix-'n-match-ed with other completely unrelated scripts.
|
||||
|
||||
For example, the `AST::merge` method allows merging all functions in one [`AST`] into another,
|
||||
forming a new, combined, group of functions.
|
||||
|
||||
In general, there are two types of _namespaces_ where functions are looked up:
|
||||
|
||||
| Namespace | Source | Lookup method | How Many |
|
||||
| --------- | ---------------------------------------------------------------------- | --------------------------------- | :----------------------: |
|
||||
| Global | `Engine::register_XXX` API, [`AST`] being evaluated, [packages] loaded | Simple function name | One |
|
||||
| Module | [`Module`] | Namespace-qualified function name | As many as [`import`]-ed |
|
||||
|
||||
|
||||
Global Namespace
|
||||
----------------
|
||||
|
||||
There is one _global_ namespace for every [`Engine`], which includes:
|
||||
|
||||
* All the native Rust functions registered via the `Engine::register_XXX` API.
|
||||
|
||||
* All the Rust functions defined in [packages] that are loaded into the [`Engine`].
|
||||
|
||||
In addition, during evaluation of an [`AST`], all script-defined functions bundled together within
|
||||
the [`AST`] are added to the global namespace and override any existing registered functions of
|
||||
the same names and number of parameters.
|
||||
|
||||
Anywhere in a Rhai script, when a function call is made, it is searched within the global namespace.
|
||||
Therefore, function calls in Rhai are _late_ bound - meaning that the function called cannot be
|
||||
determined or guaranteed and there is no way to _lock down_ the function being called.
|
||||
This aspect is very similar to JavaScript before ES6 modules.
|
||||
|
||||
```rust
|
||||
// Compile a script into AST
|
||||
let ast1 = engine.compile(
|
||||
r#"
|
||||
fn message() { "Hello!" } // greeting message
|
||||
|
||||
fn say_hello() {
|
||||
print(message()); // prints message
|
||||
}
|
||||
|
||||
say_hello();
|
||||
"#
|
||||
)?;
|
||||
|
||||
// Compile another script with an overriding function
|
||||
let ast2 = engine.compile(r#"fn message() { "Boo!" }"#)?;
|
||||
|
||||
// Merge the two AST's
|
||||
let ast = ast1.merge(ast2); // 'message' will be overwritten
|
||||
|
||||
engine.consume_ast(&ast)?; // prints 'Boo!'
|
||||
```
|
||||
|
||||
Therefore, care must be taken when _cross-calling_ functions to make sure that the correct
|
||||
functions are called.
|
||||
|
||||
The only practical way to ensure that a function is a correct one is to use [modules] -
|
||||
i.e. define the function in a separate module and then [`import`] it:
|
||||
|
||||
```rust
|
||||
message.rhai:
|
||||
|
||||
fn message() { "Hello!" }
|
||||
|
||||
script.rhai:
|
||||
|
||||
fn say_hello() {
|
||||
import "message" as msg;
|
||||
print(msg::message());
|
||||
}
|
||||
say_hello();
|
||||
```
|
||||
|
||||
|
||||
Module Namespaces
|
||||
-----------------
|
||||
|
||||
[Modules] can be dynamically loaded into a Rhai script using the [`import`] keyword.
|
||||
When that happens, functions defined within the [module] can be called with a _qualified_ name.
|
||||
|
||||
There is a catch, though, if functions in a module script refer to global functions
|
||||
defined _within the script_. When called later, those functions will be searched in the
|
||||
current global namespace and may not be found.
|
||||
|
||||
```rust
|
||||
greeting.rhai:
|
||||
|
||||
fn message() { "Hello!" };
|
||||
|
||||
fn say_hello() { print(message()); }
|
||||
|
||||
say_hello(); // 'message' is looked up in the global namespace
|
||||
|
||||
script.rhai:
|
||||
|
||||
import "greeting" as g;
|
||||
g::say_hello(); // <- error: function not found - 'message'
|
||||
```
|
||||
|
||||
In the example above, although the module `greeting.rhai` loads fine (`"Hello!"` is printed),
|
||||
the subsequent call using the _namespace-qualified_ function name fails to find the same function
|
||||
'`message`' which now essentially becomes `g::message`. The call fails as there is no more
|
||||
function named '`message`' in the global namespace.
|
||||
|
||||
Therefore, when writing functions for a [module], make sure that those functions are as _pure_
|
||||
as possible and avoid cross-calling them from each other. A [function pointer] is a valid technique
|
||||
to call another function within a module-defined function:
|
||||
|
||||
```rust
|
||||
greeting.rhai:
|
||||
|
||||
fn message() { "Hello!" };
|
||||
|
||||
fn say_hello(msg_func) { // 'msg_func' is a function pointer
|
||||
print(msg_func.call()); // call via the function pointer
|
||||
}
|
||||
|
||||
say_hello(); // 'message' is looked up in the global namespace
|
||||
|
||||
script.rhai:
|
||||
|
||||
import "greeting" as g;
|
||||
|
||||
fn my_msg() {
|
||||
import "greeting" as g; // <- must import again here...
|
||||
g::message() // <- ... otherwise will not find module 'g'
|
||||
}
|
||||
|
||||
g::say_hello(Fn("my_msg")); // prints 'Hello!'
|
||||
```
|
@@ -54,7 +54,34 @@ let fn_name = "hello"; // the function name does not have to exist yet
|
||||
|
||||
let hello = Fn(fn_name + "_world");
|
||||
|
||||
hello.call(0); // error: function not found - "hello_world (i64)"
|
||||
hello.call(0); // error: function not found - 'hello_world (i64)'
|
||||
```
|
||||
|
||||
|
||||
Global Namespace Only
|
||||
--------------------
|
||||
|
||||
Because of their dynamic nature, function pointers cannot refer to functions in a _module_ [namespace][function namespace]
|
||||
(i.e. functions in [`import`]-ed modules). They can only refer to functions within the global [namespace][function namespace].
|
||||
See [function namespaces] for more details.
|
||||
|
||||
```rust
|
||||
import "foo" as f; // assume there is 'f::do_something()'
|
||||
|
||||
f::do_something(); // works!
|
||||
|
||||
let p = Fn("f::do_something");
|
||||
|
||||
p.call(); // error: function not found - 'f::do_something'
|
||||
|
||||
fn do_something_now() { // call it from a local function
|
||||
import "foo" as f;
|
||||
f::do_something();
|
||||
}
|
||||
|
||||
let p = Fn("do_something_now");
|
||||
|
||||
p.call(); // works!
|
||||
```
|
||||
|
||||
|
||||
|
7
doc/src/language/index.md
Normal file
7
doc/src/language/index.md
Normal file
@@ -0,0 +1,7 @@
|
||||
Rhai Language Reference
|
||||
======================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
This section outlines the Rhai language.
|
||||
|
@@ -3,9 +3,9 @@ Call Method as Function
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Property getters/setters and methods in a Rust custom type registered with the [`Engine`] can be called
|
||||
Property [getters/setters] and [methods][custom types] in a Rust custom type registered with the [`Engine`] can be called
|
||||
just like a regular function. In fact, like Rust, property getters/setters and object methods
|
||||
are registered as regular functions in Rhai that take a first `&mut` parameter.
|
||||
are registered as regular [functions] in Rhai that take a first `&mut` parameter.
|
||||
|
||||
Unlike functions defined in script (for which all arguments are passed by _value_),
|
||||
native Rust functions may mutate the object (or the first argument if called in normal function call style).
|
||||
|
@@ -3,7 +3,7 @@ Create a Module from an AST
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
It is easy to convert a pre-compiled `AST` into a module: just use `Module::eval_ast_as_new`.
|
||||
It is easy to convert a pre-compiled [`AST`] into a module: just use `Module::eval_ast_as_new`.
|
||||
|
||||
Don't forget the [`export`] statement, otherwise there will be no variables exposed by the module
|
||||
other than non-[`private`] functions (unless that's intentional).
|
||||
|
@@ -3,9 +3,9 @@ Export Variables, Functions and Sub-Modules in Module
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
A _module_ is a single script (or pre-compiled `AST`) containing global variables, functions and sub-modules.
|
||||
A _module_ is a single script (or pre-compiled [`AST`]) containing global variables, functions and sub-modules.
|
||||
|
||||
A module can be created from a script via the `Module::eval_ast_as_new` method. When given an `AST`,
|
||||
A module can be created from a script via the `Module::eval_ast_as_new` method. When given an [`AST`],
|
||||
it is first evaluated, then the following items are exposed as members of the new module:
|
||||
|
||||
* Global variables - essentially all variables that remain in the [`Scope`] at the end of a script run - that are exported. Variables not exported (via the `export` statement) remain hidden.
|
||||
|
@@ -3,7 +3,7 @@ Function Overloading
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Functions defined in script can be _overloaded_ by _arity_ (i.e. they are resolved purely upon the function's _name_
|
||||
[Functions] defined in script can be _overloaded_ by _arity_ (i.e. they are resolved purely upon the function's _name_
|
||||
and _number_ of parameters, but not parameter _types_ since all parameters are the same type - [`Dynamic`]).
|
||||
|
||||
New definitions _overwrite_ previous definitions of the same name and number of parameters.
|
||||
|
Reference in New Issue
Block a user