Skip to main content

reqlang_expr/
vm.rs

1//! The virtual machine and associated types
2
3use crate::{
4    compiler::{
5        CompileTimeEnv, ExprByteCode,
6        lookup::{BUILTIN, PROMPT, SECRET, TYPE, VAR},
7        opcode,
8    },
9    errors::{ExprErrorS, ExprResult, RuntimeError},
10    prelude::lookup::{CLIENT_CTX, USER_BUILTIN},
11    types::Type,
12    value::Value,
13};
14
15#[derive(Debug, Clone, Default)]
16pub struct RuntimeEnv {
17    pub vars: Vec<String>,
18    pub prompts: Vec<String>,
19    pub secrets: Vec<String>,
20    pub client_context: Vec<Value>,
21}
22
23impl RuntimeEnv {
24    pub fn add_to_client_context(&mut self, index: usize, value: Value) {
25        if index < self.client_context.len() {
26            self.client_context[index] = value;
27        } else {
28            self.client_context.push(value);
29        }
30    }
31}
32
33#[derive(Debug)]
34pub struct Vm {
35    bytecode: Option<Box<ExprByteCode>>,
36    ip: usize,
37    stack: Vec<Value>,
38}
39
40impl Default for Vm {
41    fn default() -> Self {
42        Self::new()
43    }
44}
45
46impl Vm {
47    pub fn new() -> Self {
48        Self {
49            bytecode: None,
50            ip: 0,
51            stack: vec![],
52        }
53    }
54
55    pub fn interpret(
56        &mut self,
57        bytecode: Box<ExprByteCode>,
58        env: &CompileTimeEnv,
59        runtime_env: &RuntimeEnv,
60    ) -> ExprResult<Value> {
61        self.bytecode = Some(bytecode);
62        self.ip = 0;
63
64        let mut errs: Vec<ExprErrorS> = vec![];
65
66        while let Some(op_code) = self
67            .bytecode
68            .as_ref()
69            .and_then(|bc| bc.codes().get(self.ip))
70        {
71            if let Err(e) = self.interpret_op(env, runtime_env, *op_code) {
72                errs.extend(e);
73            }
74        }
75
76        if !errs.is_empty() {
77            return Err(errs);
78        }
79
80        self.stack_pop()
81    }
82
83    fn interpret_op(
84        &mut self,
85        env: &CompileTimeEnv,
86        runtime_env: &RuntimeEnv,
87        op_code: u8,
88    ) -> ExprResult<()> {
89        match op_code {
90            opcode::CALL => self.op_call(),
91            opcode::CONSTANT => self.op_constant(),
92            opcode::GET => self.op_get(env, runtime_env),
93            opcode::TRUE => self.op_true(),
94            opcode::FALSE => self.op_false(),
95            _ => panic!("Invalid OP code: {op_code}"),
96        }
97    }
98
99    fn op_call(&mut self) -> ExprResult<()> {
100        // Consume current op: CALL
101        self.read_u8();
102
103        let arg_count = self.read_u8() as usize;
104
105        let mut args: Vec<Value> = vec![];
106
107        for _ in 0..arg_count {
108            args.push(self.stack_pop()?);
109        }
110
111        args.reverse();
112
113        let value = self.stack_pop()?;
114
115        let builtin = value.get_func()?.func;
116
117        let result = builtin(args);
118
119        self.stack_push(result?);
120
121        Ok(())
122    }
123
124    fn op_get(&mut self, env: &CompileTimeEnv, runtime_env: &RuntimeEnv) -> ExprResult<()> {
125        // Consume current op: GET
126        self.read_u8();
127
128        let get_lookup = self.read_u8();
129        let get_idx = self.read_u8() as usize;
130
131        match get_lookup {
132            BUILTIN => {
133                let value = env
134                    .get_builtin(get_idx)
135                    .unwrap_or_else(|| panic!("undefined builtin: {get_idx}"));
136                self.stack_push(Value::Fn(value.clone().into()));
137            }
138            USER_BUILTIN => {
139                let value = env
140                    .get_user_builtin(get_idx)
141                    .unwrap_or_else(|| panic!("undefined user builtin: {get_idx}"));
142                self.stack_push(Value::Fn(value.clone().into()));
143            }
144            VAR => {
145                let value = env
146                    .get_var(get_idx)
147                    .and_then(|_| runtime_env.vars.get(get_idx))
148                    .unwrap_or_else(|| panic!("undefined variable: {get_idx}"));
149
150                self.stack_push(Value::String(value.clone()));
151            }
152            PROMPT => {
153                let value = env
154                    .get_prompt(get_idx)
155                    .and_then(|_| runtime_env.prompts.get(get_idx))
156                    .unwrap_or_else(|| panic!("undefined prompt: {get_idx}"));
157
158                self.stack_push(Value::String(value.clone()));
159            }
160            SECRET => {
161                let value = env
162                    .get_secret(get_idx)
163                    .and_then(|_| runtime_env.secrets.get(get_idx))
164                    .unwrap_or_else(|| panic!("undefined secret: {get_idx}"));
165
166                self.stack_push(Value::String(value.clone()));
167            }
168            CLIENT_CTX => {
169                let value = env
170                    .get_client_context(get_idx)
171                    .and_then(|_| runtime_env.client_context.get(get_idx))
172                    .unwrap_or_else(|| panic!("undefined client context: {get_idx}"));
173
174                self.stack_push(value.clone());
175            }
176            TYPE => {
177                let ty = self
178                    .bytecode
179                    .as_ref()
180                    .unwrap()
181                    .types()
182                    .get(get_idx)
183                    .unwrap_or_else(|| panic!("undefined type: {get_idx}"));
184
185                if ty.is_type() {
186                    self.stack_push(Value::Type(ty.clone().into()));
187                } else {
188                    self.stack_push(Value::Type(Type::Type(ty.clone().into()).into()));
189                }
190            }
191            _ => panic!("Invalid get lookup code: {get_lookup}"),
192        };
193
194        Ok(())
195    }
196
197    fn op_constant(&mut self) -> ExprResult<()> {
198        // Consume current op: CONSTANT
199        self.read_u8();
200
201        let get_idx = self.read_u8() as usize;
202
203        let s = self
204            .bytecode
205            .as_ref()
206            .expect("should have bytecode")
207            .constants()
208            .get(get_idx)
209            .unwrap_or_else(|| panic!("undefined constant: {get_idx}"));
210
211        self.stack_push(s.clone());
212
213        Ok(())
214    }
215
216    fn op_true(&mut self) -> ExprResult<()> {
217        // Consume current op: TRUE
218        self.read_u8();
219
220        self.stack_push(Value::Bool(true));
221
222        Ok(())
223    }
224
225    fn op_false(&mut self) -> ExprResult<()> {
226        // Consume current op: FALSE
227        self.read_u8();
228
229        self.stack_push(Value::Bool(false));
230
231        Ok(())
232    }
233
234    fn stack_push(&mut self, value: Value) {
235        self.stack.push(value);
236    }
237
238    fn stack_pop(&mut self) -> ExprResult<Value> {
239        if let Some(value) = self.stack.pop() {
240            return Ok(value);
241        };
242
243        Err(vec![(RuntimeError::EmptyStack.into(), 0..0)])
244    }
245
246    fn read_u8(&mut self) -> u8 {
247        let current_ip = self.ip as u8;
248
249        self.ip += 1;
250
251        *self
252            .bytecode
253            .as_ref()
254            .expect("should have bytecode")
255            .codes()
256            .get(current_ip as usize)
257            .expect("should have op in bytecode at {}")
258    }
259}
260
261#[cfg(test)]
262mod tests {
263    use crate::{compiler::get_version_bytes, errors::ExprError, prelude::lookup};
264
265    use super::*;
266
267    #[test]
268    fn test_popping_from_empty_stack() {
269        let mut vm = Vm::new();
270
271        let mut codes = get_version_bytes().to_vec();
272
273        // Get builtin function `id`
274        codes.push(opcode::GET);
275        codes.push(lookup::BUILTIN);
276        codes.push(0);
277
278        // Specify call will be passing 1 argument
279        // but don't push the bytecode for the argument passed
280        codes.push(opcode::CALL);
281        codes.push(1);
282
283        let bytecode = Box::new(ExprByteCode::new(codes, vec![], vec![]));
284        let env = CompileTimeEnv::default();
285        let runtime_env = RuntimeEnv::default();
286
287        assert_eq!(
288            Err(vec![(
289                ExprError::RuntimeError(RuntimeError::EmptyStack),
290                0..0
291            )]),
292            vm.interpret(bytecode, &env, &runtime_env)
293        );
294    }
295
296    #[test]
297    #[should_panic(expected = "Invalid OP code: 99")]
298    fn test_invalid_opcode_99() {
299        let mut vm = Vm::new();
300
301        let mut codes = get_version_bytes().to_vec();
302        codes.push(99);
303
304        let bytecode = Box::new(ExprByteCode::new(codes, vec![], vec![]));
305        let env = CompileTimeEnv::default();
306        let runtime_env = RuntimeEnv::default();
307
308        // Attempt to interpret the bytecode, expecting a panic due to invalid opcode 99
309        let _ = vm.interpret(bytecode, &env, &runtime_env);
310    }
311
312    #[test]
313    #[should_panic(expected = "Invalid get lookup code: 99")]
314    fn test_invalid_look_99() {
315        let mut vm = Vm::new();
316
317        let mut codes = get_version_bytes().to_vec();
318        codes.push(opcode::GET);
319        codes.push(99);
320        codes.push(0);
321
322        let bytecode = Box::new(ExprByteCode::new(codes, vec![], vec![]));
323        let env = CompileTimeEnv::default();
324        let runtime_env = RuntimeEnv::default();
325
326        // Attempt to interpret the bytecode, expecting a panic due to invalid opcode 99
327        let _ = vm.interpret(bytecode, &env, &runtime_env);
328    }
329
330    #[test]
331    #[should_panic(expected = "undefined variable: 99")]
332    fn undefined_variable() {
333        let mut vm = Vm::new();
334
335        let mut codes = get_version_bytes().to_vec();
336        codes.push(opcode::GET);
337        codes.push(lookup::VAR);
338        codes.push(99);
339
340        let bytecode = Box::new(ExprByteCode::new(codes, vec![], vec![]));
341        let env = CompileTimeEnv::default();
342        let runtime_env = RuntimeEnv::default();
343
344        let _ = vm.interpret(bytecode, &env, &runtime_env);
345    }
346
347    #[test]
348    #[should_panic(expected = "undefined prompt: 99")]
349    fn undefined_prompt() {
350        let mut vm = Vm::new();
351
352        let mut codes = get_version_bytes().to_vec();
353        codes.push(opcode::GET);
354        codes.push(lookup::PROMPT);
355        codes.push(99);
356
357        let bytecode = Box::new(ExprByteCode::new(codes, vec![], vec![]));
358        let env = CompileTimeEnv::default();
359        let runtime_env = RuntimeEnv::default();
360
361        let _ = vm.interpret(bytecode, &env, &runtime_env);
362    }
363
364    #[test]
365    #[should_panic(expected = "undefined secret: 99")]
366    fn undefined_secret() {
367        let mut vm = Vm::new();
368
369        let mut codes = get_version_bytes().to_vec();
370        codes.push(opcode::GET);
371        codes.push(lookup::SECRET);
372        codes.push(99);
373
374        let bytecode = Box::new(ExprByteCode::new(codes, vec![], vec![]));
375        let env = CompileTimeEnv::default();
376        let runtime_env = RuntimeEnv::default();
377
378        let _ = vm.interpret(bytecode, &env, &runtime_env);
379    }
380
381    #[test]
382    #[should_panic(expected = "undefined builtin: 255")]
383    fn undefined_builtin() {
384        let mut vm = Vm::new();
385
386        let mut codes = get_version_bytes().to_vec();
387        codes.push(opcode::GET);
388        codes.push(lookup::BUILTIN);
389        codes.push(255);
390
391        let bytecode = Box::new(ExprByteCode::new(codes, vec![], vec![]));
392        let env = CompileTimeEnv::default();
393        let runtime_env = RuntimeEnv::default();
394
395        let _ = vm.interpret(bytecode, &env, &runtime_env);
396    }
397
398    #[test]
399    #[should_panic(expected = "undefined user builtin: 255")]
400    fn undefined_user_builtin() {
401        let mut vm = Vm::new();
402
403        let mut codes = get_version_bytes().to_vec();
404        codes.push(opcode::GET);
405        codes.push(lookup::USER_BUILTIN);
406        codes.push(255);
407
408        let bytecode = Box::new(ExprByteCode::new(codes, vec![], vec![]));
409        let env = CompileTimeEnv::default();
410        let runtime_env = RuntimeEnv::default();
411
412        let _ = vm.interpret(bytecode, &env, &runtime_env);
413    }
414
415    #[test]
416    #[should_panic(expected = "undefined client context: 255")]
417    fn undefined_client_context() {
418        let mut vm = Vm::new();
419
420        let mut codes = get_version_bytes().to_vec();
421        codes.push(opcode::GET);
422        codes.push(lookup::CLIENT_CTX);
423        codes.push(255);
424
425        let bytecode = Box::new(ExprByteCode::new(codes, vec![], vec![]));
426        let env = CompileTimeEnv::default();
427        let runtime_env = RuntimeEnv::default();
428
429        let _ = vm.interpret(bytecode, &env, &runtime_env);
430    }
431
432    #[test]
433    #[should_panic(expected = "undefined type: 255")]
434    fn undefined_type() {
435        let mut vm = Vm::new();
436
437        let mut codes = get_version_bytes().to_vec();
438        codes.push(opcode::GET);
439        codes.push(lookup::TYPE);
440        codes.push(255);
441
442        let bytecode = Box::new(ExprByteCode::new(codes, vec![], vec![]));
443        let env = CompileTimeEnv::default();
444        let runtime_env = RuntimeEnv::default();
445
446        let _ = vm.interpret(bytecode, &env, &runtime_env);
447    }
448
449    #[test]
450    #[should_panic(expected = "undefined constant: 255")]
451    fn undefined_constant() {
452        let mut vm = Vm::new();
453
454        let mut codes = get_version_bytes().to_vec();
455        codes.push(opcode::CONSTANT);
456        codes.push(255);
457
458        let bytecode = Box::new(ExprByteCode::new(codes, vec![], vec![]));
459        let env = CompileTimeEnv::default();
460        let runtime_env = RuntimeEnv::default();
461
462        let _ = vm.interpret(bytecode, &env, &runtime_env);
463    }
464}