reqlang_expr/
disassembler.rs

1//! The dissassembler and associated types
2
3use crate::{
4    compiler::{CompileTimeEnv, ExprByteCode, opcode},
5    prelude::lookup,
6};
7
8pub struct Disassembler<'bytecode, 'env> {
9    bytecode: &'bytecode ExprByteCode,
10    env: &'env CompileTimeEnv,
11}
12
13impl<'bytecode, 'env> Disassembler<'bytecode, 'env> {
14    pub fn new(bytecode: &'bytecode ExprByteCode, env: &'env CompileTimeEnv) -> Self {
15        Self { bytecode, env }
16    }
17
18    /// Visualize the byte code as text
19    pub fn disassemble(&self) -> String {
20        let mut out = String::new();
21
22        let mut op_idx = 0;
23
24        out.push_str(&format!(
25            "VERSION {}\n----\n",
26            self.bytecode
27                .version()
28                .iter()
29                .map(|byte| byte.to_string())
30                .collect::<Vec<String>>()
31                .join("")
32        ));
33
34        while op_idx < self.bytecode.codes().len() {
35            let (op_byte_size, disassembled_byte_idx, disassembled_op) =
36                self.disassemble_op(op_idx);
37
38            let op_string = &format!("{disassembled_byte_idx} {disassembled_op}");
39            out.push_str(op_string);
40
41            op_idx += op_byte_size;
42        }
43
44        out
45    }
46
47    pub fn disassemble_op(&self, op_idx: usize) -> (usize, String, String) {
48        let op_idx_str = format!("{op_idx:04}");
49
50        let (op_idx_inc, op_str): (usize, String) = match self.bytecode.codes()[op_idx] {
51            opcode::GET => self.disassemble_op_get(op_idx),
52            opcode::CALL => self.disassemble_op_call("CALL", op_idx),
53            opcode::CONSTANT => self.disassemble_op_constant("CONSTANT", op_idx),
54            opcode::TRUE => self.disassemble_op_true("TRUE", op_idx),
55            opcode::FALSE => self.disassemble_op_false("FALSE", op_idx),
56            _ => (1, "".to_string()),
57        };
58
59        (op_idx_inc, op_idx_str, op_str)
60    }
61
62    // TODO
63    // fn disassemble_op_u8(&self, name: &str, op_idx: usize, expected: u8) -> (usize, String) {
64    //     let constant_op = self.bytecode.codes()[op_idx];
65    //     assert_eq!(constant_op, expected);
66
67    //     let string = format!("{name}\n");
68
69    //     (1, string)
70    // }
71
72    fn disassemble_op_true(&self, name: &str, op_idx: usize) -> (usize, String) {
73        let constant_op = self.bytecode.codes()[op_idx];
74        assert_eq!(constant_op, opcode::TRUE);
75
76        let string = format!("{name}\n");
77
78        (1, string)
79    }
80
81    fn disassemble_op_false(&self, name: &str, op_idx: usize) -> (usize, String) {
82        let constant_op = self.bytecode.codes()[op_idx];
83        assert_eq!(constant_op, opcode::FALSE);
84
85        let string = format!("{name}\n");
86
87        (1, string)
88    }
89
90    fn disassemble_op_constant(&self, name: &str, op_idx: usize) -> (usize, String) {
91        let constant_op = self.bytecode.codes()[op_idx];
92        assert_eq!(constant_op, opcode::CONSTANT);
93
94        let constant_idx = self.bytecode.codes()[op_idx + 1] as usize;
95
96        let value = self
97            .bytecode
98            .strings()
99            .get(constant_idx)
100            .expect("should have string at index");
101
102        let string = format!("{name:16} {constant_idx:>4} == '{value}'\n");
103
104        (2, string)
105    }
106
107    fn disassemble_op_get(&self, op_idx: usize) -> (usize, String) {
108        let call_op = self.bytecode.codes()[op_idx];
109        assert_eq!(call_op, opcode::GET);
110
111        let lookup_type = self.bytecode.codes()[op_idx + 1];
112        let constant_idx = self.bytecode.codes()[op_idx + 2] as usize;
113
114        let value = match lookup_type {
115            lookup::BUILTIN => {
116                let value = self.env.get_builtin(constant_idx).unwrap();
117                &value.name
118            }
119            lookup::USER_BUILTIN => {
120                let value = self.env.get_user_builtin(constant_idx).unwrap();
121                &value.name
122            }
123            lookup::VAR => {
124                let value = self.env.get_var(constant_idx).unwrap();
125
126                value.as_str()
127            }
128            lookup::PROMPT => {
129                let value = self.env.get_prompt(constant_idx).unwrap();
130
131                value
132            }
133            lookup::SECRET => {
134                let value = self.env.get_secret(constant_idx).unwrap();
135
136                value
137            }
138            lookup::CLIENT_CTX => {
139                let value = self.env.get_client_context(constant_idx).unwrap();
140
141                value
142            }
143            lookup::TYPE => {
144                let value = self.bytecode.types().get(constant_idx).unwrap();
145
146                &value.name()
147            }
148            _ => panic!("invalid get lookup code: {}", lookup_type),
149        };
150
151        let lookup_type_string = match lookup_type {
152            lookup::BUILTIN => "BUILTIN",
153            lookup::USER_BUILTIN => "USER_BUILTIN",
154            lookup::VAR => "VAR",
155            lookup::PROMPT => "PROMPT",
156            lookup::SECRET => "SECRET",
157            lookup::CLIENT_CTX => "CLIENT_CTX",
158            lookup::TYPE => "TYPE",
159            _ => panic!("invalid get lookup code: {}", lookup_type),
160        };
161
162        let name = "GET";
163
164        let string = format!("{name} {lookup_type_string:12} {constant_idx:>4} == '{value}'\n");
165
166        (3, string)
167    }
168
169    fn disassemble_op_call(&self, name: &str, op_idx: usize) -> (usize, String) {
170        let call_op = self.bytecode.codes()[op_idx];
171        assert_eq!(call_op, opcode::CALL);
172
173        let arg_count = self.bytecode.codes()[op_idx + 1];
174
175        let string = format!("{name:16} ({arg_count} args)\n",);
176
177        (2, string)
178    }
179}