1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
use crate::{Ref, Value};
// We need BTreeMap to implement the 'Heap' (registers)
use alloc::collections::BTreeMap;
// For ToString generics
use alloc::string::{String, ToString};
// We need Vec for the dynamically allocated stack
use alloc::vec::Vec;
// For implementing Display and Debug
use core::fmt::{Display, Error, Formatter};
#[derive(Default, Clone, PartialEq, PartialOrd)]
pub struct Machine {
/// A dynamically allocated stack to push and pop values onto and off of
pub stack: Vec<Ref<Value>>,
/// The place to store named values (variables)
pub registers: BTreeMap<String, Ref<Value>>,
}
impl Machine {
/// Return a new instance of a Machine with empty stack and registers
pub fn new() -> Self {
Machine {
stack: Vec::new(),
registers: BTreeMap::new(),
}
}
/// ####################################################
/// The following functions are meant to be used to
/// interface and interact with the virtual machine
/// ####################################################
/// FOR FOREIGN FUNCTIONS
/// This gets an argument from the call to this foreign
/// function by popping a value off the stack, and removing
/// the reference
pub fn get_arg(&mut self) -> Value {
(*self.pop()).clone()
}
/// FOR FOREIGN FUNCTIONS
/// This pushes a return value onto the stack
pub fn return_value(&mut self, value: Value) {
self.push(Ref::new(value))
}
/// ####################################################
/// The following functions represent instructions that
/// are natively supported by the virtual machine. These
/// are not meant to be used by foreign functions, but
/// they CAN be used without worry.
/// ####################################################
/// This function duplicates the current machine. This is
/// VERY IMPORTANT. It iterates through the stack and copies
/// each item into a new machine.
///
/// This is ONLY used to create the context for functions.
/// If we don't do this, the context Machine never goes out
/// of scope, and never lets the Refs die. This causes a
/// memory leak.
///
/// The addition of this method fixes the memory leak.
pub fn duplicate(self) -> Self {
let mut new = Self::new();
// Copy the stack for the new machine
for item in self.stack {
new.push((*item).clone().copy());
}
// Copy the registers for the new machine
for (key, value) in self.registers {
new.registers.insert(key.to_string(), value.copy());
}
// Return new machine
new
}
/// Push an item onto the stack
pub fn push(&mut self, value: Ref<Value>) {
self.stack.push(value);
}
/// Pop an item off of the stack, and return it
/// If the stack is empty, return an Error
pub fn pop(&mut self) -> Ref<Value> {
match self.stack.pop() {
Some(v) => v,
None => Value::error("Popped from empty stack, called function with too few arguments"),
}
}
/// 1) Pop off a REFERENCE value from the stack
/// 2) Push a copy the object and remove the reference
pub fn copy(&mut self) {
let value = self.pop();
self.push(value.copy());
}
/// 1) Pop off a REFERENCE value from the stack
/// 2) Pop off a VALUE value from the stack
/// 3) Assign the value of VALUE to the memory location of REFERENCE
///
/// This can be used to assign to an indexed value from a list or table
pub fn assign(&mut self) {
let reference = self.pop();
let value = self.pop();
// We cant 'safely' modify a shared reference to a value,
// so we need to convert to a mutable pointer in an unsafe block
unsafe {
// Take the Ref and convert it to a mutable pointer
let ptr = Ref::into_raw(reference) as *mut Value;
// Assign to the contents of the mutable pointer
*ptr = (*value).clone();
// Re-wrap the pointer in a Ref value to properly manage it again
Ref::from_raw(ptr as *const Value);
}
}
/// 1) Pop off the INDEX value from the stack
/// 2) Pop off a TABLE value from the stack
/// 3) Push the TABLE[INDEX] reference onto the stack
pub fn index(&mut self) {
let index = self.pop();
let table = self.pop();
let result;
// We cant 'safely' modify a shared reference to a value,
// so we need to convert to a mutable pointer in an unsafe block
unsafe {
// Take the Ref and convert it to a mutable pointer
let ptr = Ref::into_raw(table) as *mut Value;
// Get the indexed value from the pointer to the table in memory
result = (*ptr).index(index);
// Re-wrap the pointer in a Ref value to properly manage it again
Ref::from_raw(ptr as *const Value);
}
self.push(result);
}
/// 1) Pop off the INDEX value from the stack
/// 2) Pop off a TABLE value from the stack
/// 3) Push the TABLE onto the stack
/// 4) Call the value at TABLE[INDEX] as a function
pub fn method_call(&mut self) {
let index = self.pop();
let table = self.pop();
// This is the `self` value to be passed to the function
// The `self` value cannot be directly assigned to,
// HOWEVER, its members / attributes can be assigned to
self.push(Ref::clone(&table));
self.push(table);
self.push(index);
self.index();
self.call();
}
/// 1) Pop off function from the stack
/// 2) Call it with this Machine instance
pub fn call(&mut self) {
let function = self.pop();
function.call(self);
}
/// 1) Pop off a COUNTER identifier from the stack
/// 2) Pop off an ELEMENT identifier from the stack
/// 3) Pop off a LIST value from the stack
/// 4) Pop off a BODY function from the stack
/// 5) For COUNTER, ELEMENT in enumeration of LIST:
/// 6) Store LIST[COUNTER] in ELEMENT
/// 7) Call BODY with current instance
/// 8) Increment COUNTER
pub fn for_loop(&mut self) {
let counter_name = self.pop();
let element_name = self.pop();
let iterator = (*self.pop()).clone();
let body = self.pop();
for (index, item) in iterator.into_iter().enumerate() {
self.registers.insert(element_name.to_string(), item);
self.registers.insert(counter_name.to_string(), Value::number(index as f64));
body.call_global(self);
}
}
/// 1) Pop off a CONDITION function from the stack
/// 2) Pop off a BODY function from the stack
/// 3) Call the CONDITION function with the context of this instance
/// 4) If the return value is true, run the BODY function with the context of this instance
/// 5) Goto step 3
pub fn while_loop(&mut self) {
let condition = self.pop();
let body = self.pop();
// This will take the top item of the stack and convert it to a bool
let get_condition = |machine: &mut Machine| -> bool { (*machine.pop()).clone().into() };
// First, get the condition
condition.call_global(self);
while get_condition(self) {
// If the condition is true, run the body of the while loop
body.call_global(self);
// Push the condition again to test on the next iteration
condition.call_global(self);
}
}
/// 1) Pop off a CONDITION function from the stack
/// 2) Pop off a THEN function from the stack
/// 3) Pop off an ELSE function from the stack
/// 4) Call the CONDITION function with the context of this instance
/// 5) If the return value is true, run the THEN function with the context of this instance
/// 6) If the return value is false, run the ELSE function with the context of this instance
pub fn if_then_else(&mut self) {
let condition = self.pop();
let then_fn = self.pop();
let else_fn = self.pop();
// This will take the top item of the stack and convert it to a bool
let get_condition = |machine: &mut Machine| -> bool { (*machine.pop()).clone().into() };
// First, get the condition
condition.call_global(self);
if get_condition(self) {
// If the condition is true, run the body of the while loop
then_fn.call_global(self);
} else {
// Push the condition again to test on the next iteration
else_fn.call_global(self);
}
}
/// 1) Pop off a KEY value from the stack
/// 2) Pop off a VALUE value from the stack
/// 3) Assign the value of VALUE to the register named KEY
pub fn store(&mut self) {
// The register to assign to
let key = self.pop();
// The value to assign to it
let value = self.pop();
// registers[key] = value
self.registers.insert(key.to_string(), value);
}
/// 1) Pop off a KEY value from the stack
/// 2) Push the value in the register named KEY to the stack
pub fn load(&mut self) {
let key = &self.pop().to_string();
// The reason we don't do an if-let expression here is the fact
// that we can't borrow self as both mutable and immutable at once
if self.registers.contains_key(key) {
self.push(Ref::clone(self.registers.get(key).unwrap()));
} else {
self.push(Value::error(format!("No register named {}", key)));
}
}
}
/// How to print Machine / convert Machine to string
/// This is for debugging code and seeing the current instance of the machine
impl Display for Machine {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(
f,
"Machine {{\n\tstack: {:?}\n\theap: {:?}\n}}",
self.stack, self.registers
)
}
}