OOP support.

This commit is contained in:
Stephen Chung
2020-06-26 10:39:18 +08:00
parent 259b6d0fcf
commit 175c3ccaec
27 changed files with 498 additions and 234 deletions

View File

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

View File

@@ -37,6 +37,8 @@ Dynamic
* Dynamic dispatch via [function pointers].
* Some support for [OOP].
Safe
----

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
{
"version": "0.15.1",
"version": "0.16.0",
"rootUrl": "",
"rootUrlX": "/rhai"
}

View File

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

View File

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

View File

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

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

View File

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

View File

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