1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
//! _(grain)_ A bytecode VM for Rhai.
//!
//! Rhai evaluates by walking its AST, which the parser allocates a node at a
//! time — so holding a script costs in proportion to how much program it is,
//! and the parser's peak is higher again than what it settles at. That is what
//! caps script size on a small target long before anything else does.
//! Rhai Grain compiles the tree to a flat instruction stream that can be
//! produced elsewhere and loaded without a parser.
//!
//! `tests/grain/allocation.rs` measures both ends of that with a tracking
//! allocator.
//!
//! Execution reuses the host `Engine`: `Dynamic` stays the value type and every
//! registered function is dispatched by Rhai itself. Only control flow, local
//! variable access and operator fast paths are reimplemented.
//!
//! A program that has been lowered all the way through can be written out with
//! [`Program::write`] and read back with [`Program::read`] — see [`mod@format`].
//! That is the artifact the device loads, and the reason the tree never has to
//! exist there.
//!
//! Coverage is total from the start, by construction rather than by effort.
//! Anything the compiler cannot yet lower is kept as an AST fragment and handed
//! back to Rhai's walker through [`bytecode::Op::EvalAst`], so a `Program`
//! always means the same thing as the `AST` it came from. Progress is measured
//! by [`Program::residual_count`] falling, not by constructs becoming legal.
//!
//! # Compiling and running
//!
//! The `Engine` does the parsing and, at runtime, all the dispatching; the VM
//! only replaces the walk between those two.
//!
//! ```
//! use rhai::grain::{Compiler, Vm};
//! use rhai::{Engine, Scope};
//!
//! let engine = Engine::new();
//! let ast = engine.compile("let total = 0; for i in 0..10 { total += i; } total")?;
//!
//! let program = Compiler::new().compile(&ast);
//!
//! // The `Scope` is the caller's locals: a script declares are left in it,
//! // exactly as `Engine::eval_with_scope` would.
//! let mut scope = Scope::new();
//! let value = Vm::new(&engine).eval_with_scope(&mut scope, &program)?;
//!
//! assert_eq!(value.as_int().unwrap(), 45);
//! # Ok::<_, Box<rhai::EvalAltResult>>(())
//! ```
//!
//! # Shipping an artifact
//!
//! The point of the byte encoding: compile on a host, run somewhere that never
//! sees the source. A loaded `Program` borrows its instructions from the bytes,
//! so nothing it retains grows with how long the script is.
//!
//! ```
//! use rhai::grain::{Compiler, Program, Vm};
//! use rhai::{Engine, Scope};
//!
//! let engine = Engine::new();
//!
//! // On the host.
//! let ast = engine.compile("let x = 6; x * 7")?;
//! let program = Compiler::new().compile(&ast);
//!
//! // `write` refuses a program still holding AST fragments, so this is also
//! // the check that the script lowered all the way through.
//! assert_eq!(program.residual_count(), 0);
//! let bytes = program.write().expect("no residuals, so it is writable");
//!
//! // On the device, with no parser and no `AST` in sight.
//! let loaded = Program::read(&bytes).expect("written by this build");
//! let value = Vm::new(&engine).eval(&loaded)?;
//!
//! assert_eq!(value.as_int().unwrap(), 42);
//! # Ok::<_, Box<rhai::EvalAltResult>>(())
//! ```
//!
//! Diagnostics are separable: [`Program::write_stripped`] hands back the
//! artifact and a [`Sidecar`] separately, so the device carries only the first.
//! A failure comes back as one [`Fault`] per frame, and the host resolves those
//! against the sidecar it kept — a stack of addresses on one side, a symbol
//! file on the other, as a crash reporter does.
//!
//! The example below needs script functions to have two frames, positions to
//! resolve them to, and a division by zero that raises rather than panicking,
//! so it is compiled only where all three exist.
//! # Debugging
//!
//! A `debugging` build marks every statement, and the VM stops at the markers:
//! `back_trace`, stepping, break-points by position and the function-exit
//! events all work against a chunk.
//!
//! A statement is as fine as the grain gets. Rhai's walker stops at every
//! *expression* too, which is a node a compiled program no longer has — so a
//! step lands on the next statement rather than part way through the one it is
//! on, and a break-point on a function name, a call's arity or a property never
//! matches, because what a marker hands the callback is a synthetic `Noop` and
//! not the call. A break-point by position covers the same line.
//!
//! The markers are the one part of a program that a shipping build does not
//! compile: a device with no callback to call has nothing to stop for. They cost
//! about six bytes per statement where they are compiled — `tests/grain/format.rs`
//! measures it — and an artifact written without them still runs anywhere, it
//! simply cannot be stopped.
// A VM that runs untrusted bytecode has no business containing any, and saying
// so here makes it the compiler's problem rather than a promise. `crates/
// rhaigrain-pos` declares the same.
pub use Compiler;
pub use ;
pub use ;
pub use ;