1use crate::{
2 HeapAllocator,
3 backend::chunk::{Chunk, OpCode},
4};
5
6#[allow(dead_code)]
7pub fn disassemble_chunk(chunk: &Chunk, allocator: &HeapAllocator, name: &str) {
8 println!("== {} ==", name);
9
10 let mut offset = 0;
11
12 while offset < chunk.code.len() {
13 offset = disassemble_instruction(chunk, allocator, offset);
14 }
15}
16
17pub fn disassemble_instruction(chunk: &Chunk, allocator: &HeapAllocator, offset: usize) -> usize {
18 print!("{:04} ", offset);
19
20 if offset > 0 && chunk.locs[offset].line == chunk.locs[offset - 1].line {
21 print!(" | ");
22 } else {
23 print!("{:04} ", chunk.locs[offset].line);
24 }
25
26 let instruction = chunk.code[offset];
27 let opcode = OpCode::from(instruction);
28
29 match opcode {
30 OpCode::Constant => constant_instruction("OP_CONSTANT", chunk, allocator, offset),
31 OpCode::Constant16 => constant16_instruction("OP_CONSTANT16", chunk, allocator, offset),
32 OpCode::Return => simple_instruction("OP_RETURN", offset),
33 OpCode::Negate => simple_instruction("OP_NEGATE", offset),
34 OpCode::Add => simple_instruction("OP_ADD", offset),
35 OpCode::Subtract => simple_instruction("OP_SUBTRACT", offset),
36 OpCode::Multiply => simple_instruction("OP_MULTIPLY", offset),
37 OpCode::Divide => simple_instruction("OP_DIVIDE", offset),
38 OpCode::Nil => simple_instruction("OP_NIL", offset),
39 OpCode::True => simple_instruction("OP_TRUE", offset),
40 OpCode::False => simple_instruction("OP_FALSE", offset),
41 OpCode::Not => simple_instruction("OP_NOT", offset),
42 OpCode::Equal => simple_instruction("OP_EQUAL", offset),
43 OpCode::Greater => simple_instruction("OP_GREATER", offset),
44 OpCode::Less => simple_instruction("OP_LESS", offset),
45 OpCode::GreaterEqual => simple_instruction("OP_GREATER_EQUAL", offset),
46 OpCode::LessEqual => simple_instruction("OP_LESS_EQUAL", offset),
47 OpCode::Modulo => simple_instruction("OP_MODULO", offset),
48 OpCode::Pop => simple_instruction("OP_POP", offset),
49 OpCode::DefineGlobal => {
50 string_constant_instruction("OP_DEFINE_GLOBAL", chunk, allocator, offset)
51 }
52 OpCode::GetGlobal => string_constant_instruction("OP_GET_GLOBAL", chunk, allocator, offset),
53 OpCode::SetGlobal => string_constant_instruction("OP_SET_GLOBAL", chunk, allocator, offset),
54 OpCode::DefineGlobal16 => {
55 string_constant_16_instruction("OP_DEFINE_GLOBAL16", chunk, allocator, offset)
56 }
57 OpCode::GetGlobal16 => {
58 string_constant_16_instruction("OP_GET_GLOBAL16", chunk, allocator, offset)
59 }
60 OpCode::SetGlobal16 => {
61 string_constant_16_instruction("OP_SET_GLOBAL16", chunk, allocator, offset)
62 }
63 OpCode::GetLocal => byte_instruction("OP_GET_LOCAL", chunk, offset),
64 OpCode::SetLocal => byte_instruction("OP_SET_LOCAL", chunk, offset),
65 OpCode::JumpIfFalse => jump_instruction("OP_JUMP_IF_FALSE", 1, chunk, offset),
66 OpCode::Jump => jump_instruction("OP_JUMP", 1, chunk, offset),
67 OpCode::Loop => jump_instruction("OP_LOOP", 1, chunk, offset),
68 OpCode::Call => byte_instruction("OP_CALL", chunk, offset),
69 OpCode::CloseUpvalue => simple_instruction("OP_CLOSE_UPVALUE", offset),
70 OpCode::Closure => {
71 let mut offset = offset;
72 offset += 1; let constant = chunk.code[offset];
74 offset += 1;
75
76 print!("{:<16} {:4} '", "OP_CLOSURE", constant);
77 let value = chunk.constants[constant as usize];
78 println!("{}'", value.to_display_string(allocator));
79 let function_obj = value.as_function().map(|f| allocator.get_function(f));
80
81 if let Some(function) = function_obj {
82 for _j in 0..function.upvalue_count {
83 let is_local = chunk.code[offset];
84 offset += 1;
85 let index = chunk.code[offset];
86 offset += 1;
87
88 println!(
89 "{:04} | {} {}",
90 offset - 2,
91 if is_local != 0 { "local" } else { "upvalue" },
92 index
93 );
94 }
95 }
96
97 offset
98 }
99 OpCode::Closure16 => {
100 let mut offset = offset;
101 offset += 1; let high_byte = chunk.code[offset] as usize;
103 offset += 1;
104 let low_byte = chunk.code[offset] as usize;
105 offset += 1;
106 let constant = (high_byte << 8) | low_byte;
107
108 print!("{:<16} {:4} '", "OP_CLOSURE16", constant);
109 let value = chunk.constants[constant];
110 println!("{}'", value.to_display_string(allocator));
111 let function_obj = value.as_function().map(|f| allocator.get_function(f));
112
113 if let Some(function) = function_obj {
114 for _j in 0..function.upvalue_count {
115 let is_local = chunk.code[offset];
116 offset += 1;
117 let index = chunk.code[offset];
118 offset += 1;
119
120 println!(
121 "{:04} | {} {}",
122 offset - 2,
123 if is_local != 0 { "local" } else { "upvalue" },
124 index
125 );
126 }
127 }
128
129 offset
130 }
131 OpCode::GetUpvalue => byte_instruction("OP_GET_UPVALUE", chunk, offset),
132 OpCode::SetUpvalue => byte_instruction("OP_GET_UPVALUE", chunk, offset),
133 OpCode::Class => constant_instruction("OP_CLASS", chunk, allocator, offset),
134 OpCode::Class16 => constant16_instruction("OP_CLASS16", chunk, allocator, offset),
135 OpCode::GetProperty => {
136 string_constant_instruction("OP_GET_PROPERTY", chunk, allocator, offset)
137 }
138 OpCode::SetProperty => {
139 string_constant_instruction("OP_SET_PROPERTY", chunk, allocator, offset)
140 }
141 OpCode::Method => string_constant_instruction("OP_METHOD", chunk, allocator, offset),
142 OpCode::GetProperty16 => {
143 string_constant_16_instruction("OP_GET_PROPERTY16", chunk, allocator, offset)
144 }
145 OpCode::SetProperty16 => {
146 string_constant_16_instruction("OP_SET_PROPERTY16", chunk, allocator, offset)
147 }
148 OpCode::Method16 => string_constant_16_instruction("OP_METHOD16", chunk, allocator, offset),
149 OpCode::Invoke => invoke_instruction("OP_INVOKE", chunk, allocator, offset),
150 OpCode::Invoke16 => invoke_16_instruction("OP_INVOKE16", chunk, allocator, offset),
151 OpCode::Inherit => simple_instruction("OP_INHERIT", offset),
152 OpCode::GetSuper => string_constant_instruction("OP_GET_SUPER", chunk, allocator, offset),
153 OpCode::GetSuper16 => {
154 string_constant_16_instruction("OP_GET_SUPER16", chunk, allocator, offset)
155 }
156 OpCode::SuperInvoke => invoke_instruction("OP_SUPER_INVOKE", chunk, allocator, offset),
157 OpCode::SuperInvoke16 => {
158 invoke_16_instruction("OP_SUPER_INVOKE16", chunk, allocator, offset)
159 }
160 OpCode::InitField => string_constant_instruction("OP_INIT_FIELD", chunk, allocator, offset),
161 OpCode::InitField16 => {
162 string_constant_16_instruction("OP_INIT_FIELD16", chunk, allocator, offset)
163 }
164 OpCode::ArrayLiteral => byte_instruction("OP_ARRAY_LITERAL", chunk, offset),
165 OpCode::GetArrayIndex => simple_instruction("OP_GET_ARRAY_INDEX", offset),
166 OpCode::SetArrayIndex => simple_instruction("OP_SET_ARRAY_INDEX", offset),
167 OpCode::ObjectLiteral => byte_instruction("OP_OBJECT_LITERAL", chunk, offset),
168 OpCode::Is => simple_instruction("OP_IS", offset),
169 OpCode::GetOptionalProperty => {
170 string_constant_instruction("OP_GET_OPTIONAL_PROPERTY", chunk, allocator, offset)
171 }
172 OpCode::GetOptionalProperty16 => {
173 string_constant_16_instruction("OP_GET_OPTIONAL_PROPERTY16", chunk, allocator, offset)
174 }
175 OpCode::JumpIfNil => jump_instruction("OP_JUMP_IF_NIL", 1, chunk, offset),
176 OpCode::TailCall => byte_instruction("OP_TAIL_CALL", chunk, offset),
177 OpCode::Module => string_constant_instruction("OP_MODULE", chunk, allocator, offset),
178 OpCode::Module16 => string_constant_16_instruction("OP_MODULE", chunk, allocator, offset),
179 OpCode::ModuleReturn => simple_instruction("OP_MODULE_RETURN", offset),
180 }
181}
182
183fn constant_instruction(
184 name: &str,
185 chunk: &Chunk,
186 allocator: &HeapAllocator,
187 offset: usize,
188) -> usize {
189 let constant = chunk.code[offset + 1] as usize;
190 print!("{:<16} {:4} '", name, constant);
191
192 chunk
193 .constants
194 .get(constant)
195 .copied()
196 .unwrap_or_default()
197 .print(allocator);
198
199 println!("'");
200 offset + 2
201}
202
203fn string_constant_instruction(
204 name: &str,
205 chunk: &Chunk,
206 allocator: &HeapAllocator,
207 offset: usize,
208) -> usize {
209 let constant = chunk.code[offset + 1] as usize;
210 print!("{:<16} {:4} '", name, constant);
211
212 if let Some(value) = chunk.constants.get(constant) {
213 match value.as_string() {
214 Some(handle) => {
215 let string_value = allocator.strings.get(handle);
216 print!("{}", string_value);
217 }
218 _ => {
219 print!("<not a string constant>");
220 }
221 }
222 } else {
223 print!("<invalid constant index>");
224 }
225
226 println!("'");
227 offset + 2
228}
229
230fn simple_instruction(name: &str, offset: usize) -> usize {
231 println!("{}", name);
232 offset + 1
233}
234
235fn byte_instruction(name: &str, chunk: &Chunk, offset: usize) -> usize {
236 let slot = chunk.code[offset + 1];
237 println!("{:<16} {:4}", name, slot);
238 offset + 2
239}
240
241fn jump_instruction(name: &str, sign: i32, chunk: &Chunk, offset: usize) -> usize {
242 let jump = ((chunk.code[offset + 1] as u16) << 8) | (chunk.code[offset + 2] as u16);
243 let target = offset as i32 + 3 + sign * (jump as i32);
244 println!("{:<16} {:4} -> {}", name, offset, target);
245 offset + 3
246}
247
248fn constant16_instruction(
249 name: &str,
250 chunk: &Chunk,
251 allocator: &HeapAllocator,
252 offset: usize,
253) -> usize {
254 let high_byte = chunk.code[offset + 1] as usize;
255 let low_byte = chunk.code[offset + 2] as usize;
256 let constant = (high_byte << 8) | low_byte;
257 print!("{:<16} {:4} '", name, constant);
258
259 chunk
260 .constants
261 .get(constant)
262 .copied()
263 .unwrap_or_default()
264 .print(allocator);
265
266 println!("'");
267 offset + 3
268}
269
270fn invoke_instruction(
271 name: &str,
272 chunk: &Chunk,
273 allocator: &HeapAllocator,
274 offset: usize,
275) -> usize {
276 let constant = chunk.code[offset + 1];
277 let arg_count = chunk.code[offset + 2];
278 print!("{:<16} ({} args) {:4} '", name, arg_count, constant);
279
280 if let Some(value) = chunk.constants.get(constant as usize) {
281 match value.as_string() {
282 Some(handle) => {
283 let string_value = allocator.strings.get(handle);
284 print!("{}", string_value);
285 }
286 _ => {
287 print!("<not a string constant>");
288 }
289 }
290 } else {
291 print!("<invalid constant index>");
292 }
293
294 println!("'");
295 offset + 3
296}
297
298fn string_constant_16_instruction(
299 name: &str,
300 chunk: &Chunk,
301 allocator: &HeapAllocator,
302 offset: usize,
303) -> usize {
304 let high_byte = chunk.code[offset + 1] as usize;
305 let low_byte = chunk.code[offset + 2] as usize;
306 let constant = (high_byte << 8) | low_byte;
307 print!("{:<16} {:4} '", name, constant);
308
309 if let Some(value) = chunk.constants.get(constant) {
310 match value.as_string() {
311 Some(handle) => {
312 let string_value = allocator.strings.get(handle);
313 print!("{}", string_value);
314 }
315 _ => {
316 print!("<not a string constant>");
317 }
318 }
319 } else {
320 print!("<invalid constant index>");
321 }
322
323 println!("'");
324 offset + 3
325}
326
327fn invoke_16_instruction(
328 name: &str,
329 chunk: &Chunk,
330 allocator: &HeapAllocator,
331 offset: usize,
332) -> usize {
333 let high_byte = chunk.code[offset + 1] as usize;
334 let low_byte = chunk.code[offset + 2] as usize;
335 let constant = (high_byte << 8) | low_byte;
336 let arg_count = chunk.code[offset + 3];
337 print!("{:<16} ({} args) {:4} '", name, arg_count, constant);
338
339 if let Some(value) = chunk.constants.get(constant) {
340 match value.as_string() {
341 Some(handle) => {
342 let string_value = allocator.strings.get(handle);
343 print!("{}", string_value);
344 }
345 _ => {
346 print!("<not a string constant>");
347 }
348 }
349 } else {
350 print!("<invalid constant index>");
351 }
352
353 println!("'");
354 offset + 4
355}
356
357#[allow(dead_code)]
358pub fn disassemble_program(allocator: &HeapAllocator) {
359 println!("=== PROGRAM DISASSEMBLY ===");
360 println!();
361
362 let mut function_count = 0;
363
364 for (index, function) in allocator.iter_functions() {
365 function_count += 1;
366
367 let function_name = allocator.strings.get(function.name);
368
369 println!(
370 "Function #{} (Object #{}) - {}:",
371 function_count, index, function_name
372 );
373 println!(" Arity: {}", function.arity);
374 disassemble_chunk(&function.chunk, allocator, function_name);
375 println!();
376 }
377
378 if function_count == 0 {
379 println!("No functions found in the program.");
380 } else {
381 println!(
382 "=== END PROGRAM DISASSEMBLY ({} functions) ===",
383 function_count
384 );
385 }
386}
387
388#[macro_export]
389macro_rules! debug_log {
390 ($is_debug:expr, $($arg:tt)*) => {{
391 #[cfg(debug_assertions)]
392 {
393 if $is_debug {
394 println!($($arg)*);
395 }
396 }
397 }};
398}