1use std::ops::Range;
4
5use crate::{
6 compiler::{
7 CompileTimeEnv, ExprByteCode,
8 lookup::{BUILTIN, PROMPT, SECRET, VAR},
9 opcode,
10 },
11 errors::{ExprError, ExprResult},
12 prelude::lookup::{CLIENT_CTX, USER_BUILTIN},
13 value::Value,
14};
15
16#[derive(Debug, Clone, Default)]
17pub struct RuntimeEnv {
18 pub vars: Vec<String>,
19 pub prompts: Vec<String>,
20 pub secrets: Vec<String>,
21 pub client_context: Vec<Value>,
22}
23
24impl RuntimeEnv {
25 pub fn add_to_client_context(&mut self, index: usize, value: Value) {
26 if index < self.client_context.len() {
27 self.client_context[index] = value;
28 } else {
29 self.client_context.push(value);
30 }
31 }
32}
33
34#[derive(Debug)]
35pub struct Vm {
36 bytecode: Option<Box<ExprByteCode>>,
37 ip: usize,
38 stack: Vec<Value>,
39}
40
41impl Vm {
42 pub fn new() -> Self {
43 Self {
44 bytecode: None,
45 ip: 0,
46 stack: vec![],
47 }
48 }
49
50 pub fn interpret(
51 &mut self,
52 bytecode: Box<ExprByteCode>,
53 env: &CompileTimeEnv,
54 runtime_env: &RuntimeEnv,
55 ) -> ExprResult<Value> {
56 self.bytecode = Some(bytecode);
57 self.ip = 0;
58
59 let mut errs: Vec<(ExprError, Range<usize>)> = vec![];
60
61 while let Some(op_code) = self
62 .bytecode
63 .as_ref()
64 .and_then(|bc| bc.codes().get(self.ip))
65 {
66 if let Err(e) = self.interpret_op(env, runtime_env, *op_code) {
67 errs.extend(e);
68 }
69 }
70
71 if !errs.is_empty() {
72 return Err(errs.into());
73 }
74
75 let value = self.stack_pop()?;
76
77 Ok(value)
78 }
79
80 fn interpret_op(
81 &mut self,
82 env: &CompileTimeEnv,
83 runtime_env: &RuntimeEnv,
84 op_code: u8,
85 ) -> ExprResult<()> {
86 match op_code {
87 opcode::CALL => self.op_call(),
88 opcode::CONSTANT => self.op_constant(),
89 opcode::GET => self.op_get(env, runtime_env),
90 opcode::TRUE => self.op_true(),
91 opcode::FALSE => self.op_false(),
92 _ => panic!("Invalid OP code: {op_code}"),
93 }
94 }
95
96 fn op_call(&mut self) -> ExprResult<()> {
97 assert_eq!(opcode::CALL, self.read_u8(), "Expected CALL opcode");
99
100 let arg_count = self.read_u8() as usize;
101
102 let mut args: Vec<Value> = vec![];
103
104 for _ in 0..arg_count {
105 args.push(self.stack_pop()?);
106 }
107
108 args.reverse();
109
110 let value = self.stack_pop()?;
111
112 let builtin = value.get_func().func.clone();
113
114 let result = builtin(args);
115
116 self.stack_push(result);
117
118 Ok(())
119 }
120
121 fn op_get(&mut self, env: &CompileTimeEnv, runtime_env: &RuntimeEnv) -> ExprResult<()> {
122 assert_eq!(opcode::GET, self.read_u8(), "Expected GET opcode");
123 let get_lookup = self.read_u8();
124 let get_idx = self.read_u8() as usize;
125
126 match get_lookup {
127 BUILTIN => {
128 let value = env
129 .get_builtin(get_idx)
130 .unwrap_or_else(|| panic!("undefined builtin: {get_idx}"));
131 self.stack_push(Value::Fn(value.clone()));
132 }
133 USER_BUILTIN => {
134 let value = env
135 .get_user_builtin(get_idx)
136 .unwrap_or_else(|| panic!("undefined user builtin: {get_idx}"));
137 self.stack_push(Value::Fn(value.clone()));
138 }
139 VAR => {
140 let value = env
141 .get_var(get_idx)
142 .and_then(|_| runtime_env.vars.get(get_idx))
143 .unwrap_or_else(|| panic!("undefined variable: {get_idx}"));
144
145 self.stack_push(Value::String(value.clone()));
146 }
147 PROMPT => {
148 let value = env
149 .get_prompt(get_idx)
150 .and_then(|_| runtime_env.prompts.get(get_idx))
151 .unwrap_or_else(|| panic!("undefined prompt: {get_idx}"));
152
153 self.stack_push(Value::String(value.clone()));
154 }
155 SECRET => {
156 let value = env
157 .get_secret(get_idx)
158 .and_then(|_| runtime_env.secrets.get(get_idx))
159 .unwrap_or_else(|| panic!("undefined secret: {get_idx}"));
160
161 self.stack_push(Value::String(value.clone()));
162 }
163 CLIENT_CTX => {
164 let value = env
165 .get_client_context(get_idx)
166 .and_then(|_| runtime_env.client_context.get(get_idx))
167 .unwrap_or_else(|| panic!("undefined client context: {get_idx}"));
168
169 self.stack_push(value.clone());
170 }
171 _ => panic!("Invalid get lookup code: {}", get_lookup),
172 };
173
174 Ok(())
175 }
176
177 fn op_constant(&mut self) -> ExprResult<()> {
178 assert_eq!(opcode::CONSTANT, self.read_u8(), "Expected CONSTANT opcode");
179
180 let get_idx = self.read_u8() as usize;
181
182 let s = self
183 .bytecode
184 .as_ref()
185 .expect("should have bytecode")
186 .strings()
187 .get(get_idx)
188 .unwrap_or_else(|| panic!("undefined string: {}", get_idx));
189
190 self.stack_push(Value::String(s.clone()));
191
192 Ok(())
193 }
194
195 fn op_true(&mut self) -> ExprResult<()> {
196 assert_eq!(opcode::TRUE, self.read_u8(), "Expected TRUE opcode");
197
198 self.stack_push(Value::Bool(true));
199
200 Ok(())
201 }
202
203 fn op_false(&mut self) -> ExprResult<()> {
204 assert_eq!(opcode::FALSE, self.read_u8(), "Expected FALSE opcode");
205
206 self.stack_push(Value::Bool(false));
207
208 Ok(())
209 }
210
211 fn stack_push(&mut self, value: Value) {
212 self.stack.push(value);
213 }
214
215 fn stack_pop(&mut self) -> ExprResult<Value> {
216 Ok(self
217 .stack
218 .pop()
219 .expect("should have a value to pop from the stack"))
220 }
221
222 fn read_u8(&mut self) -> u8 {
223 let current_ip = self.ip as u8;
224
225 self.ip += 1;
226
227 *self
228 .bytecode
229 .as_ref()
230 .expect("should have bytecode")
231 .codes()
232 .get(current_ip as usize)
233 .expect("should have op in bytecode at {}")
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 #[test]
242 #[should_panic(expected = "Invalid OP code: 99")]
243 fn test_invalid_opcode_99() {
244 let mut vm = Vm::new();
245
246 let bytecode = Box::new(ExprByteCode::new(vec![99], vec![])); let env = CompileTimeEnv::default();
248 let runtime_env = RuntimeEnv::default();
249
250 let _ = vm.interpret(bytecode, &env, &runtime_env);
252 }
253
254 #[test]
255 #[should_panic(expected = "Invalid get lookup code: 99")]
256 fn test_invalid_look_99() {
257 let mut vm = Vm::new();
258
259 let bytecode = Box::new(ExprByteCode::new(vec![opcode::GET, 99, 0], vec![])); let env = CompileTimeEnv::default();
261 let runtime_env = RuntimeEnv::default();
262
263 let _ = vm.interpret(bytecode, &env, &runtime_env);
265 }
266}