rhai 1.27.0

Embedded scripting for Rust
Documentation
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
use bitflags::bitflags;

#[cfg(feature = "no_std")]
use std::prelude::v1::*;

bitflags! {
    /// Per-step flags for a chain.
    #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct StepFlags: u8 {
        /// The step short-circuits when the receiver is `()`.
        const SKIP_IF_UNIT = 0b_0000_0001;
    }
}

/// One step along `a.b[i].c(x)`.
///
/// Steps live in the program's chain pool rather than in the instruction
/// stream, because a chain is walked by _one_ instruction rather than several.
///
/// It has to be: the walk holds a `&mut` into the container at every level, and
/// a borrow cannot survive a trip round the dispatch loop.
///
/// That is also what makes it correct — Rhai holds the same references, so a
/// mutation partway down a chain lands in the same place rather than in a copy.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Step {
    /// `[i]`, where the index was evaluated onto the operand stack before the
    /// chain instruction ran, at `operand` from the first of them.
    ///
    /// Pre-evaluating mirrors Rhai, which collects every index in a chain into
    /// an array before walking it. It has to happen first: evaluating an index
    /// halfway down would need the operand stack while a borrow of it is live.
    ///
    /// ## Position
    ///
    /// Rhai reports `a[10]` out of bounds against the `10` rather than against
    /// the chain.
    ///
    /// `bracket` is the other position Rhai keeps for a step, and the two are
    /// not interchangeable: `pos` is where the index expression starts and
    /// `bracket` is the `[` in front of it.
    ///
    /// An out-of-bounds index is blamed on the first and indexing something
    /// that cannot be indexed on the second, so `a[0][5]` where `a[0]` is not
    /// indexable names the *second* `[` — the step that failed.
    ///
    /// A chain carrying only one position would name the wrong one.
    Index {
        /// Where the index sits on the operand stack.
        operand: u16,
        /// Per-step flags, such as null-conditional chaining.
        flags: StepFlags,
        /// Where the index expression starts.
        pos: rhai::Position,
        /// The `[` in front of it.
        bracket: rhai::Position,
    },

    /// `.name`, which is a key lookup on a map and a getter call on anything
    /// else — the distinction Rhai makes at runtime, not at parse time.
    Property {
        /// The bare name, for a map key and for error messages.
        name: u32,
        /// `get$name`.
        getter: u32,
        /// `set$name`, for the write-back.
        setter: u32,
        /// Per-step flags, such as null-conditional chaining.
        flags: StepFlags,
        /// Where the property is in the source.
        pos: rhai::Position,
    },

    /// `.name(args)`, with the receiver as the first argument by reference.
    Method {
        /// The name of the method
        name: u32,
        /// How many arguments, not counting the receiver.
        argc: u8,
        /// Where the first of them sits on the operand stack.
        operand: u16,
        /// Per-step flags, such as null-conditional chaining.
        flags: StepFlags,
        /// Where the call is in the source.
        pos: rhai::Position,
    },
}

impl Step {
    /// How many source positions this step carries.
    ///
    /// Two for an index, which keeps the `[` apart from what is inside it.
    #[must_use]
    pub fn position_slots(&self) -> u32 {
        match self {
            Step::Index { .. } => 2,
            Step::Property { .. } | Step::Method { .. } => 1,
        }
    }

    /// Dump the disassembly of the step.
    #[cfg(feature = "internals")]
    pub fn disassemble(&self, program: &crate::grain::Program) -> String {
        match self {
            Step::Index { operand, flags, .. } => {
                format!(
                    "{}[{operand}]",
                    if flags.contains(StepFlags::SKIP_IF_UNIT) {
                        "?"
                    } else {
                        ""
                    }
                )
            }
            Step::Property { name, flags, .. } => {
                format!(
                    ".{}{}",
                    if flags.contains(StepFlags::SKIP_IF_UNIT) {
                        "?"
                    } else {
                        ""
                    },
                    program.name(*name).unwrap()
                )
            }
            Step::Method {
                name,
                argc,
                operand,
                flags,
                ..
            } if *argc > 0 => format!(
                ".{}{}({argc} args from {operand})",
                if flags.contains(StepFlags::SKIP_IF_UNIT) {
                    "?"
                } else {
                    ""
                },
                program.name(*name).unwrap()
            ),
            Step::Method { name, flags, .. } => format!(
                ".{}{}()",
                if flags.contains(StepFlags::SKIP_IF_UNIT) {
                    "?"
                } else {
                    ""
                },
                program.name(*name).unwrap()
            ),
        }
    }
}

