use crate::compiler::value::values::Value;
use crate::runtime::bytecode::OpCode::*;
use crate::runtime::bytecode::{instruction_length, OpCode};
use crate::runtime::compiled_script::Bytecode;
use std::collections::hash_map::Entry::Vacant;
use std::collections::HashMap;
use std::io::Result;
use std::io::Write;
use tracing::trace;
#[derive(Default)]
struct Disassembly {
offset: usize,
labels: HashMap<usize, String>,
buffer: Vec<u8>,
}
fn simple_instruction(instruction: &str, context: &mut Disassembly) -> Result<()> {
writeln!(context.buffer, "{:>15}", instruction)?;
context.offset += 1;
Ok(())
}
fn loop_instruction(
instruction: &str,
context: &mut Disassembly,
bytecode: &Bytecode,
) -> Result<()> {
let argument = bytecode.get(context.offset + 1) as usize;
let label = context
.labels
.get(&(context.offset - argument + 2))
.unwrap();
writeln!(
context.buffer,
"{:>15}{:>8}{:>20}",
instruction, argument, label
)?;
context.offset += 2;
Ok(())
}
fn jump_instruction(
instruction: &str,
context: &mut Disassembly,
bytecode: &Bytecode,
) -> Result<()> {
let argument = bytecode.get(context.offset + 1) as usize;
let label = context
.labels
.get(&(context.offset + argument + 2))
.unwrap();
writeln!(
context.buffer,
"{:>15}{:>8}{:>20}",
instruction, argument, label
)?;
context.offset += 2;
Ok(())
}
fn byte_instruction(
instruction: &str,
context: &mut Disassembly,
bytecode: &Bytecode,
) -> Result<()> {
let argument = bytecode.get(context.offset + 1);
writeln!(context.buffer, "{:>15}{:>8}", instruction, argument)?;
context.offset += 2;
Ok(())
}
fn single_byte_constant_instruction(
instruction: &str,
context: &mut Disassembly,
bytecode: &Bytecode,
constants: &[Value],
) -> Result<()> {
let argument = bytecode.get(context.offset + 1) as usize;
let constant = &constants[argument];
writeln!(
context.buffer,
"{:>15}{:>8}{:>20}",
instruction, argument, constant
)?;
context.offset += 2;
Ok(())
}
fn dump_instruction(
instruction: u8,
bytecode: &Bytecode,
context: &mut Disassembly,
constants: &[Value],
) -> Result<()> {
match OpCode::from_byte(instruction) {
OpNone => simple_instruction("OpNone", context),
OpPop => simple_instruction("OpPop", context),
OpConst => single_byte_constant_instruction("OpConst", context, bytecode, constants),
OpTrue => simple_instruction("OpTrue", context),
OpFalse => simple_instruction("OpFalse", context),
OpMktuple => byte_instruction("OpMkTuple", context, bytecode),
OpAdd => simple_instruction("OpAdd", context),
OpSubtract => simple_instruction("OpSubtract", context),
OpMultiply => simple_instruction("OpMultiply", context),
OpDivide => simple_instruction("OpDivide", context),
OpNegate => simple_instruction("OpNegate", context),
OpAnd => simple_instruction("OpAnd", context),
OpOr => simple_instruction("OpOr", context),
OpNot => simple_instruction("OpNot", context),
OpEquals => simple_instruction("OpEquals", context),
OpGt => simple_instruction("OpGt", context),
OpLt => simple_instruction("OpLt", context),
OpReturn => simple_instruction("OpReturn", context),
OpSetGlobal => byte_instruction("OpSetGlobal", context, bytecode),
OpGetGlobal => byte_instruction("OpGetGlobal", context, bytecode),
OpSetLocal => byte_instruction("OpSetLocal", context, bytecode),
OpGetLocal => byte_instruction("OpGetLocal", context, bytecode),
OpSetFunt => single_byte_constant_instruction("OpSetFunt", context, bytecode, constants),
OpCall => byte_instruction("OpCall", context, bytecode),
OpJump => jump_instruction("OpJump", context, bytecode),
OpJumpIfFalse => jump_instruction("OpJumpIfFalse", context, bytecode),
OpLoop => loop_instruction("OpLoop", context, bytecode),
OpPull => byte_instruction("OpPull", context, bytecode),
OpProduce => byte_instruction("OpProduce", context, bytecode),
OpOpenCursor => byte_instruction("OpOpenCursor", context, bytecode),
OpCloseCursor => byte_instruction("OpCloseCursor", context, bytecode),
OpTupleGet => byte_instruction("OpTupleGet", context, bytecode),
OpNewInstance => byte_instruction("OpNewInstance", context, bytecode),
OpSetField => byte_instruction("OpSetField", context, bytecode),
OpGetField => byte_instruction("OpGetField", context, bytecode),
}
}
fn disassemble_instruction(
bytecode: &Bytecode,
context: &mut Disassembly,
constants: &[Value],
) -> Result<()> {
write!(context.buffer, "{:04}", context.offset)?;
if context.offset > 0 && bytecode.lines[context.offset] == bytecode.lines[context.offset - 1] {
write!(context.buffer, "{:>8} ", "|")?;
} else {
write!(context.buffer, "{:>8} ", bytecode.lines[context.offset])?;
}
dump_instruction(bytecode.get(context.offset), bytecode, context, constants)
}
fn disassemble_instruction_no_lines(
bytecode: &Bytecode,
context: &mut Disassembly,
constants: &[Value],
) -> Result<()> {
write!(context.buffer, " {:04} ", context.offset)?;
dump_instruction(bytecode.get(context.offset), bytecode, context, constants)
}
pub fn disassemble(name: &str, bytecode: &Bytecode, constants: &[Value]) -> Result<()> {
let mut context = Disassembly::default();
writeln!(context.buffer, "== {} ==", name)?;
writeln!(
context.buffer,
"{:<6}, {:<4}, {:>12}, {:>8}, {:>8}",
"offset", "line", "opcode", "operand1", "constant1"
)?;
while context.offset < bytecode.len() {
if let Some(label) = context.labels.get(&context.offset) {
writeln!(context.buffer, "{}:", label)?;
}
disassemble_instruction(bytecode, &mut context, constants)?;
}
trace!("{}", String::from_utf8(context.buffer).unwrap());
Ok(())
}
pub fn disassemble_no_lines(name: &str, bytecode: &Bytecode, constants: &[Value]) -> Result<()> {
let mut context = Disassembly::default();
labels(&bytecode.bytes, &mut context);
writeln!(context.buffer, "== {} ==", name)?;
writeln!(
context.buffer,
"{:>6}, {:>16}, {:>10}, {:>8}",
"offset", "opcode", "operand", "constant"
)?;
while context.offset < bytecode.len() {
if let Some(label) = context.labels.get(&context.offset) {
writeln!(context.buffer, "{}:", label)?;
}
disassemble_instruction_no_lines(bytecode, &mut context, constants)?;
}
trace!("{}", String::from_utf8(context.buffer).unwrap());
Ok(())
}
fn labels(bytecode: &[u8], context: &mut Disassembly) {
let mut offset = 0;
let mut loop_count = 0;
let mut cond_jump_count = 0;
let mut jump_count = 0;
while offset < bytecode.len() {
let current = OpCode::from_byte(bytecode[offset]);
match current {
OpLoop => {
let target = offset - bytecode[offset + 1] as usize + 2_usize; if let Vacant(e) = context.labels.entry(target) {
e.insert(format!("loop_{}", loop_count));
loop_count += 1;
}
}
OpJumpIfFalse => {
let target = offset + bytecode[offset + 1] as usize + 2; if let Vacant(e) = context.labels.entry(target) {
e.insert(format!("cond_jump_{}", cond_jump_count));
cond_jump_count += 1;
}
}
OpJump => {
let target = offset + bytecode[offset + 1] as usize + 2; if let Vacant(e) = context.labels.entry(target) {
e.insert(format!("jump_{}", jump_count));
jump_count += 1;
}
}
_ => {}
}
offset += instruction_length(current);
}
}