Skip to main content

stack_assembly/
effect.rs

1/// # An event triggered by scripts, to signal a specific condition
2///
3/// Effects moderate the communication between script and host. The effect
4/// itself only relays _which_ effect has triggered, but that may signal to
5/// the host that a different communication channel (like operand stack or
6/// memory) is ready to be accessed.
7///
8/// ## Handling Effects
9///
10/// The host may handle effects however it wishes. But since most effects
11/// signal error conditions that the script would not expect to recover
12/// from, a well-behaving host must be careful not to handle effects in
13/// a way that make reasoning about the script's behavior difficult.
14///
15/// Abandoning the evaluation and reporting an error in the appropriate
16/// manner, is the only reasonable way to handle most effects. The
17/// exception to that is [`Effect::Yield`], which does not signal an error
18/// condition. A script would expect to continue afterwards.
19///
20/// To make that possible, the host must clear the effect by calling
21/// [`Eval::clear_effect`].
22///
23/// ### Example
24///
25/// ```
26/// use stack_assembly::{Effect, Eval, Script};
27///
28/// // This script increments a number in a loop, yielding control to the
29/// // host every time it did so.
30/// let script = Script::compile("
31///     0
32///
33///     increment:
34///         1 +
35///         yield
36///         @increment jump
37/// ");
38///
39/// let mut eval = Eval::new();
40///
41/// // When running the script for the first time, we expect that it has
42/// // incremented the number once, before yielding.
43/// let (effect, _) = eval.run(&script);
44/// assert_eq!(effect, Effect::Yield);
45/// assert_eq!(eval.operand_stack.to_u32_slice(), &[1]);
46///
47/// // To allow the script to continue, we must clear the effect.
48/// eval.clear_effect();
49///
50/// // Since we handled the effect correctly, we can now assume that the
51/// // script has incremented the number a second time, before yielding
52/// // again.
53/// let (effect, _) = eval.run(&script);
54/// assert_eq!(effect, Effect::Yield);
55/// assert_eq!(eval.operand_stack.to_u32_slice(), &[2]);
56/// ```
57///
58/// [`Eval::clear_effect`]: crate::Eval::clear_effect
59#[derive(Clone, Copy, Debug, Eq, PartialEq)]
60pub enum Effect {
61    /// # An assertion failed
62    ///
63    /// Can trigger when evaluating `assert`, if its input is zero.
64    AssertionFailed,
65
66    /// # Tried to divide by zero
67    ///
68    /// Can trigger when evaluating the `/` operator, if its second input is
69    /// `0`.
70    DivisionByZero,
71
72    /// # Division resulted in integer overflow
73    ///
74    /// Can only trigger when evaluating the `/` operator, if its first input is
75    /// the lowest signed (two's complement) 32-bit integer, and its second
76    /// input is `-1`.
77    ///
78    /// All other arithmetic operators wrap on overflow and don't trigger this
79    /// effect.
80    IntegerOverflow,
81
82    /// # A memory address is out of bounds
83    ///
84    /// Can trigger when evaluating the `read` or `write` operators, if their
85    /// _address_ input (when interpreted as an unsigned 32-bit integer) does
86    /// not refer to an address that is within the bounds of the memory.
87    InvalidAddress,
88
89    /// # Index doesn't refer to valid value on the operand stack
90    ///
91    /// Can trigger when evaluating the `copy` or `drop` operators, if their
92    /// _index_ input is too large to refer to a value on the operand stack.
93    InvalidOperandStackIndex,
94
95    /// # Evaluated a reference that is not paired with a matching label
96    ///
97    /// Can trigger when evaluating a reference, if that reference does not
98    /// refer to a label.
99    InvalidReference,
100
101    /// # Tried popping a value from an empty operand stack
102    ///
103    /// Can trigger when evaluating any operator that has more inputs than the
104    /// number of values currently on the operand stack.
105    OperandStackUnderflow,
106
107    /// # Ran out of operators to evaluate
108    ///
109    /// Triggers when evaluation reaches the end of the script, where no more
110    /// operators are available. This is not an error, which makes it one of the
111    /// ways to signal the regular end of evaluation, alongside
112    /// [`Effect::Return`].
113    OutOfOperators,
114
115    /// # Evaluated `return` while call stack was empty
116    ///
117    /// This is not an error, which makes it one of the ways to signal the
118    /// regular end of evaluation, alongside [`Effect::OutOfOperators`].
119    Return,
120
121    /// # Evaluated an identifier that the language does not recognize
122    ///
123    /// Can trigger when evaluating an identifier, if that identifier does not
124    /// refer to a known operation.
125    UnknownIdentifier,
126
127    /// # The evaluating script yields control to the host
128    ///
129    /// Triggers when evaluating the `yield` operator.
130    Yield,
131}