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, level: Option<usize>) -> String {
20        let level = level.unwrap_or(0);
21
22        let mut out = String::new();
23
24        let mut op_idx = 0;
25
26        while op_idx < self.bytecode.codes().len() {
27            let (op_byte_size, disassembled_byte_idx, disassembled_op) =
28                self.disassemble_op(op_idx, level);
29
30            let spacer = if level == 0 {
31                String::new()
32            } else {
33                let s = if level == 1 { "" } else { " " };
34                format!(
35                    "{space:width$}{bar} ",
36                    space = s,
37                    bar = "|",
38                    width = (level - 1) * 2
39                )
40            };
41
42            let op_string = &format!("{spacer}{disassembled_byte_idx} {disassembled_op}");
43            out.push_str(op_string);
44
45            op_idx += op_byte_size;
46        }
47
48        out
49    }
50
51    pub fn disassemble_op(&self, op_idx: usize, level: usize) -> (usize, String, String) {
52        let op_idx_str = format!("{op_idx:04}");
53
54        let _spacer = if level == 0 {
55            String::new()
56        } else {
57            let s = if level == 1 { "" } else { " " };
58            format!(
59                "{space:width$}{bar} ",
60                space = s,
61                bar = "|",
62                width = (level - 1) * 2
63            )
64        };
65
66        let (op_idx_inc, op_str): (usize, String) = match self.bytecode.codes()[op_idx] {
67            opcode::GET => self.disassemble_op_get(op_idx),
68            opcode::CALL => self.disassemble_op_call("CALL", op_idx),
69            opcode::CONSTANT => self.disassemble_op_constant("CONSTANT", op_idx),
70            opcode::TRUE => self.disassemble_op_true("TRUE", op_idx),
71            opcode::FALSE => self.disassemble_op_false("FALSE", op_idx),
72            _ => (1, "".to_string()),
73        };
74
75        (op_idx_inc, op_idx_str, op_str)
76    }
77
78    fn disassemble_op_true(&self, name: &str, op_idx: usize) -> (usize, String) {
79        let constant_op = self.bytecode.codes()[op_idx];
80        assert_eq!(constant_op, opcode::TRUE);
81
82        let string = format!("{name}\n");
83
84        (1, string)
85    }
86
87    fn disassemble_op_false(&self, name: &str, op_idx: usize) -> (usize, String) {
88        let constant_op = self.bytecode.codes()[op_idx];
89        assert_eq!(constant_op, opcode::FALSE);
90
91        let string = format!("{name}\n");
92
93        (1, string)
94    }
95
96    fn disassemble_op_constant(&self, name: &str, op_idx: usize) -> (usize, String) {
97        let constant_op = self.bytecode.codes()[op_idx];
98        assert_eq!(constant_op, opcode::CONSTANT);
99
100        let constant_idx = self.bytecode.codes()[op_idx + 1] as usize;
101
102        let value = self
103            .bytecode
104            .strings()
105            .get(constant_idx)
106            .expect("should have string at index");
107
108        let string = format!("{name:16} {constant_idx:>4} == '{value}'\n");
109
110        (2, string)
111    }
112
113    fn disassemble_op_get(&self, op_idx: usize) -> (usize, String) {
114        let call_op = self.bytecode.codes()[op_idx];
115        assert_eq!(call_op, opcode::GET);
116
117        let lookup_type = self.bytecode.codes()[op_idx + 1];
118        let constant_idx = self.bytecode.codes()[op_idx + 2] as usize;
119
120        let value = match lookup_type {
121            lookup::BUILTIN => {
122                let value = self
123                    .env
124                    .get_builtin(constant_idx)
125                    .unwrap_or_else(|| panic!("undefined builtin: {constant_idx}"));
126                &value.name
127            }
128            lookup::USER_BUILTIN => {
129                let value = self
130                    .env
131                    .get_user_builtin(constant_idx)
132                    .unwrap_or_else(|| panic!("undefined user builtin: {constant_idx}"));
133                &value.name
134            }
135            lookup::VAR => {
136                let value = self
137                    .env
138                    .get_var(constant_idx)
139                    .unwrap_or_else(|| panic!("undefined variable: {constant_idx}"));
140
141                value
142            }
143            lookup::PROMPT => {
144                let value = self
145                    .env
146                    .get_prompt(constant_idx)
147                    .unwrap_or_else(|| panic!("undefined prompt: {constant_idx}"));
148
149                value
150            }
151            lookup::SECRET => {
152                let value = self
153                    .env
154                    .get_secret(constant_idx)
155                    .unwrap_or_else(|| panic!("undefined secret: {constant_idx}"));
156
157                value
158            }
159            lookup::CLIENT_CTX => {
160                let value = self
161                    .env
162                    .get_client_context(constant_idx)
163                    .unwrap_or_else(|| panic!("undefined client context: {constant_idx}"));
164
165                value
166            }
167            _ => panic!("invalid get lookup code: {}", lookup_type),
168        };
169
170        let lookup_type_string = match lookup_type {
171            lookup::BUILTIN => "BUILTIN",
172            lookup::USER_BUILTIN => "USER_BUILTIN",
173            lookup::VAR => "VAR",
174            lookup::PROMPT => "PROMPT",
175            lookup::SECRET => "SECRET",
176            lookup::CLIENT_CTX => "CLIENT_CTX",
177            _ => panic!("invalid get lookup code: {}", lookup_type),
178        };
179
180        let name = "GET";
181
182        let string = format!("{name} {lookup_type_string:12} {constant_idx:>4} == '{value}'\n");
183
184        (3, string)
185    }
186
187    fn disassemble_op_call(&self, name: &str, op_idx: usize) -> (usize, String) {
188        let call_op = self.bytecode.codes()[op_idx];
189        assert_eq!(call_op, opcode::CALL);
190
191        let arg_count = self.bytecode.codes()[op_idx + 1];
192
193        let string = format!("{name:16} ({arg_count} args)\n",);
194
195        (2, string)
196    }
197}