OOP support.
This commit is contained in:
@@ -56,6 +56,7 @@ The Rhai Scripting Language
|
||||
5. [Arrays](language/arrays.md)
|
||||
6. [Object Maps](language/object-maps.md)
|
||||
1. [Parse from JSON](language/json.md)
|
||||
2. [Special Support for OOP](language/object-maps-oop.md)
|
||||
7. [Time-Stamps](language/timestamps.md)
|
||||
3. [Keywords](language/keywords.md)
|
||||
4. [Statements](language/statements.md)
|
||||
@@ -92,14 +93,15 @@ The Rhai Scripting Language
|
||||
8. [Maximum Call Stack Depth](safety/max-call-stack.md)
|
||||
9. [Maximum Statement Depth](safety/max-stmt-depth.md)
|
||||
7. [Advanced Topics](advanced.md)
|
||||
1. [Script Optimization](engine/optimize/index.md)
|
||||
1. [Object-Oriented Programming (OOP)](language/oop.md)
|
||||
2. [Script Optimization](engine/optimize/index.md)
|
||||
1. [Optimization Levels](engine/optimize/optimize-levels.md)
|
||||
2. [Re-Optimize an AST](engine/optimize/reoptimize.md)
|
||||
3. [Eager Function Evaluation](engine/optimize/eager.md)
|
||||
4. [Side-Effect Considerations](engine/optimize/side-effects.md)
|
||||
5. [Volatility Considerations](engine/optimize/volatility.md)
|
||||
6. [Subtle Semantic Changes](engine/optimize/semantics.md)
|
||||
2. [Eval Statement](language/eval.md)
|
||||
3. [Eval Statement](language/eval.md)
|
||||
8. [Appendix](appendix/index.md)
|
||||
1. [Keywords](appendix/keywords.md)
|
||||
2. [Operators](appendix/operators.md)
|
||||
|
@@ -37,6 +37,8 @@ Dynamic
|
||||
|
||||
* Dynamic dispatch via [function pointers].
|
||||
|
||||
* Some support for [OOP].
|
||||
|
||||
Safe
|
||||
----
|
||||
|
||||
|
@@ -13,6 +13,7 @@ It doesn't attempt to be a new language. For example:
|
||||
* No structures/records - define your types in Rust instead; Rhai can seamlessly work with _any Rust type_.
|
||||
|
||||
There is, however, a built-in [object map] type which is adequate for most uses.
|
||||
It is possible to simulate [OOP] by storing [function pointers] in [object map] properties, turning them into _methods_.
|
||||
|
||||
* No first-class functions - Code your functions in Rust instead, and register them with Rhai.
|
||||
|
||||
|
@@ -3,30 +3,31 @@ Keywords List
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
| Keyword | Description | Not available under |
|
||||
| :-------------------: | --------------------------------------- | ------------------- |
|
||||
| `true` | Boolean true literal | |
|
||||
| `false` | Boolean false literal | |
|
||||
| `let` | Variable declaration | |
|
||||
| `const` | Constant declaration | |
|
||||
| `if` | If statement | |
|
||||
| `else` | else block of if statement | |
|
||||
| `while` | While loop | |
|
||||
| `loop` | Infinite loop | |
|
||||
| `for` | For loop | |
|
||||
| `in` | Containment test, part of for loop | |
|
||||
| `continue` | Continue a loop at the next iteration | |
|
||||
| `break` | Loop breaking | |
|
||||
| `return` | Return value | |
|
||||
| `throw` | Throw exception | |
|
||||
| `private` | Mark function private | [`no_function`] |
|
||||
| `import` | Import module | [`no_module`] |
|
||||
| `export` | Export variable | [`no_module`] |
|
||||
| `as` | Alias for variable export | [`no_module`] |
|
||||
| `fn` (lower-case `f`) | Function definition | [`no_function`] |
|
||||
| `Fn` (capital `F`) | Function to create a [function pointer] | [`no_function`] |
|
||||
| `call` | Call a [function pointer] | [`no_function`] |
|
||||
| `type_of` | Get type name of value | |
|
||||
| `print` | Print value | |
|
||||
| `debug` | Print value in debug format | |
|
||||
| `eval` | Evaluate script | |
|
||||
| Keyword | Description | Not available under |
|
||||
| :-------------------: | ---------------------------------------- | :-----------------: |
|
||||
| `true` | Boolean true literal | |
|
||||
| `false` | Boolean false literal | |
|
||||
| `let` | Variable declaration | |
|
||||
| `const` | Constant declaration | |
|
||||
| `if` | If statement | |
|
||||
| `else` | else block of if statement | |
|
||||
| `while` | While loop | |
|
||||
| `loop` | Infinite loop | |
|
||||
| `for` | For loop | |
|
||||
| `in` | Containment test, part of for loop | |
|
||||
| `continue` | Continue a loop at the next iteration | |
|
||||
| `break` | Loop breaking | |
|
||||
| `return` | Return value | |
|
||||
| `throw` | Throw exception | |
|
||||
| `import` | Import module | [`no_module`] |
|
||||
| `export` | Export variable | [`no_module`] |
|
||||
| `as` | Alias for variable export | [`no_module`] |
|
||||
| `private` | Mark function private | [`no_function`] |
|
||||
| `fn` (lower-case `f`) | Function definition | [`no_function`] |
|
||||
| `Fn` (capital `F`) | Function to create a [function pointer] | [`no_function`] |
|
||||
| `call` | Call a [function pointer] | [`no_function`] |
|
||||
| `this` | Reference to base object for method call | [`no_function`] |
|
||||
| `type_of` | Get type name of value | |
|
||||
| `print` | Print value | |
|
||||
| `debug` | Print value in debug format | |
|
||||
| `eval` | Evaluate script | |
|
||||
|
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "0.15.1",
|
||||
"version": "0.16.0",
|
||||
"rootUrl": "",
|
||||
"rootUrlX": "/rhai"
|
||||
}
|
@@ -1,6 +1,8 @@
|
||||
Function Pointers
|
||||
=================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
It is possible to store a _function pointer_ in a variable just like a normal value.
|
||||
In fact, internally a function pointer simply stores the _name_ of the function as a string.
|
||||
|
||||
|
@@ -53,29 +53,6 @@ fn foo() { x } // <- syntax error: variable 'x' doesn't exist
|
||||
```
|
||||
|
||||
|
||||
Arguments Passed by Value
|
||||
------------------------
|
||||
|
||||
Functions defined in script always take [`Dynamic`] parameters (i.e. the parameter can be of any type).
|
||||
Therefore, functions with the same name and same _number_ of parameters are equivalent.
|
||||
|
||||
It is important to remember that all arguments are passed by _value_, so all Rhai script-defined functions
|
||||
are _pure_ (i.e. they never modify their arguments).
|
||||
Any update to an argument will **not** be reflected back to the caller.
|
||||
|
||||
This can introduce subtle bugs, if not careful, especially when using the _method-call_ style.
|
||||
|
||||
```rust
|
||||
fn change(s) { // 's' is passed by value
|
||||
s = 42; // only a COPY of 's' is changed
|
||||
}
|
||||
|
||||
let x = 500;
|
||||
x.change(); // de-sugars to 'change(x)'
|
||||
x == 500; // 'x' is NOT changed!
|
||||
```
|
||||
|
||||
|
||||
Global Definitions Only
|
||||
----------------------
|
||||
|
||||
@@ -106,3 +83,47 @@ A function does not need to be defined prior to being used in a script;
|
||||
a statement in the script can freely call a function defined afterwards.
|
||||
|
||||
This is similar to Rust and many other modern languages, such as JavaScript's `function` keyword.
|
||||
|
||||
|
||||
Arguments Passed by Value
|
||||
------------------------
|
||||
|
||||
Functions defined in script always take [`Dynamic`] parameters (i.e. the parameter can be of any type).
|
||||
Therefore, functions with the same name and same _number_ of parameters are equivalent.
|
||||
|
||||
It is important to remember that all arguments are passed by _value_, so all Rhai script-defined functions
|
||||
are _pure_ (i.e. they never modify their arguments).
|
||||
Any update to an argument will **not** be reflected back to the caller.
|
||||
|
||||
```rust
|
||||
fn change(s) { // 's' is passed by value
|
||||
s = 42; // only a COPY of 's' is changed
|
||||
}
|
||||
|
||||
let x = 500;
|
||||
|
||||
change(x);
|
||||
|
||||
x == 500; // 'x' is NOT changed!
|
||||
```
|
||||
|
||||
|
||||
`this` - Simulating an Object Method
|
||||
-----------------------------------
|
||||
|
||||
Functions can also be called in method-call style. When this is the case, the keyword '`this`'
|
||||
binds to the object in the method call and can be changed.
|
||||
|
||||
```rust
|
||||
fn change() { // not that the object does not need a parameter
|
||||
this = 42; // 'this' binds to the object in method-call
|
||||
}
|
||||
|
||||
let x = 500;
|
||||
|
||||
x.change(); // call 'change' in method-call style, 'this' binds to 'x'
|
||||
|
||||
x == 42; // 'x' is changed!
|
||||
|
||||
change(); // <- error: `this` is unbounded
|
||||
```
|
||||
|
@@ -57,13 +57,13 @@ if the first one already proves the condition wrong.
|
||||
Single boolean operators `&` and `|` always evaluate both operands.
|
||||
|
||||
```rust
|
||||
this() || that(); // that() is not evaluated if this() is true
|
||||
a() || b(); // b() is not evaluated if a() is true
|
||||
|
||||
this() && that(); // that() is not evaluated if this() is false
|
||||
a() && b(); // b() is not evaluated if a() is false
|
||||
|
||||
this() | that(); // both this() and that() are evaluated
|
||||
a() | b(); // both a() and b() are evaluated
|
||||
|
||||
this() & that(); // both this() and that() are evaluated
|
||||
a() & b(); // both a() and b() are evaluated
|
||||
```
|
||||
|
||||
Compound Assignment Operators
|
||||
|
23
doc/src/language/object-maps-oop.md
Normal file
23
doc/src/language/object-maps-oop.md
Normal file
@@ -0,0 +1,23 @@
|
||||
Special Support for OOP via Object Maps
|
||||
======================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
[Object maps] can be used to simulate object-oriented programming ([OOP]) by storing data
|
||||
as properties and methods as properties holding [function pointers].
|
||||
|
||||
If an [object map]'s property holding a [function pointer] is called in method-call style,
|
||||
it calls the function referenced by the [function pointer].
|
||||
|
||||
```rust
|
||||
fn do_action(x) { print(this + x); } // 'this' binds to the object when called
|
||||
|
||||
let obj = #{
|
||||
data: 40,
|
||||
action: Fn("do_action") // 'action' holds a function pointer to 'do_action'
|
||||
};
|
||||
|
||||
obj.action(2); // prints 42
|
||||
|
||||
obj.action.call(2); // <- the above de-sugars to this
|
||||
```
|
44
doc/src/language/oop.md
Normal file
44
doc/src/language/oop.md
Normal file
@@ -0,0 +1,44 @@
|
||||
Object-Oriented Programming (OOP)
|
||||
================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Rhai does not have _objects_ per se, but it is possible to _simulate_ object-oriented programming.
|
||||
|
||||
|
||||
Use [Object Maps] to Simulate OOP
|
||||
--------------------------------
|
||||
|
||||
Rhai's [object maps] has [special support for OOP]({{rootUrl}}/language/object-maps-oop.md).
|
||||
|
||||
| Rhai concept | Maps to OOP |
|
||||
| ----------------------------------------------------- | :---------: |
|
||||
| [Object maps] | objects |
|
||||
| [Object map] properties holding values | properties |
|
||||
| [Object map] properties that hold [function pointers] | methods |
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
// Define the object
|
||||
let obj = #{
|
||||
data: 0,
|
||||
increment: Fn("add"), // when called, 'this' binds to 'obj'
|
||||
update: Fn("update"), // when called, 'this' binds to 'obj'
|
||||
action: Fn("action") // when called, 'this' binds to 'obj'
|
||||
};
|
||||
|
||||
// Define functions
|
||||
fn add(x) { this.data += x; } // update using 'this'
|
||||
fn update(x) { this.data = x; } // update using 'this'
|
||||
fn action() { print(this.data); } // access properties of 'this'
|
||||
|
||||
// Use the object
|
||||
obj.increment(1);
|
||||
obj.action(); // prints 1
|
||||
|
||||
obj.update(42);
|
||||
obj.action(); // prints 42
|
||||
```
|
@@ -77,6 +77,9 @@
|
||||
|
||||
[`eval`]: {{rootUrl}}/language/eval.md
|
||||
|
||||
[OOP]: {{rootUrl}}/language/oop.md
|
||||
|
||||
|
||||
[maximum statement depth]: {{rootUrl}}/safety/max-stmt-depth.md
|
||||
[maximum call stack depth]: {{rootUrl}}/safety/max-call-stack.md
|
||||
[maximum number of operations]: {{rootUrl}}/safety/max-operations.md
|
||||
|
@@ -97,16 +97,17 @@ println!("result: {}", result.field); // prints 42
|
||||
Method-Call Style vs. Function-Call Style
|
||||
----------------------------------------
|
||||
|
||||
In fact, any function with a first argument that is a `&mut` reference can be used
|
||||
Any function with a first argument that is a `&mut` reference can be used
|
||||
as method calls because internally they are the same thing: methods on a type is
|
||||
implemented as a functions taking a `&mut` first argument.
|
||||
This design is similar to Rust.
|
||||
|
||||
```rust
|
||||
fn foo(ts: &mut TestStruct) -> i64 {
|
||||
ts.field
|
||||
}
|
||||
|
||||
engine.register_fn("foo", foo); // register ad hoc function with correct signature
|
||||
engine.register_fn("foo", foo); // register a Rust native function
|
||||
|
||||
let result = engine.eval::<i64>(
|
||||
"let x = new_ts(); x.foo()" // 'foo' can be called like a method on 'x'
|
||||
|
Reference in New Issue
Block a user