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            opcode::NOT => self.disassemble_op_u8("NOT", op_idx, opcode::NOT),
57            opcode::EQ => self.disassemble_op_u8("EQ", op_idx, opcode::EQ),
58            opcode::TYPE => self.disassemble_op_u8("TYPE", op_idx, opcode::TYPE),
59            _ => (1, "".to_string()),
60        };
61
62        (op_idx_inc, op_idx_str, op_str)
63    }
64
65    fn disassemble_op_u8(&self, name: &str, op_idx: usize, expected: u8) -> (usize, String) {
66        let constant_op = self.bytecode.codes()[op_idx];
67        assert_eq!(constant_op, expected);
68
69        let string = format!("{name}\n");
70
71        (1, string)
72    }
73
74    fn disassemble_op_true(&self, name: &str, op_idx: usize) -> (usize, String) {
75        let constant_op = self.bytecode.codes()[op_idx];
76        assert_eq!(constant_op, opcode::TRUE);
77
78        let string = format!("{name}\n");
79
80        (1, string)
81    }
82
83    fn disassemble_op_false(&self, name: &str, op_idx: usize) -> (usize, String) {
84        let constant_op = self.bytecode.codes()[op_idx];
85        assert_eq!(constant_op, opcode::FALSE);
86
87        let string = format!("{name}\n");
88
89        (1, string)
90    }
91
92    fn disassemble_op_constant(&self, name: &str, op_idx: usize) -> (usize, String) {
93        let constant_op = self.bytecode.codes()[op_idx];
94        assert_eq!(constant_op, opcode::CONSTANT);
95
96        let constant_idx = self.bytecode.codes()[op_idx + 1] as usize;
97
98        let value = self
99            .bytecode
100            .strings()
101            .get(constant_idx)
102            .expect("should have string at index");
103
104        let string = format!("{name:16} {constant_idx:>4} == '{value}'\n");
105
106        (2, string)
107    }
108
109    fn disassemble_op_get(&self, op_idx: usize) -> (usize, String) {
110        let call_op = self.bytecode.codes()[op_idx];
111        assert_eq!(call_op, opcode::GET);
112
113        let lookup_type = self.bytecode.codes()[op_idx + 1];
114        let constant_idx = self.bytecode.codes()[op_idx + 2] as usize;
115
116        let value = match lookup_type {
117            lookup::BUILTIN => {
118                let value = self.env.get_builtin(constant_idx).unwrap();
119                &value.name
120            }
121            lookup::USER_BUILTIN => {
122                let value = self.env.get_user_builtin(constant_idx).unwrap();
123                &value.name
124            }
125            lookup::VAR => {
126                let value = self.env.get_var(constant_idx).unwrap();
127
128                value
129            }
130            lookup::PROMPT => {
131                let value = self.env.get_prompt(constant_idx).unwrap();
132
133                value
134            }
135            lookup::SECRET => {
136                let value = self.env.get_secret(constant_idx).unwrap();
137
138                value
139            }
140            lookup::CLIENT_CTX => {
141                let value = self.env.get_client_context(constant_idx).unwrap();
142
143                value
144            }
145            _ => panic!("invalid get lookup code: {}", lookup_type),
146        };
147
148        let lookup_type_string = match lookup_type {
149            lookup::BUILTIN => "BUILTIN",
150            lookup::USER_BUILTIN => "USER_BUILTIN",
151            lookup::VAR => "VAR",
152            lookup::PROMPT => "PROMPT",
153            lookup::SECRET => "SECRET",
154            lookup::CLIENT_CTX => "CLIENT_CTX",
155            _ => panic!("invalid get lookup code: {}", lookup_type),
156        };
157
158        let name = "GET";
159
160        let string = format!("{name} {lookup_type_string:12} {constant_idx:>4} == '{value}'\n");
161
162        (3, string)
163    }
164
165    fn disassemble_op_call(&self, name: &str, op_idx: usize) -> (usize, String) {
166        let call_op = self.bytecode.codes()[op_idx];
167        assert_eq!(call_op, opcode::CALL);
168
169        let arg_count = self.bytecode.codes()[op_idx + 1];
170
171        let string = format!("{name:16} ({arg_count} args)\n",);
172
173        (2, string)
174    }
175}