reqlang_expr/
vm.rs

1//! The virtual machine and associated types
2
3use 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        // Confirm the current op code is CALL
98        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![])); // 99 as invalid opcode
247        let env = CompileTimeEnv::default();
248        let runtime_env = RuntimeEnv::default();
249
250        // Attempt to interpret the bytecode, expecting a panic due to invalid opcode 99
251        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![])); // 99 as invalid opcode
260        let env = CompileTimeEnv::default();
261        let runtime_env = RuntimeEnv::default();
262
263        // Attempt to interpret the bytecode, expecting a panic due to invalid opcode 99
264        let _ = vm.interpret(bytecode, &env, &runtime_env);
265    }
266}