/// What a chain does when it gets to the end.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Tail {
    /// Push the value the chain arrived at.
    Read,
    /// Assign the top of the operand stack to it, optionally through an
    /// operator, and push unit.
    Assign {
        /// Index into the op-assignment pool; absent for a plain `=`.
        op: Option<u32>,
    },
}

impl Tail {
    /// Dump the disassembly of the tail.
    #[cfg(feature = "internals")]
    pub fn disassemble(&self, program: &crate::grain::Program) -> String {
        match self {
            Tail::Read => String::new(),
            Tail::Assign { op } => match op {
                Some(op) => format!("{}", program.assign_op(*op).unwrap().disassemble(program)),
                None => "=".to_string(),
            },
        }
    }
}

/// Where a chain starts.
///
/// The distinction is whether the root has an identity to write back into.
///
/// Rhai draws the same line and in the same place: a variable root becomes a
/// `Target` into the scope entry, and anything else is evaluated into a
/// temporary and walked there.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Root {
    /// A local, by slot.
    ///
    /// The only root a chain can write *through*: a mutation partway down has
    /// to land in the scope entry, and walking a copy of it would lose the
    /// mutation.
    Local {
        /// The slot index
        slot: u16,
        /// Names it in `ErrorAssignmentToConstant`.
        name: u32,
    },

    /// A variable no slot addresses, which can be:
    ///
    /// * variable the caller puts in the `Scope`
    /// * resolver return result
    /// * an exported constant from a module
    ///
    /// Whether it can be written through is not known until it is looked up,
    /// which is the whole difference from [`Root::Local`]. Rhai decides the
    /// same way and at the same moment.
    ///
    /// ## Position
    ///
    /// Carries its own position because the lookup can fail and
    /// [`ErrorVariableNotFound`][rhai::EvalAltResult::ErrorVariableNotFound]
    /// is reported against the variable, not the chain.
    ///
    /// This costs nothing extra: chain positions already live in this pool
    /// rather than in the stripping table, for the reason [`Step::pos`] gives.
    Named {
        /// The name of the variable
        name: u32,
        /// Where the variable is in the source.
        pos: rhai::Position,
    },

    /// The frame's receiver.
    ///
    /// `this.push(1)` has to mutate the caller's value, while a temporary would
    /// walk a copy and drop the mutation silently.
    ///
    /// ## Position
    ///
    /// Carries its own position because the chain instruction's table entry is
    /// the `.` or the `[`, while
    /// [`ErrorUnboundThis`][rhai::EvalAltResult::ErrorUnboundThis] is reported
    /// against the `this`.
    ///
    /// [`Root::Named`] carries one for the same reason.
    This {
        /// Where the `this` is in the source.
        pos: rhai::Position,
    },

    /// A value the instruction takes off the operand stack, pushed above the
    /// step operands.
    ///
    /// ## Example
    ///
    /// `[1, 2, 3].len()`, `f().x`, `(a + b).to_string()`.
    ///
    /// ## Mutability
    ///
    /// Nothing is written back, because there is nowhere to write it back to —
    /// and nothing can be assigned to one, because Rhai's parser refuses
    /// `f().x = 1` before this ever sees it.
    Temporary,
}

impl Root {
    /// How many source positions this root carries.
    ///
    /// Only the two that can fail on their own; a slot and a temporary have
    /// nothing to report against.
    #[must_use]
    pub fn position_slots(&self) -> u32 {
        match self {
            Root::Named { .. } | Root::This { .. } => 1,
            Root::Local { .. } | Root::Temporary => 0,
        }
    }
}

/// A whole `a.b[i].c` chain.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Chain {
    /// Where the chain starts.
    pub root: Root,
    /// The steps, in source order.
    pub steps: Vec<Step>,
    /// What happens at the end.
    pub tail: Tail,
    /// How many operand-stack values the *steps* consume, so the VM knows
    /// where they start. Not the whole instruction's appetite — see
    /// [`Chain::consumes`].
    pub operands: u16,
}

