stack_vm/
lib.rs

1//! # stack-vm
2//!
3//! This crate implements a generic stack machine for which you provide the
4//! operands and the instructions and this crate provides the rest of the
5//! infrastructure required to run it.
6//!
7//! It also provides a simple instruction builder which you can use to generate
8//! your program.
9//!
10//! Stack machines are computers which use an operand stack to perform the
11//! evaluation of postfix expressions.  Every computer architecture has it's
12//! own instruction set which is the basic set of operations that the computer
13//! can perform.
14//!
15//! Instructions usually describe basic arithmetic operations, I/O, jumps, etc.
16//!
17//! ## Computer Architecture
18//!
19//! The architecture of a computer system is how the various logic components
20//! are connected together in order to execute the instructions and produce
21//! side-effects (useful outcomes).
22//!
23//! There are two main ways to organise a computer architecture:
24//! * *Von Neumann* - in which the program data and instructions are stored in
25//!   the same memory.
26//! * *Harvard* - in which the program data and instructions are stored in
27//!   separate memory sections.
28//!
29//! The bulk of modern processors are Von Neumann type machines.
30//!
31//! We can also classify our machines by the way that they store intermediate
32//! values:
33//! * *Accumulator* - the most basic form of processor where only a single
34//!   register is used to store the results of computation.
35//! * *Stack* - stack machines use an operand stack to push and pop results
36//!   off the top of.
37//! * *Register* - register machines use a number of named (or numbered)
38//!   registers to store values or pass arguments.
39//!
40//! Most modern processors are register machines, although interestingly both
41//! register and stack machines can be used to emulate their cousin.
42//!
43//! ## Instruction Sets
44//!
45//! The instruction set is the definition of the machine.  Without instructions
46//! your machine can't *do* anything.  They are the fundamental building blocks
47//! of your computer, so you need to think this through before building it.
48//!
49//! This virtual machine uses Rust functions as instructions rather than
50//! transistors and logic gates, but the effect is the same.
51//!
52//! In order to generate your instructions you need to create a bunch of Rust
53//! functions which conform to the `stack_vm::InstructionFn` signature.
54//!
55//! For example:
56//!
57//! ```
58//! use stack_vm::Machine;
59//! type Operand = i64;
60//!
61//! fn push(machine: &mut Machine<Operand>, args: &[usize]) {
62//!     let arg = machine.get_data(args[0]).clone();
63//!     machine.operand_push(arg);
64//! }
65//! ```
66//!
67//! Once you have finished defining your instructions you can use them to build
68//! a `stack_vm::InstructionTable`, where every instruction is identified by
69//! it's `op_code`, `name` and `arity`.
70//!
71//! * `op_code` a positive integer which uniquely identifies this instruction.
72//!   This is manually entered rather than auto-generated from insert order
73//!   so that you can maintain as much compatibility between versions of your
74//!   VM as possible.
75//!
76//! * `name` a string used to identify this instruction; mainly for debugging.
77//!
78//! * `arity` the number of arguments your instruction expects *from program
79//!   data*.  This is not the number of operands your function needs off the
80//!   operand stack.  This is used so that you can place constant data into
81//!   the program at compile time.
82//!
83//! ```
84//! use stack_vm::{Instruction, InstructionTable, Machine};
85//! type Operand = i64;
86//!
87//! fn push(machine: &mut Machine<Operand>, args: &[usize]) {
88//!     let arg = machine.get_data(args[0]).clone();
89//!     machine.operand_push(arg);
90//! }
91//!
92//! fn add(machine: &mut Machine<Operand>, _args: &[usize]) {
93//!     let rhs = machine.operand_pop().clone();
94//!     let lhs = machine.operand_pop().clone();
95//!     machine.operand_push(lhs + rhs);
96//! }
97//!
98//! let mut instruction_table = InstructionTable::new();
99//! instruction_table.insert(Instruction::new(0, "push", 1, push));
100//! instruction_table.insert(Instruction::new(1, "add",  0, add));
101//! ```
102//!
103//! ## Code generation
104//!
105//! One your instruction set is defined then you can use the
106//! `stack_vm::Builder` object to build a representation that the VM can
107//! execute.
108//!
109//! For example, to push two integers on the stack and add them:
110//!
111//! ```
112//! use stack_vm::{Instruction, InstructionTable, Machine, Builder};
113//! type Operand = i64;
114//!
115//! fn push(machine: &mut Machine<Operand>, args: &[usize]) {
116//!     let arg = machine.get_data(args[0]).clone();
117//!     machine.operand_push(arg);
118//! }
119//!
120//! fn add(machine: &mut Machine<Operand>, _args: &[usize]) {
121//!     let rhs = machine.operand_pop().clone();
122//!     let lhs = machine.operand_pop().clone();
123//!     machine.operand_push(lhs + rhs);
124//! }
125//!
126//! let mut instruction_table = InstructionTable::new();
127//! instruction_table.insert(Instruction::new(0, "push", 1, push));
128//! instruction_table.insert(Instruction::new(1, "add",  0, add));
129//!
130//! let mut builder: Builder<Operand> = Builder::new(&instruction_table);
131//! builder.push("push", vec![3 as Operand]);
132//! builder.push("push", vec![4 as Operand]);
133//! builder.push("add", vec![]);
134//! ```
135//!
136//! This will result in the following code:
137//!
138//! ```text
139//! @0 = 3
140//! @1 = 4
141//!
142//! .main:
143//!   push @0
144//!   push @1
145//!   add
146//! ```
147//!
148//! ## Running your program
149//!
150//! Once you have the instructions and code generated then you can put them
151//! together with the `stack_vm::Machine` to execute it.
152//!
153//! ```
154//! use stack_vm::{Instruction, InstructionTable, Machine, Builder, WriteManyTable, Code};
155//! type Operand = i64;
156//!
157//! fn push(machine: &mut Machine<Operand>, args: &[usize]) {
158//!     let arg = machine.get_data(args[0]).clone();
159//!     machine.operand_push(arg);
160//! }
161//!
162//! fn add(machine: &mut Machine<Operand>, _args: &[usize]) {
163//!     let rhs = machine.operand_pop().clone();
164//!     let lhs = machine.operand_pop().clone();
165//!     machine.operand_push(lhs + rhs);
166//! }
167//!
168//! let mut instruction_table = InstructionTable::new();
169//! instruction_table.insert(Instruction::new(0, "push", 1, push));
170//! instruction_table.insert(Instruction::new(1, "add",  0, add));
171//!
172//! let mut builder: Builder<Operand> = Builder::new(&instruction_table);
173//! builder.push("push", vec![3 as Operand]);
174//! builder.push("push", vec![4 as Operand]);
175//! builder.push("add", vec![]);
176//!
177//! let constants: WriteManyTable<Operand> = WriteManyTable::new();
178//! let mut machine = Machine::new(Code::from(builder), &constants, &instruction_table);
179//! machine.run();
180//! assert_eq!(machine.operand_pop(), 7);
181//! ```
182//!
183//! ## Calling functions:
184//!
185//! Functions are executed by having the machine jump to another label within
186//! the code and continue executing from there.
187//!
188//! Every time the machine jumps it creates a new call frame, which allows it
189//! to store and retrieve local variables without clobbering their parent
190//! call context.  It also contains the return address, meaning that when you
191//! ask the machine to return it will know which address in the code to go back
192//! to after removing the frame.
193//!
194//! You can find an example of function calling in this package's acceptance
195//! tests.
196
197extern crate rmp;
198
199mod builder;
200mod code;
201mod frame;
202mod from_byte_code;
203mod instruction;
204mod instruction_table;
205mod machine;
206mod stack;
207mod table;
208mod to_byte_code;
209mod write_many_table;
210mod write_once_table;
211
212pub use crate::builder::Builder;
213pub use crate::code::Code;
214pub use crate::frame::Frame;
215pub use crate::from_byte_code::FromByteCode;
216pub use crate::instruction::{Instruction, InstructionFn};
217pub use crate::instruction_table::InstructionTable;
218pub use crate::machine::Machine;
219pub use crate::stack::Stack;
220pub use crate::table::Table;
221pub use crate::to_byte_code::ToByteCode;
222pub use crate::write_many_table::WriteManyTable;
223pub use crate::write_once_table::WriteOnceTable;
224
225#[cfg(test)]
226mod acceptance;