Skip to main content

qanglang_core/
debug.rs

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; // Skip the opcode itself
73            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; // Skip the opcode itself
102            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}