lox_lang/vm/mod.rs
1use std::{any::Any, collections::HashMap};
2
3mod errors;
4mod frame;
5mod gc;
6mod io;
7mod list;
8mod run;
9
10pub(crate) use errors::RuntimeError;
11
12use {
13 crate::{Compiler, Error, Gc, UpvalueRef, Value},
14 frame::CallFrame,
15 list::List,
16};
17
18/// The Lox virtual machine.
19///
20/// ### Example
21///
22/// ```
23/// # use lox_lang::VM;
24/// let mut vm = VM::default();
25/// vm.interpret("3 + 3 == 6"); // true
26/// ```
27pub struct VM {
28 stdout: Option<Vec<u8>>,
29
30 stack: Vec<Value>,
31 frames: Vec<CallFrame>,
32 globals: HashMap<Box<str>, Value>,
33 open_upvalues: List<Gc<UpvalueRef>>,
34
35 objects: List<Gc<dyn Any>>,
36 compiler_roots: Vec<Value>,
37 total_allocations: usize,
38 next_gc: usize,
39}
40
41impl Default for VM {
42 /// The `VM` constructor.
43 ///
44 /// Program output defaults to [`Stdout`]. To customize
45 /// this behavior, see the [`buffer_output`] method.
46 ///
47 /// [`buffer_output`]: #method.buffer_output
48 /// [`Stdout`]: https://doc.rust-lang.org/std/io/struct.Stdout.html
49 fn default() -> Self {
50 VM {
51 stdout: None,
52 stack: Vec::with_capacity(256),
53 frames: Vec::with_capacity(256),
54 globals: HashMap::new(),
55 open_upvalues: List::new(),
56 objects: List::new(),
57 compiler_roots: Vec::new(),
58 total_allocations: 0,
59 next_gc: 1 << 20,
60 }
61 }
62}
63
64impl<'source> VM {
65 /// Compile and run Lox code from a source string.
66 ///
67 /// ### Errors
68 ///
69 /// Errors are returned in a `Vec`. This is because compilation (not runtime) is able to
70 /// continue after error(s) are encountered.
71 pub fn interpret<T: AsRef<str> + 'source>(&mut self, source: T) -> Result<(), Vec<Error>> {
72 self.compiler_roots.clear();
73
74 let fun = Compiler::compile(source.as_ref(), &mut |f| {
75 let val = Value::Fun(self.alloc(f));
76 self.compiler_roots.push(val.clone());
77 val
78 })
79 .map_err(|errs| errs.into_iter().map(Into::into).collect::<Vec<_>>())?;
80
81 let fun = Value::Fun(self.alloc(fun));
82
83 self.stack.push(fun);
84 self.compiler_roots.clear();
85
86 let _ = self.call_value_from_stack(0);
87
88 self.run().map_err(|err| {
89 let mut line_no = None;
90 let mut backtrace = format!("{}", err);
91
92 for frame in self.frames.iter().rev() {
93 let function = &(*frame.func).name;
94 let (line, _) = frame.chunk().find_line(frame.inst);
95
96 if line_no.is_none() {
97 line_no = Some(line);
98 }
99
100 backtrace += &format!("\n=>> line {} in `{}`", line, function);
101 }
102
103 log::error!("{}", backtrace);
104
105 vec![Error::from_runtime_error(err, line_no)]
106 })
107 }
108
109 /// Define a global variable inside the runtime.
110 ///
111 /// Since globals are late bound in Lox, functions that reference the provided name will see
112 /// the provided value, **even if they were declared in the runtime _before_ calling this
113 /// method**.
114 ///
115 /// ### Example
116 ///
117 /// ```
118 /// use std::io::Read;
119 ///
120 /// let mut vm = lox_lang::VM::default();
121 /// vm.buffer_output(true);
122 ///
123 /// vm.interpret("fun hello() { print world; }");
124 /// vm.define_global("world", "greetings, Earthling.".into());
125 /// vm.interpret("hello();");
126 ///
127 /// let mut buffer = String::new();
128 /// vm.read_to_string(&mut buffer).unwrap();
129 /// assert_eq!(buffer, "greetings, Earthling.\n");
130 /// ```
131 pub fn define_global<T: ?Sized + ToString>(&mut self, name: &T, value: Value) {
132 self.globals
133 .insert(name.to_string().into_boxed_str(), value);
134 }
135}
136
137impl Drop for VM {
138 fn drop(&mut self) {
139 // we free all objects here, as the VM is ultimately an arena for them. all other
140 // references to those objects are weak ones.
141 //
142 // note that we do not touch the `open_upvalues` list - they are actually owned by
143 // `objects`.
144 while let Some(obj) = self.objects.pop() {
145 obj.free();
146 }
147 }
148}