reqlang_expr/
vm.rs

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