reorganize module

This commit is contained in:
Timur Gordon
2025-04-04 08:28:07 +02:00
parent 1ea37e2e7f
commit 939b6b4e57
375 changed files with 7580 additions and 191 deletions

View File

@@ -1,54 +0,0 @@
Break-Points
============
{{#include ../../links.md}}
A _break-point_ **always** stops the current evaluation and calls the [debugging][debugger]
callback.
A break-point is represented by the `debugger::BreakPoint` type, which is an `enum` with
the following variants.
| `BreakPoint` variant | Not available under | Description |
| ---------------------------------------- | :-----------------: | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `AtPosition { source, pos, enabled }` | [`no_position`] | breaks at the specified position in the specified source (empty if none);<br/>if `pos` is at beginning of line, breaks anywhere on the line |
| `AtFunctionName { name, enabled }` | | breaks when a function matching the specified name is called (can be [operator]) |
| `AtFunctionCall { name, args, enabled }` | | breaks when a function matching the specified name (can be [operator]) and the specified number of arguments is called |
| `AtProperty { name, enabled }` | [`no_object`] | breaks at the specified property access |
Access Break-Points
-------------------
The following [`debugger::Debugger`] methods allow access to break-points for manipulation.
| Method | Return type | Description |
| ------------------ | :--------------------: | ------------------------------------------------- |
| `break_points` | `&[BreakPoint]` | returns a slice of all `BreakPoint`'s |
| `break_points_mut` | `&mut Vec<BreakPoint>` | returns a mutable reference to all `BreakPoint`'s |
Example
-------
```rust
use rhai::debugger::*;
let debugger = &mut context.global_runtime_state_mut().debugger_mut();
// Get number of break-points.
let num_break_points = debugger.break_points().len();
// Add a new break-point on calls to 'foo(_, _, _)'
debugger.break_points_mut().push(
BreakPoint::AtFunctionCall { name: "foo".into(), args: 3 }
);
// Display all break-points
for bp in debugger.break_points().iter() {
println!("{bp}");
}
// Clear all break-points
debugger.break_points_mut().clear();
```

View File

@@ -1,32 +0,0 @@
Call Stack
==========
{{#include ../../links.md}}
```admonish info.side.wide "Call stack frames"
Each "frame" in the call stack corresponds to one layer of [function] call (script-defined
or native Rust).
A call stack frame has the type `debugger::CallStackFrame`.
```
The [debugger] keeps a _call stack_ of [function] calls with argument values.
This call stack can be examined to determine the control flow at any particular point.
The `Debugger::call_stack` method returns a slice of all call stack frames.
```rust
use rhai::debugger::*;
let debugger = &mut context.global_runtime_state().debugger();
// Get depth of the call stack.
let depth = debugger.call_stack().len();
// Display all function calls
for frame in debugger.call_stack().iter() {
println!("{frame}");
}
```

View File

@@ -1,110 +0,0 @@
Register with the Debugger
==========================
{{#include ../../links.md}}
Hooking up a debugging interface is as simple as providing closures to the [`Engine`]'s built-in
debugger via `Engine::register_debugger`.
```rust
use rhai::debugger::{ASTNode, DebuggerCommand};
let mut engine = Engine::new();
engine.register_debugger(
// Provide a callback to initialize the debugger state
|engine, mut debugger| {
debugger.set_state(...);
debugger
},
// Provide a callback for each debugging step
|context, event, node, source, pos| {
...
DebuggerCommand::StepOver
}
);
```
~~~admonish tip.small "Tip: Accessing the `Debugger`"
The type `debugger::Debugger` allows for manipulating [break-points], among others.
The [`Engine`]'s debugger instance can be accessed via `context.global_runtime_state().debugger()` (immutable)
or `context.global_runtime_state_mut().debugger_mut()` (mutable).
~~~
Callback Functions Signature
----------------------------
There are two callback functions to register for the debugger.
The first is simply a function to initialize the state of the debugger with the following signature.
> ```rust
> Fn(&Engine, debugger::Debugger) -> debugger::Debugger
> ```
The second callback is a function which will be called by the debugger during each step, with the
following signature.
> ```rust
> Fn(context: EvalContext, event: debugger::DebuggerEvent, node: ASTNode, source: &str, pos: Position) -> Result<debugger::DebuggerCommand, Box<EvalAltResult>>
> ```
where:
| Parameter | Type | Description |
| --------- | :-------------: | ---------------------------------------------------------------------------------------------------------------------------- |
| `context` | [`EvalContext`] | the current _evaluation context_ |
| `event` | `DebuggerEvent` | an `enum` indicating the event that triggered the debugger |
| `node` | `ASTNode` | an `enum` with two variants: `Expr` or `Stmt`, corresponding to the current expression node or statement node in the [`AST`] |
| `source` | `&str` | the source of the current [`AST`], or empty if none |
| `pos` | `Position` | position of the current node, same as `node.position()` |
and [`EvalContext`] is a type that encapsulates the current _evaluation context_.
### Event
The `event` parameter of the second closure passed to `Engine::register_debugger` contains a
`debugger::DebuggerEvent` which is an `enum` with the following variants.
| `DebuggerEvent` variant | Description |
| -------------------------------- | ----------------------------------------------------------------------------------------------- |
| `Start` | the debugger is triggered at the beginning of evaluation |
| `Step` | the debugger is triggered at the next step of evaluation |
| `BreakPoint(`_n_`)` | the debugger is triggered by the _n_-th [break-point] |
| `FunctionExitWithValue(`_r_`)` | the debugger is triggered by a function call returning with value _r_ which is `&Dynamic` |
| `FunctionExitWithError(`_err_`)` | the debugger is triggered by a function call exiting with error _err_ which is `&EvalAltResult` |
| `End` | the debugger is triggered at the end of evaluation |
### Return value
```admonish tip.side.wide "Tip: Initialization"
When a script starts evaluation, the debugger always stops at the very _first_ [`AST`] node
with the `event` parameter set to `DebuggerStatus::Start`.
This allows initialization to be done (e.g. setting up [break-points]).
```
The second closure passed to `Engine::register_debugger` will be called when stepping into or over
expressions and statements, or when [break-points] are hit.
The return type of the closure is `Result<debugger::DebuggerCommand, Box<EvalAltResult>>`.
If an error is returned, the script evaluation at that particular instance returns with that
particular error. It is thus possible to _abort_ the script evaluation by returning an error that is
not _catchable_, such as `EvalAltResult::ErrorTerminated`.
If no error is returned, then the return `debugger::DebuggerCommand` variant determines the
continued behavior of the debugger.
| `DebuggerCommand` variant | Behavior | `gdb` equivalent |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | :--------------: |
| `Continue` | continue with normal script evaluation | `continue` |
| `StepInto` | run to the next expression or statement, diving into functions | `step` |
| `StepOver` | run to the next expression or statement, skipping over functions | |
| `Next` | run to the next statement, skipping over functions | `next` |
| `FunctionExit` | run to the end of the current function call; debugger is triggered _before_ the function call returns and the [`Scope`] cleared | `finish` |

View File

@@ -1,50 +0,0 @@
Debugging Interface
===================
{{#include ../../links.md}}
For systems open to external user-created scripts, it is usually desirable to provide a _debugging_
experience to the user. The alternative is to provide a custom implementation of [`debug`] via
`Engine::on_debug` that traps debug output to show in a side panel, for example, which is actually
extremely simple.
Nevertheless, in some systems, it may not be convenient, or even possible, for the user to debug his
or her scripts simply via good-old [`print`] or [`debug`] statements &ndash; the system does not
have any facility for printed output, for instance.
Or the system may require more advanced debugging facilities than mere [`print`] statements &ndash;
such as [break-points].
For these advanced scenarios, Rhai contains a _Debugging_ interface, turned on via the [`debugging`]
feature (which implies the [`internals`] feature).
The debugging interface resides under the `debugger` sub-module.
```admonish tip.small "The Rhai Debugger"
The [`rhai-dbg`]({{repoHome}}/src/bin/rhai-dbg.rs) bin tool shows a simple example of
employing the debugging interface to create a debugger for Rhai scripts!
```
Built-in Functions
------------------
The following functions (defined in the [`DebuggingPackage`][built-in packages] but excluded when
using a [raw `Engine`]) provides runtime information for debugging purposes.
| Function | Parameter(s) | Not available under | Description |
| ------------ | ------------ | :---------------------------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `back_trace` | _none_ | [`no_function`], [`no_index`] | returns an [array] of [object maps] or [strings], each containing one level of [function] call;</br>returns an empty [array] if no [debugger] is registered |
```rust
// This recursive function prints its own call stack during each run
fn foo(x) {
print(back_trace()); // prints the current call stack
if x > 0 {
foo(x - 1)
}
}
```

View File

@@ -1,92 +0,0 @@
Implement a Debugging Server
============================
Sometimes it is desirable to embed a debugging _server_ inside the application such that an external
debugger interface can connect to the application's running instance at runtime.
This way, when scripts are run within the application, it is easy for an external interface to debug
those scripts as they run.
Such connections may take the form of any communication channel, for example a TCP/IP connection, a
named pipe, or an MPSC channel.
Example
-------
### Server side
The following example assumes bi-direction, blocking messaging channels, such as a WebSocket
connection, with a server that accepts connections and creates those channels.
```rust
use rhai::debugger::{ASTNode, DebuggerCommand};
let mut engine = Engine::new();
engine.register_debugger(
// Use the initialization callback to set up the communications channel
// and listen to it
|engine, mut debugger| {
// Create server that will listen to requests
let mut server = MyCommServer::new();
server.listen("localhost:8080");
// Wrap it up in a shared locked cell so it can be 'Clone'
let server = Rc::new(RefCell::new(server));
// Store the channel in the debugger state
debugger.set_state(Dynamic::from(server));
debugger
},
// Trigger the server during each debugger stop point
|context, event, node, source, pos| {
// Get the state
let mut state = context.tag_mut();
// Get the server
let mut server = state.write_lock::<MyCommServer>().unwrap();
// Send the event to the server - blocking call
server.send_message(...);
// Receive command - blocking call
match server.receive_message() {
None => DebuggerCommand::StepOver,
// Decode command
Ok(...) => { ... }
Ok(...) => { ... }
Ok(...) => { ... }
Ok(...) => { ... }
Ok(...) => { ... }
:
:
}
}
);
```
### Client side
The client can be any system that can work with WebSockets for messaging.
```js
// Connect to the application's debugger
let webSocket = new WebSocket("wss://localhost:8080");
webSocket.on_message = (event) => {
let msg = JSON.parse(event.data);
switch msg.type {
// handle debugging events from the application...
case "step": {
:
}
:
:
}
};
// Send command to the application
webSocket.send("step-over");
```

View File

@@ -1,67 +0,0 @@
Debugger State
==============
{{#include ../../links.md}}
Sometimes it is useful to keep a persistent _state_ within the [debugger].
The `Engine::register_debugger` API accepts a function that returns the initial value of the
[debugger's][debugger] state, which is a [`Dynamic`] and can hold any value.
This state value is the stored into the [debugger]'s custom state.
Access the Debugger State
-------------------------
Use `EvalContext::global_runtime_state().debugger()` (immutable) or
`EvalContext::global_runtime_state_mut().debugger_mut()` (mutable) to gain access to the current
[`debugger::Debugger`] instance.
The following [`debugger::Debugger`] methods allow access to the custom [debugger] state.
| Method | Parameter type | Return type | Description |
| ----------- | :-------------------------------: | :-------------------------: | ----------------------------------------------- |
| `state` | _none_ | [`&Dynamic`][`Dynamic`] | returns the custom state |
| `state_mut` | _none_ | [`&mut Dynamic`][`Dynamic`] | returns a mutable reference to the custom state |
| `set_state` | [`impl Into<Dynamic>`][`Dynamic`] | _none_ | sets the value of the custom state |
Example
-------
```rust
engine.register_debugger(
|engine, mut debugger| {
// Say, use an object map for the debugger state
let mut state = Map::new();
// Initialize properties
state.insert("hello".into(), 42_64.into());
state.insert("foo".into(), false.into());
debugger.set_state(state);
debugger
},
|context, node, source, pos| {
// Print debugger state - which is an object map
let state = context.global_runtime_state().debugger().state();
println!("Current state = {state}");
// Get the state as an object map
let mut state = context.global_runtime_state_mut()
.debugger_mut().state_mut()
.write_lock::<Map>().unwrap();
// Read state
let hello = state.get("hello").unwrap().as_int().unwrap();
// Modify state
state.insert("hello".into(), (hello + 1).into());
state.insert("foo".into(), true.into());
state.insert("something_new".into(), "hello, world!".into());
// Continue with debugging
Ok(DebuggerCommand::StepInto)
}
);
```