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}