stack_assembly/
eval.rs

1use crate::{Effect, Memory, Stack, Value};
2
3/// # The ongoing evaluation of a script
4///
5/// This is the main entry point into this library's API. To evaluate a script,
6/// you can pass it to [`Eval::start`], then use [`Eval::run`] or [`Eval::step`]
7/// to advance the evaluation.
8///
9/// ## Example
10///
11/// ```
12/// use stack_assembly::Eval;
13///
14/// let script = "1 2 +";
15///
16/// let mut eval = Eval::start(script);
17/// eval.run();
18///
19/// assert_eq!(eval.stack.to_i32_slice(), &[3]);
20/// ```
21#[derive(Debug)]
22pub struct Eval {
23    operators: Vec<Operator>,
24    labels: Vec<Label>,
25    next_operator: usize,
26
27    /// # The active effect, if one has triggered
28    ///
29    /// Effects moderate the communication between script and host. The effect
30    /// itself only relays _which_ effect has triggered, but that may signal to
31    /// the host that a different communication channel (like [`stack`] or
32    /// [`memory`]) is ready to be accessed.
33    ///
34    /// [`Eval::start`] initializes this field to `None`. [`Eval::run`] and
35    /// [`Eval::step`] may store an effect here, if the script triggers one. If
36    /// that is the case, the host may handle the effect, to allow evaluation
37    /// to continue.
38    ///
39    /// ## Handling Effects
40    ///
41    /// The host may handle effects however it wishes. But since most effects
42    /// signal error conditions that the script would not expect to recover
43    /// from, a well-behaving host must be careful not to handle effects in
44    /// a way that make reasoning about the script's behavior difficult.
45    ///
46    /// Abandoning the evaluation and reporting an error in the appropriate
47    /// manner, is the only reasonable way to handle most effects. The
48    /// exception to that is [`Effect::Yield`], which does not signal an error
49    /// condition. A script would expect to continue afterwards.
50    ///
51    /// To make that possible, the host must clear the effect by setting this
52    /// field to `None`.
53    ///
54    /// ### Example
55    ///
56    /// ```
57    /// use stack_assembly::{Effect, Eval};
58    ///
59    /// // This script increments a number in a loop, yielding control to the
60    /// // host every time it did so.
61    /// let script = "
62    ///     0
63    ///
64    ///     increment:
65    ///         1 +
66    ///         yield
67    ///         @increment jump
68    /// ";
69    ///
70    /// let mut eval = Eval::start(script);
71    ///
72    /// // When running the script for the first time, we expect that it has
73    /// // incremented the number once, before yielding.
74    /// eval.run();
75    /// assert_eq!(eval.effect, Some(Effect::Yield));
76    /// assert_eq!(eval.stack.to_u32_slice(), &[1]);
77    ///
78    /// // To allow the script to continue, we must clear the effect.
79    /// eval.effect = None;
80    ///
81    /// // Since we handled the effect correctly, we can now assume that the
82    /// // script has incremented the number a second time, before yielding
83    /// // again.
84    /// eval.run();
85    /// assert_eq!(eval.effect, Some(Effect::Yield));
86    /// assert_eq!(eval.stack.to_u32_slice(), &[2]);
87    /// ```
88    ///
89    /// [`next_operator`]: #structfield.next_operator
90    /// [`stack`]: #structfield.stack
91    /// [`memory`]: #structfield.memory
92    pub effect: Option<Effect>,
93
94    /// # The operand stack
95    ///
96    /// StackAssembly's evaluation model is based on an implicit stack which
97    /// stores all operands. An operator's output is pushed to that stack, and
98    /// any of its inputs are popped from there.
99    ///
100    /// Alongside [`memory`], this field is the primary channel for
101    /// communication between script and host.
102    ///
103    /// Most hosts should restrict modifications to this field to when the
104    /// script triggers [`Effect::Yield`], and then only do so in a
105    /// well-reasoned and documented manner. Anything else might make reasoning
106    /// about the script's behavior very difficult.
107    ///
108    /// None the less, the host has full access to this field, as to not
109    /// restrict any experimental or non-standard use cases.
110    ///
111    /// [`memory`]: #structfield.memory
112    pub stack: Stack,
113
114    /// # The memory
115    ///
116    /// StackAssembly provides a linear memory that is freely addressable per
117    /// word.
118    ///
119    /// Alongside [`stack`], this field is the primary channel for
120    /// communication between script and host.
121    ///
122    /// Most hosts should restrict modifications to this field to when the
123    /// script triggers [`Effect::Yield`], and then only do so in a
124    /// well-reasoned and documented manner. Anything else might make reasoning
125    /// about the script's behavior very difficult.
126    ///
127    /// None the less, the host has full access to this field, as to not
128    /// restrict any experimental or non-standard use cases.
129    ///
130    /// [`stack`]: #structfield.stack
131    pub memory: Memory,
132}
133
134impl Eval {
135    /// # Start evaluating the provided script
136    ///
137    /// Compile the provided script and return an `Eval` instance that is ready
138    /// for evaluation. To evaluate any operators, you must call [`Eval::run`]
139    /// or [`Eval::step`].
140    pub fn start(script: &str) -> Self {
141        let mut operators = Vec::new();
142        let mut labels = Vec::new();
143
144        for line in script.lines() {
145            for token in line.split_whitespace() {
146                if token.starts_with("#") {
147                    // This is a comment. Ignore the rest of the line.
148                    break;
149                }
150
151                let operator = if let Some((name, "")) = token.rsplit_once(":")
152                {
153                    labels.push(Label {
154                        name: name.to_string(),
155                        operator: operators.len(),
156                    });
157                    continue;
158                } else if let Some(("", name)) = token.split_once("@") {
159                    Operator::Reference {
160                        name: name.to_string(),
161                    }
162                } else if let Some(("", value)) = token.split_once("0x")
163                    && let Ok(value) = i32::from_str_radix(value, 16)
164                {
165                    Operator::Integer { value }
166                } else if let Ok(value) = token.parse::<i32>() {
167                    Operator::Integer { value }
168                } else {
169                    Operator::Identifier {
170                        value: token.to_string(),
171                    }
172                };
173
174                operators.push(operator);
175            }
176        }
177
178        Self {
179            operators,
180            labels,
181            next_operator: 0,
182            effect: None,
183            stack: Stack { values: Vec::new() },
184            memory: Memory {
185                values: vec![Value::from(0); 1024],
186            },
187        }
188    }
189
190    /// # Advance the evaluation until it triggers an effect
191    ///
192    /// If an effect is currently active (see [`effect`] field), do nothing and
193    /// return immediately. Otherwise, keep evaluating operators until one
194    /// triggers an effect.
195    ///
196    /// If you need more control over the evaluation, consider using
197    /// [`Eval::step`] instead.
198    ///
199    /// [`effect`]: #structfield.effect
200    /// [`next_operator`]: #structfield.next_operator
201    pub fn run(&mut self) -> &mut Effect {
202        while self.effect.is_none() {
203            self.step();
204        }
205
206        // It's a bit of a shame we have to unwrap the `Option` like this, but
207        // I tried doing it from within the loop, and failed due to the borrow
208        // checker.
209        let Some(effect) = &mut self.effect else {
210            unreachable!(
211                "An effect must have triggered, or we wouldn't have exited the \
212                loop just now."
213            );
214        };
215
216        effect
217    }
218
219    /// # Advance the evaluation by one step
220    ///
221    /// If an effect is currently active (see [`effect`] field), do nothing and
222    /// return immediately. Otherwise, evaluate the next operator. If that
223    /// triggers an effect, store that in the [`effect`] field.
224    ///
225    /// This function may be used for advancing the evaluation of the script in
226    /// a controlled manner. If you just want to keep evaluating until the next
227    /// effect, consider using [`Eval::run`] instead.
228    ///
229    /// [`effect`]: #structfield.effect
230    /// [`next_operator`]: #structfield.next_operator
231    pub fn step(&mut self) {
232        if self.effect.is_some() {
233            return;
234        }
235
236        if let Err(effect) = self.evaluate_next_operator() {
237            self.effect = Some(effect);
238        }
239    }
240
241    fn evaluate_next_operator(&mut self) -> Result<(), Effect> {
242        let Some(operator) = self.operators.get(self.next_operator) else {
243            return Err(Effect::OutOfOperators);
244        };
245        self.next_operator += 1;
246
247        match operator {
248            Operator::Identifier { value: identifier } => {
249                if identifier == "*" {
250                    let b = self.stack.pop()?.to_i32();
251                    let a = self.stack.pop()?.to_i32();
252
253                    self.stack.push(a.wrapping_mul(b));
254                } else if identifier == "+" {
255                    let b = self.stack.pop()?.to_i32();
256                    let a = self.stack.pop()?.to_i32();
257
258                    self.stack.push(a.wrapping_add(b));
259                } else if identifier == "-" {
260                    let b = self.stack.pop()?.to_i32();
261                    let a = self.stack.pop()?.to_i32();
262
263                    self.stack.push(a.wrapping_sub(b));
264                } else if identifier == "/" {
265                    let b = self.stack.pop()?.to_i32();
266                    let a = self.stack.pop()?.to_i32();
267
268                    if b == 0 {
269                        return Err(Effect::DivisionByZero);
270                    }
271                    if a == i32::MIN && b == -1 {
272                        return Err(Effect::IntegerOverflow);
273                    }
274
275                    let quotient = a / b;
276                    let remainder = a % b;
277
278                    self.stack.push(quotient);
279                    self.stack.push(remainder);
280                } else if identifier == "<" {
281                    let b = self.stack.pop()?.to_i32();
282                    let a = self.stack.pop()?.to_i32();
283
284                    let c = if a < b { 1 } else { 0 };
285
286                    self.stack.push(c);
287                } else if identifier == "<=" {
288                    let b = self.stack.pop()?.to_i32();
289                    let a = self.stack.pop()?.to_i32();
290
291                    let c = if a <= b { 1 } else { 0 };
292
293                    self.stack.push(c);
294                } else if identifier == "=" {
295                    let b = self.stack.pop()?.to_i32();
296                    let a = self.stack.pop()?.to_i32();
297
298                    let c = if a == b { 1 } else { 0 };
299
300                    self.stack.push(c);
301                } else if identifier == ">" {
302                    let b = self.stack.pop()?.to_i32();
303                    let a = self.stack.pop()?.to_i32();
304
305                    let c = if a > b { 1 } else { 0 };
306
307                    self.stack.push(c);
308                } else if identifier == ">=" {
309                    let b = self.stack.pop()?.to_i32();
310                    let a = self.stack.pop()?.to_i32();
311
312                    let c = if a >= b { 1 } else { 0 };
313
314                    self.stack.push(c);
315                } else if identifier == "and" {
316                    let b = self.stack.pop()?.to_i32();
317                    let a = self.stack.pop()?.to_i32();
318
319                    let c = a & b;
320
321                    self.stack.push(c);
322                } else if identifier == "or" {
323                    let b = self.stack.pop()?.to_i32();
324                    let a = self.stack.pop()?.to_i32();
325
326                    let c = a | b;
327
328                    self.stack.push(c);
329                } else if identifier == "xor" {
330                    let b = self.stack.pop()?.to_i32();
331                    let a = self.stack.pop()?.to_i32();
332
333                    let c = a ^ b;
334
335                    self.stack.push(c);
336                } else if identifier == "count_ones" {
337                    let a = self.stack.pop()?.to_i32();
338                    let b = a.count_ones();
339                    self.stack.push(b);
340                } else if identifier == "leading_zeros" {
341                    let a = self.stack.pop()?.to_i32();
342                    let b = a.leading_zeros();
343                    self.stack.push(b);
344                } else if identifier == "trailing_zeros" {
345                    let a = self.stack.pop()?.to_i32();
346                    let b = a.trailing_zeros();
347                    self.stack.push(b);
348                } else if identifier == "rotate_left" {
349                    let num_positions = self.stack.pop()?.to_u32();
350                    let a = self.stack.pop()?.to_i32();
351
352                    let b = a.rotate_left(num_positions);
353
354                    self.stack.push(b);
355                } else if identifier == "rotate_right" {
356                    let num_positions = self.stack.pop()?.to_u32();
357                    let a = self.stack.pop()?.to_i32();
358
359                    let b = a.rotate_right(num_positions);
360
361                    self.stack.push(b);
362                } else if identifier == "shift_left" {
363                    let num_positions = self.stack.pop()?.to_i32();
364                    let a = self.stack.pop()?.to_i32();
365
366                    let b = a << num_positions;
367
368                    self.stack.push(b);
369                } else if identifier == "shift_right" {
370                    let num_positions = self.stack.pop()?.to_i32();
371                    let a = self.stack.pop()?.to_i32();
372
373                    let b = a >> num_positions;
374
375                    self.stack.push(b);
376                } else if identifier == "copy" {
377                    let index_from_top = self.stack.pop()?.to_usize();
378                    let index_from_bottom =
379                        convert_stack_index(&self.stack, index_from_top)?;
380
381                    let Some(value) =
382                        self.stack.values.get(index_from_bottom).copied()
383                    else {
384                        unreachable!(
385                            "We computed the index from the top, based on the \
386                            number of values on the stack. Since that did not \
387                            result in an integer overflow, it's not possible \
388                            that we ended up with an out-of-range index."
389                        );
390                    };
391
392                    self.stack.push(value);
393                } else if identifier == "drop" {
394                    let index_from_top = self.stack.pop()?.to_usize();
395                    let index_from_bottom =
396                        convert_stack_index(&self.stack, index_from_top)?;
397
398                    // This could theoretically panic, but actually won't, for
399                    // the same reason that the index must be valid in the
400                    // implementation of `copy`.
401                    self.stack.values.remove(index_from_bottom);
402                } else if identifier == "jump" {
403                    let index = self.stack.pop()?.to_usize();
404                    self.next_operator = index;
405                } else if identifier == "jump_if" {
406                    let index = self.stack.pop()?.to_usize();
407                    let condition = self.stack.pop()?.to_i32();
408
409                    if condition != 0 {
410                        self.next_operator = index;
411                    }
412                } else if identifier == "assert" {
413                    let value = self.stack.pop()?.to_i32();
414
415                    if value == 0 {
416                        return Err(Effect::AssertionFailed);
417                    }
418                } else if identifier == "yield" {
419                    return Err(Effect::Yield);
420                } else if identifier == "read" {
421                    let address = self.stack.pop()?.to_usize();
422
423                    let Some(value) = self.memory.values.get(address).copied()
424                    else {
425                        return Err(Effect::InvalidAddress);
426                    };
427
428                    self.stack.push(value);
429                } else if identifier == "write" {
430                    let value = self.stack.pop()?;
431                    let address = self.stack.pop()?.to_usize();
432
433                    if address < self.memory.values.len() {
434                        self.memory.values[address] = value;
435                    } else {
436                        return Err(Effect::InvalidAddress);
437                    }
438                } else {
439                    return Err(Effect::UnknownIdentifier);
440                }
441            }
442            Operator::Integer { value } => {
443                self.stack.push(*value);
444            }
445            Operator::Reference { name } => {
446                let label =
447                    self.labels.iter().find(|label| &label.name == name);
448
449                if let Some(&Label { ref name, operator }) = label {
450                    let Ok(operator) = operator.try_into() else {
451                        panic!(
452                            "Operator index `{operator}` of label `{name}` is \
453                            out of bounds. This can only happen on platforms \
454                            where the width of Rust's `usize` is wider than 32 \
455                            bits, with a script that consists of at least 2^32 \
456                            operators.\n\
457                            \n\
458                            Scripts that large seem barely realistic in the \
459                            first place, more so on a 32-bit platform. At \
460                            best, this is a niche use case that StackAssembly \
461                            happens to not support, making this panic an \
462                            acceptable outcome."
463                        );
464                    };
465                    let operator: u32 = operator;
466
467                    self.stack.push(operator);
468                } else {
469                    return Err(Effect::InvalidReference);
470                }
471            }
472        }
473
474        Ok(())
475    }
476}
477
478#[derive(Debug)]
479enum Operator {
480    Identifier { value: String },
481    Integer { value: i32 },
482    Reference { name: String },
483}
484
485#[derive(Debug)]
486struct Label {
487    pub name: String,
488    pub operator: usize,
489}
490
491fn convert_stack_index(
492    stack: &Stack,
493    index_from_top: usize,
494) -> Result<usize, Effect> {
495    let index_from_bottom = stack
496        .values
497        .len()
498        .checked_sub(1)
499        .and_then(|index| index.checked_sub(index_from_top));
500
501    index_from_bottom.ok_or(Effect::InvalidStackIndex)
502}