impl Chain {
    /// Whether the chain takes a value off the operand stack to store.
    #[must_use]
    pub fn assigns(&self) -> bool {
        matches!(self.tail, Tail::Assign { .. })
    }

    /// Whether the root itself arrives on the operand stack.
    ///
    /// Only a temporary does. The other two are reached where they live — by
    /// slot or by name — and getting this wrong is not a small mistake:
    /// [`Chain::consumes`] is what the verifier models a chunk's whole operand
    /// depth on, and what the VM finds its operands with.
    #[must_use]
    pub fn roots_on_stack(&self) -> bool {
        match self.root {
            Root::Local { .. } | Root::Named { .. } | Root::This { .. } => false,
            Root::Temporary => true,
        }
    }

    /// Everything the instruction takes off the operand stack, pushed in order:
    ///
    /// 1) step operands,
    /// 2) the root,
    /// 3) the value being assigned
    ///
    /// which is Rhai's evaluation order and not the normal reading order:
    /// it collects a chain's indices and arguments *before* it evaluates what
    /// they are being applied to.
    #[must_use]
    pub fn consumes(&self) -> usize {
        self.operands as usize + usize::from(self.roots_on_stack()) + usize::from(self.assigns())
    }

    /// How many source positions this chain carries.
    ///
    /// Also the stride from one chain's sidecar slots to the next one's.
    #[must_use]
    pub fn position_slots(&self) -> u32 {
        self.root.position_slots() + self.steps.iter().map(Step::position_slots).sum::<u32>()
    }

    /// The slot holding step `index`'s own position.
    ///
    /// An index step owns two — this one, then the `[` — and this names the
    /// first. A fault records the step, not which of its positions raised, so
    /// `a[i]` restores to the index expression even where Rhai blamed the
    /// bracket.
    #[must_use]
    pub fn step_slot(&self, index: usize) -> Option<u32> {
        (index < self.steps.len()).then(|| {
            self.root.position_slots()
                + self.steps[..index]
                    .iter()
                    .map(Step::position_slots)
                    .sum::<u32>()
        })
    }

    /// Every source position this chain carries, in the order they are written.
    ///
    /// The one definition of that order, which the writer, the sidecar and
    /// [`Chain::step_slot`] all follow. They must agree exactly: read back
    /// against a different order, a stream restores plausible positions rather
    /// than none.
    pub fn positions(&self) -> impl Iterator<Item = rhai::Position> + '_ {
        let root = match self.root {
            Root::Named { pos, .. } | Root::This { pos } => Some(pos),
            Root::Local { .. } | Root::Temporary => None,
        };

        root.into_iter().chain(
            self.steps
                .iter()
                .flat_map(|step| match step {
                    Step::Index { pos, bracket, .. } => [Some(*pos), Some(*bracket)],
                    Step::Property { pos, .. } | Step::Method { pos, .. } => [Some(*pos), None],
                })
                .flatten(),
        )
    }

    /// The same positions, to write back into.
    pub fn positions_mut(&mut self) -> impl Iterator<Item = &mut rhai::Position> {
        let Self { root, steps, .. } = self;

        let root = match root {
            Root::Named { pos, .. } | Root::This { pos } => Some(pos),
            Root::Local { .. } | Root::Temporary => None,
        };

        root.into_iter().chain(
            steps
                .iter_mut()
                .flat_map(|step| match step {
                    Step::Index { pos, bracket, .. } => [Some(pos), Some(bracket)],
                    Step::Property { pos, .. } | Step::Method { pos, .. } => [Some(pos), None],
                })
                .flatten(),
        )
    }

    /// Dump the disassembly of the entire chain.
    #[cfg(feature = "internals")]
    pub fn disassemble(&self, program: &crate::grain::Program) -> String {
        format!(
            "{:?} {} {} {}",
            self.root,
            match self.root {
                Root::Named { name, .. } | Root::Local { name, .. } => program.name(name).unwrap(),
                Root::This { .. } => "this",
                Root::Temporary => "<temp value>",
            },
            self.steps
                .iter()
                .map(|step| step.disassemble(program))
                .collect::<Vec<_>>()
                .join(" "),
            self.tail.disassemble(program)
        )
    }
}