use crate::{
HeapAllocator,
backend::chunk::{Chunk, OpCode},
};
#[allow(dead_code)]
pub fn disassemble_chunk(chunk: &Chunk, allocator: &HeapAllocator, name: &str) {
println!("== {} ==", name);
let mut offset = 0;
while offset < chunk.code.len() {
offset = disassemble_instruction(chunk, allocator, offset);
}
}
pub fn disassemble_instruction(chunk: &Chunk, allocator: &HeapAllocator, offset: usize) -> usize {
print!("{:04} ", offset);
if offset > 0 && chunk.locs[offset].line == chunk.locs[offset - 1].line {
print!(" | ");
} else {
print!("{:04} ", chunk.locs[offset].line);
}
let instruction = chunk.code[offset];
let opcode = OpCode::from(instruction);
match opcode {
OpCode::Constant => constant_instruction("OP_CONSTANT", chunk, allocator, offset),
OpCode::Constant16 => constant16_instruction("OP_CONSTANT16", chunk, allocator, offset),
OpCode::Return => simple_instruction("OP_RETURN", offset),
OpCode::Negate => simple_instruction("OP_NEGATE", offset),
OpCode::Add => simple_instruction("OP_ADD", offset),
OpCode::Subtract => simple_instruction("OP_SUBTRACT", offset),
OpCode::Multiply => simple_instruction("OP_MULTIPLY", offset),
OpCode::Divide => simple_instruction("OP_DIVIDE", offset),
OpCode::Nil => simple_instruction("OP_NIL", offset),
OpCode::True => simple_instruction("OP_TRUE", offset),
OpCode::False => simple_instruction("OP_FALSE", offset),
OpCode::Not => simple_instruction("OP_NOT", offset),
OpCode::Equal => simple_instruction("OP_EQUAL", offset),
OpCode::Greater => simple_instruction("OP_GREATER", offset),
OpCode::Less => simple_instruction("OP_LESS", offset),
OpCode::GreaterEqual => simple_instruction("OP_GREATER_EQUAL", offset),
OpCode::LessEqual => simple_instruction("OP_LESS_EQUAL", offset),
OpCode::Modulo => simple_instruction("OP_MODULO", offset),
OpCode::Pop => simple_instruction("OP_POP", offset),
OpCode::DefineGlobal => {
string_constant_instruction("OP_DEFINE_GLOBAL", chunk, allocator, offset)
}
OpCode::GetGlobal => string_constant_instruction("OP_GET_GLOBAL", chunk, allocator, offset),
OpCode::SetGlobal => string_constant_instruction("OP_SET_GLOBAL", chunk, allocator, offset),
OpCode::DefineGlobal16 => {
string_constant_16_instruction("OP_DEFINE_GLOBAL16", chunk, allocator, offset)
}
OpCode::GetGlobal16 => {
string_constant_16_instruction("OP_GET_GLOBAL16", chunk, allocator, offset)
}
OpCode::SetGlobal16 => {
string_constant_16_instruction("OP_SET_GLOBAL16", chunk, allocator, offset)
}
OpCode::GetLocal => byte_instruction("OP_GET_LOCAL", chunk, offset),
OpCode::SetLocal => byte_instruction("OP_SET_LOCAL", chunk, offset),
OpCode::JumpIfFalse => jump_instruction("OP_JUMP_IF_FALSE", 1, chunk, offset),
OpCode::Jump => jump_instruction("OP_JUMP", 1, chunk, offset),
OpCode::Loop => jump_instruction("OP_LOOP", 1, chunk, offset),
OpCode::Call => byte_instruction("OP_CALL", chunk, offset),
OpCode::CloseUpvalue => simple_instruction("OP_CLOSE_UPVALUE", offset),
OpCode::Closure => {
let mut offset = offset;
offset += 1; let constant = chunk.code[offset];
offset += 1;
print!("{:<16} {:4} '", "OP_CLOSURE", constant);
let value = chunk.constants[constant as usize];
println!("{}'", value.to_display_string(allocator));
let function_obj = value.as_function().map(|f| allocator.get_function(f));
if let Some(function) = function_obj {
for _j in 0..function.upvalue_count {
let is_local = chunk.code[offset];
offset += 1;
let index = chunk.code[offset];
offset += 1;
println!(
"{:04} | {} {}",
offset - 2,
if is_local != 0 { "local" } else { "upvalue" },
index
);
}
}
offset
}
OpCode::Closure16 => {
let mut offset = offset;
offset += 1; let high_byte = chunk.code[offset] as usize;
offset += 1;
let low_byte = chunk.code[offset] as usize;
offset += 1;
let constant = (high_byte << 8) | low_byte;
print!("{:<16} {:4} '", "OP_CLOSURE16", constant);
let value = chunk.constants[constant];
println!("{}'", value.to_display_string(allocator));
let function_obj = value.as_function().map(|f| allocator.get_function(f));
if let Some(function) = function_obj {
for _j in 0..function.upvalue_count {
let is_local = chunk.code[offset];
offset += 1;
let index = chunk.code[offset];
offset += 1;
println!(
"{:04} | {} {}",
offset - 2,
if is_local != 0 { "local" } else { "upvalue" },
index
);
}
}
offset
}
OpCode::GetUpvalue => byte_instruction("OP_GET_UPVALUE", chunk, offset),
OpCode::SetUpvalue => byte_instruction("OP_GET_UPVALUE", chunk, offset),
OpCode::Class => constant_instruction("OP_CLASS", chunk, allocator, offset),
OpCode::Class16 => constant16_instruction("OP_CLASS16", chunk, allocator, offset),
OpCode::GetProperty => {
string_constant_instruction("OP_GET_PROPERTY", chunk, allocator, offset)
}
OpCode::SetProperty => {
string_constant_instruction("OP_SET_PROPERTY", chunk, allocator, offset)
}
OpCode::Method => string_constant_instruction("OP_METHOD", chunk, allocator, offset),
OpCode::GetProperty16 => {
string_constant_16_instruction("OP_GET_PROPERTY16", chunk, allocator, offset)
}
OpCode::SetProperty16 => {
string_constant_16_instruction("OP_SET_PROPERTY16", chunk, allocator, offset)
}
OpCode::Method16 => string_constant_16_instruction("OP_METHOD16", chunk, allocator, offset),
OpCode::Invoke => invoke_instruction("OP_INVOKE", chunk, allocator, offset),
OpCode::Invoke16 => invoke_16_instruction("OP_INVOKE16", chunk, allocator, offset),
OpCode::Inherit => simple_instruction("OP_INHERIT", offset),
OpCode::GetSuper => string_constant_instruction("OP_GET_SUPER", chunk, allocator, offset),
OpCode::GetSuper16 => {
string_constant_16_instruction("OP_GET_SUPER16", chunk, allocator, offset)
}
OpCode::SuperInvoke => invoke_instruction("OP_SUPER_INVOKE", chunk, allocator, offset),
OpCode::SuperInvoke16 => {
invoke_16_instruction("OP_SUPER_INVOKE16", chunk, allocator, offset)
}
OpCode::InitField => string_constant_instruction("OP_INIT_FIELD", chunk, allocator, offset),
OpCode::InitField16 => {
string_constant_16_instruction("OP_INIT_FIELD16", chunk, allocator, offset)
}
OpCode::ArrayLiteral => byte_instruction("OP_ARRAY_LITERAL", chunk, offset),
OpCode::GetArrayIndex => simple_instruction("OP_GET_ARRAY_INDEX", offset),
OpCode::SetArrayIndex => simple_instruction("OP_SET_ARRAY_INDEX", offset),
OpCode::ObjectLiteral => byte_instruction("OP_OBJECT_LITERAL", chunk, offset),
OpCode::Is => simple_instruction("OP_IS", offset),
OpCode::GetOptionalProperty => {
string_constant_instruction("OP_GET_OPTIONAL_PROPERTY", chunk, allocator, offset)
}
OpCode::GetOptionalProperty16 => {
string_constant_16_instruction("OP_GET_OPTIONAL_PROPERTY16", chunk, allocator, offset)
}
OpCode::JumpIfNil => jump_instruction("OP_JUMP_IF_NIL", 1, chunk, offset),
OpCode::TailCall => byte_instruction("OP_TAIL_CALL", chunk, offset),
OpCode::Module => string_constant_instruction("OP_MODULE", chunk, allocator, offset),
OpCode::Module16 => string_constant_16_instruction("OP_MODULE", chunk, allocator, offset),
OpCode::ModuleReturn => simple_instruction("OP_MODULE_RETURN", offset),
}
}
fn constant_instruction(
name: &str,
chunk: &Chunk,
allocator: &HeapAllocator,
offset: usize,
) -> usize {
let constant = chunk.code[offset + 1] as usize;
print!("{:<16} {:4} '", name, constant);
chunk
.constants
.get(constant)
.copied()
.unwrap_or_default()
.print(allocator);
println!("'");
offset + 2
}
fn string_constant_instruction(
name: &str,
chunk: &Chunk,
allocator: &HeapAllocator,
offset: usize,
) -> usize {
let constant = chunk.code[offset + 1] as usize;
print!("{:<16} {:4} '", name, constant);
if let Some(value) = chunk.constants.get(constant) {
match value.as_string() {
Some(handle) => {
let string_value = allocator.strings.get(handle);
print!("{}", string_value);
}
_ => {
print!("<not a string constant>");
}
}
} else {
print!("<invalid constant index>");
}
println!("'");
offset + 2
}
fn simple_instruction(name: &str, offset: usize) -> usize {
println!("{}", name);
offset + 1
}
fn byte_instruction(name: &str, chunk: &Chunk, offset: usize) -> usize {
let slot = chunk.code[offset + 1];
println!("{:<16} {:4}", name, slot);
offset + 2
}
fn jump_instruction(name: &str, sign: i32, chunk: &Chunk, offset: usize) -> usize {
let jump = ((chunk.code[offset + 1] as u16) << 8) | (chunk.code[offset + 2] as u16);
let target = offset as i32 + 3 + sign * (jump as i32);
println!("{:<16} {:4} -> {}", name, offset, target);
offset + 3
}
fn constant16_instruction(
name: &str,
chunk: &Chunk,
allocator: &HeapAllocator,
offset: usize,
) -> usize {
let high_byte = chunk.code[offset + 1] as usize;
let low_byte = chunk.code[offset + 2] as usize;
let constant = (high_byte << 8) | low_byte;
print!("{:<16} {:4} '", name, constant);
chunk
.constants
.get(constant)
.copied()
.unwrap_or_default()
.print(allocator);
println!("'");
offset + 3
}
fn invoke_instruction(
name: &str,
chunk: &Chunk,
allocator: &HeapAllocator,
offset: usize,
) -> usize {
let constant = chunk.code[offset + 1];
let arg_count = chunk.code[offset + 2];
print!("{:<16} ({} args) {:4} '", name, arg_count, constant);
if let Some(value) = chunk.constants.get(constant as usize) {
match value.as_string() {
Some(handle) => {
let string_value = allocator.strings.get(handle);
print!("{}", string_value);
}
_ => {
print!("<not a string constant>");
}
}
} else {
print!("<invalid constant index>");
}
println!("'");
offset + 3
}
fn string_constant_16_instruction(
name: &str,
chunk: &Chunk,
allocator: &HeapAllocator,
offset: usize,
) -> usize {
let high_byte = chunk.code[offset + 1] as usize;
let low_byte = chunk.code[offset + 2] as usize;
let constant = (high_byte << 8) | low_byte;
print!("{:<16} {:4} '", name, constant);
if let Some(value) = chunk.constants.get(constant) {
match value.as_string() {
Some(handle) => {
let string_value = allocator.strings.get(handle);
print!("{}", string_value);
}
_ => {
print!("<not a string constant>");
}
}
} else {
print!("<invalid constant index>");
}
println!("'");
offset + 3
}
fn invoke_16_instruction(
name: &str,
chunk: &Chunk,
allocator: &HeapAllocator,
offset: usize,
) -> usize {
let high_byte = chunk.code[offset + 1] as usize;
let low_byte = chunk.code[offset + 2] as usize;
let constant = (high_byte << 8) | low_byte;
let arg_count = chunk.code[offset + 3];
print!("{:<16} ({} args) {:4} '", name, arg_count, constant);
if let Some(value) = chunk.constants.get(constant) {
match value.as_string() {
Some(handle) => {
let string_value = allocator.strings.get(handle);
print!("{}", string_value);
}
_ => {
print!("<not a string constant>");
}
}
} else {
print!("<invalid constant index>");
}
println!("'");
offset + 4
}
#[allow(dead_code)]
pub fn disassemble_program(allocator: &HeapAllocator) {
println!("=== PROGRAM DISASSEMBLY ===");
println!();
let mut function_count = 0;
for (index, function) in allocator.iter_functions() {
function_count += 1;
let function_name = allocator.strings.get(function.name);
println!(
"Function #{} (Object #{}) - {}:",
function_count, index, function_name
);
println!(" Arity: {}", function.arity);
disassemble_chunk(&function.chunk, allocator, function_name);
println!();
}
if function_count == 0 {
println!("No functions found in the program.");
} else {
println!(
"=== END PROGRAM DISASSEMBLY ({} functions) ===",
function_count
);
}
}
#[macro_export]
macro_rules! debug_log {
($is_debug:expr, $($arg:tt)*) => {{
#[cfg(debug_assertions)]
{
if $is_debug {
println!($($arg)*);
}
}
}};
}