Skip to main content

stack_assembly/
lib.rs

1//! # StackAssembly
2//!
3//! StackAssembly is a minimalist, stack-based, assembly-like programming
4//! language. Here's a small taste:
5//!
6//! ```text
7//! # Push `0` to the stack.
8//! 0
9//!
10//! increment:
11//!     # Increment the value on the stack by `1`.
12//!     1 +
13//!
14//!     # If the value on the stack is smaller than `255`, jump to `increment:`.
15//!     0 copy 255 <
16//!     @increment
17//!         jump_if
18//!
19//! # Looks like we didn't jump to `increment:` that last time, so the value
20//! # must be `255` now.
21//! 255 = assert
22//! ```
23//!
24//! Please check out the [repository on GitHub][repository] to learn more about
25//! StackAssembly. This documentation, while it contains some information about
26//! the language itself, is focused on how to use this library, which contains
27//! the StackAssembly interpreter.
28//!
29//! [repository]: https://github.com/hannobraun/stack-assembly
30//!
31//! ## Usage
32//!
33//! This library contains the interpreter for StackAssembly. It is intentionally
34//! minimalist. You provide a **script**, and the library gives you an API to
35//! evaluate it.
36//!
37//! ```
38//! use stack_assembly::{Eval, Script};
39//!
40//! let script = Script::compile("1 2 +");
41//!
42//! let mut eval = Eval::new();
43//! eval.run(&script);
44//!
45//! assert_eq!(eval.operand_stack.to_i32_slice(), &[3]);
46//! ```
47//!
48//! [`Script`] and [`Eval`] are the main entry points to the library's API.
49//!
50//! ### Hosts
51//!
52//! [`Eval`] evaluates scripts in a sandboxed environment, not giving them any
53//! access to the system it itself runs on. StackAssembly scripts by themselves
54//! cannot do much.
55//!
56//! To change that, we need a **host**. A host is Rust code that uses this
57//! library to drive the evaluation of a StackAssembly script. It can choose to
58//! provide additional capabilities to the script.
59//!
60//! ```
61//! use stack_assembly::{Effect, Eval, Script};
62//!
63//! // A script that seems to want to print the value `3`.
64//! let script = Script::compile("
65//!     3 @print jump
66//!
67//!     print:
68//!         yield
69//! ");
70//!
71//! // Start the evaluation and advance it until the script triggers an effect.
72//! let mut eval = Eval::new();
73//! let (effect, _) = eval.run(&script);
74//!
75//! // `run` has returned, meaning an effect has triggered. Let's make sure that
76//! // went as expected.
77//! assert_eq!(effect, Effect::Yield);
78//! let Ok(value) = eval.operand_stack.pop() else {
79//!     unreachable!("We know that the script pushes a value before yielding.");
80//! };
81//!
82//! // The script calls `yield` at a label named `print`. I guess it expects us
83//! // to print the value then.
84//! println!("{value:?}");
85//! ```
86//!
87//! When the script triggers the "yield" effect, this host prints the value
88//! that's currently on top of the stack.
89//!
90//! This is just a simple example. A more full-featured host would provide more
91//! services in addition to printing values. Such a host could determine which
92//! service the script means to request by inspecting which other values it put
93//! on the stack, or into memory.
94
95#![warn(missing_debug_implementations)]
96#![warn(missing_docs)]
97
98mod effect;
99mod eval;
100mod memory;
101mod operand_stack;
102mod script;
103mod value;
104
105#[cfg(test)]
106mod tests;
107
108pub use self::{
109    effect::Effect,
110    eval::Eval,
111    memory::Memory,
112    operand_stack::{OperandStack, OperandStackUnderflow},
113    script::{OperatorIndex, Script},
114    value::Value,
115};