stack_vm/
builder.rs

1//! The instruction builder.
2//!
3//! Use this module to build code for your machine to execute.
4//!
5//! ## Examples
6//!
7//! ```
8//! use stack_vm::{Instruction, InstructionTable, Builder, Machine};
9//!
10//! fn push(machine: &mut Machine<f64>, args: &[usize]) {
11//!     let arg = machine.get_data(args[0]).clone();
12//!     machine.operand_push(arg)
13//! }
14//!
15//! let mut instruction_table: InstructionTable<f64> = InstructionTable::new();
16//! instruction_table.insert(Instruction::new(0, "push", 1, push));
17//!
18//! let mut builder: Builder<f64> = Builder::new(&instruction_table);
19//! builder.push("push", vec![1.23]);
20//! ```
21
22use crate::instruction_table::InstructionTable;
23use crate::table::Table;
24use crate::write_once_table::WriteOnceTable;
25use std::fmt;
26
27/// The builder struct.
28///
29/// Contains:
30/// * an `InstructionTable`.
31/// * a list of instructions that have been pushed into this builder.
32/// * a `Table` of labels used for jumping.
33/// * a list of `T` to be stored in the builder's data section.
34pub struct Builder<'a, T: 'a + fmt::Debug + PartialEq> {
35    pub instruction_table: &'a InstructionTable<T>,
36    pub instructions: Vec<usize>,
37    pub labels: WriteOnceTable<usize>,
38    pub data: Vec<T>,
39}
40
41impl<'a, T: fmt::Debug + PartialEq> Builder<'a, T> {
42    /// Create a new `Builder` from an `InstructionTable`.
43    pub fn new(instruction_table: &'a InstructionTable<T>) -> Builder<T> {
44        let mut labels = WriteOnceTable::new();
45        labels.insert("main", 0);
46        Builder {
47            instruction_table: &instruction_table,
48            instructions: vec![],
49            labels,
50            data: vec![],
51        }
52    }
53
54    /// Push an instruction into the code.
55    ///
56    /// * `name` should match that of an instruction in the `InstructionTable`.
57    /// * `args` a vector of operands to be pushed into the builder's data
58    ///   section.
59    pub fn push(&mut self, name: &str, args: Vec<T>) {
60        let instr = self
61            .instruction_table
62            .by_name(name)
63            .unwrap_or_else(|| panic!("Unable to find instruction with name {:?}", name));
64
65        if args.len() != instr.arity {
66            panic!(
67                "Instruction {} has arity of {}, but you provided {} arguments.",
68                instr.name,
69                instr.arity,
70                args.len()
71            )
72        }
73
74        self.instructions.push(instr.op_code);
75        self.instructions.push(instr.arity);
76        for arg in args {
77            let pos = self.push_data(arg);
78            self.instructions.push(pos);
79        }
80    }
81
82    /// Insert a label at this point in the code.
83    ///
84    /// Labels are used as targets for jumps.  When you call this method a
85    /// label is stored which points to the position of the next instruction.
86    pub fn label(&mut self, name: &str) {
87        let idx = self.instructions.len();
88        self.labels.insert(name, idx);
89    }
90
91    /// Return the length of the instructions vector.
92    ///
93    /// i.e. the number of instructions pushed so far.
94    pub fn len(&self) -> usize {
95        self.instructions.len()
96    }
97
98    /// Return whether the Builder contains any instructions.
99    pub fn is_empty(&self) -> bool {
100        self.instructions.is_empty()
101    }
102
103    fn push_data(&mut self, data: T) -> usize {
104        let pos = self.data.iter().position(|d| d == &data);
105        match pos {
106            Some(pos) => pos,
107            None => {
108                self.data.push(data);
109                self.data.len() - 1
110            }
111        }
112    }
113}
114
115impl<'a, T: 'a + fmt::Debug + PartialEq> fmt::Debug for Builder<'a, T> {
116    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
117        let mut result = String::new();
118
119        for i in 0..self.data.len() {
120            result.push_str(&format!("@{} = {:?}\n", i, self.data[i]));
121        }
122
123        let mut ip = 0;
124        let len = self.instructions.len();
125        loop {
126            for label in self.labels.keys() {
127                let idx = *self.labels.get(&label).unwrap();
128                if idx == ip {
129                    result.push_str(&format!("\n.{}:\n", label));
130                }
131            }
132
133            if ip == len {
134                break;
135            }
136
137            let op_code = self.instructions[ip];
138            ip += 1;
139            let arity = self.instructions[ip];
140
141            let instr = self
142                .instruction_table
143                .by_op_code(op_code)
144                .unwrap_or_else(|| panic!("Unable to find instruction with op code {}", op_code));
145
146            result.push_str(&format!("\t{}", &instr.name));
147
148            for _j in 0..arity {
149                ip += 1;
150                let const_idx = self.instructions[ip];
151                result.push_str(&format!(" @{}", const_idx));
152            }
153            result.push_str("\n");
154
155            ip += 1;
156        }
157
158        write!(f, "{}", result)
159    }
160}
161
162#[cfg(test)]
163mod test {
164    use super::*;
165    use crate::instruction::Instruction;
166    use crate::instruction_table::InstructionTable;
167    use crate::machine::Machine;
168
169    fn noop(_machine: &mut Machine<usize>, _args: &[usize]) {}
170
171    fn example_instruction_table() -> InstructionTable<usize> {
172        let mut it = InstructionTable::new();
173        it.insert(Instruction::new(0, "noop", 0, noop));
174        it.insert(Instruction::new(1, "push", 1, noop));
175        it.insert(Instruction::new(2, "pop", 0, noop));
176        it
177    }
178
179    #[test]
180    fn new() {
181        let it = example_instruction_table();
182        let builder: Builder<usize> = Builder::new(&it);
183        assert!(builder.instructions.is_empty());
184    }
185
186    #[test]
187    fn push() {
188        let it = example_instruction_table();
189        let mut builder: Builder<usize> = Builder::new(&it);
190        builder.push("noop", vec![]);
191        assert!(!builder.instructions.is_empty());
192    }
193
194    #[test]
195    #[should_panic(expected = "has arity of")]
196    fn push_with_incorrect_arity() {
197        let it = example_instruction_table();
198        let mut builder: Builder<usize> = Builder::new(&it);
199        builder.push("noop", vec![1]);
200    }
201
202    #[test]
203    fn label() {
204        let it = example_instruction_table();
205        let mut builder: Builder<usize> = Builder::new(&it);
206        builder.push("noop", vec![]);
207        builder.label("wow");
208        assert_eq!(*builder.labels.get("wow").unwrap(), 2);
209    }
210
211    #[test]
212    fn data_is_deduped() {
213        let it = example_instruction_table();
214        let mut builder: Builder<usize> = Builder::new(&it);
215        builder.push("push", vec![123]);
216        builder.push("push", vec![123]);
217        builder.push("push", vec![123]);
218        assert_eq!(builder.data.len(), 1);
219    }
220
221    #[test]
222    fn debug_format() {
223        let it = example_instruction_table();
224        let mut builder: Builder<usize> = Builder::new(&it);
225        builder.push("noop", vec![]);
226        builder.push("push", vec![123]);
227        builder.push("push", vec![456]);
228        builder.label("some_function");
229        builder.push("pop", vec![]);
230
231        let actual = format!("{:?}", builder);
232        let expected = "@0 = 123
233@1 = 456
234
235.main:
236\tnoop
237\tpush @0
238\tpush @1
239
240.some_function:
241\tpop
242";
243        assert_eq!(actual, expected);
244    }
245}