Prettify the hell out of chunk disassembly

This commit is contained in:
Jeff 2024-09-12 13:03:24 -04:00
parent 78c9ed97e2
commit 8b33df3d4a
5 changed files with 240 additions and 253 deletions

View File

@ -104,7 +104,7 @@ impl Chunk {
pub fn get_local(&self, index: usize, position: Span) -> Result<&Local, ChunkError> { pub fn get_local(&self, index: usize, position: Span) -> Result<&Local, ChunkError> {
self.locals self.locals
.get(index as usize) .get(index)
.ok_or(ChunkError::LocalIndexOutOfBounds { index, position }) .ok_or(ChunkError::LocalIndexOutOfBounds { index, position })
} }
@ -193,78 +193,8 @@ impl Chunk {
self.locals.pop() self.locals.pop()
} }
pub fn disassemble(&self, name: &str) -> String { pub fn disassemble<'a>(&self, name: &'a str) -> DisassembledChunk<'a> {
let mut output = String::new(); DisassembledChunk::new(name, self)
let name_length = name.len();
let buffer_length = 51_usize.saturating_sub(name_length);
let name_buffer = " ".repeat(buffer_length / 2);
let underline = "-".repeat(name_length);
output.push_str(&format!("{name_buffer}{name}{name_buffer}\n"));
output.push_str(&format!("{name_buffer}{underline}{name_buffer}\n",));
output.push_str(" Code \n");
output.push_str("------ ---------------- -------------------- --------\n");
output.push_str("OFFSET INSTRUCTION INFO POSITION\n");
output.push_str("------ ---------------- -------------------- --------\n");
for (offset, (instruction, position)) in self.instructions.iter().enumerate() {
let display = format!(
"{offset:^6} {:37} {position}\n",
instruction.disassemble(self)
);
output.push_str(&display);
}
output.push_str("\n Constants\n");
output.push_str("----- ---- -----\n");
output.push_str("INDEX KIND VALUE\n");
output.push_str("----- ---- -----\n");
for (index, value_option) in self.constants.iter().enumerate() {
let value_kind_display = match value_option {
Some(Value::Raw(_)) => "RAW ",
Some(Value::Reference(_)) => "REF ",
Some(Value::Mutable(_)) => "MUT ",
None => "EMPTY",
};
let value_display = value_option
.as_ref()
.map(|value| value.to_string())
.unwrap_or_else(|| "EMPTY".to_string());
let display = format!("{index:3} {value_kind_display} {value_display}\n",);
output.push_str(&display);
}
output.push_str("\n Locals\n");
output.push_str("----- ---------- ----- -----\n");
output.push_str("INDEX NAME DEPTH VALUE\n");
output.push_str("----- ---------- ----- -----\n");
for (
index,
Local {
identifier,
depth,
value,
},
) in self.locals.iter().enumerate()
{
let value_display = value
.as_ref()
.map(|value| value.to_string())
.unwrap_or_else(|| "EMPTY".to_string());
let display = format!(
"{index:3} {:10} {depth:<5} {value_display}\n",
identifier.as_str()
);
output.push_str(&display);
}
output
} }
} }
@ -313,6 +243,165 @@ impl Local {
} }
} }
pub struct DisassembledChunk<'a> {
name: &'a str,
body: String,
}
impl DisassembledChunk<'_> {
pub fn new<'a>(name: &'a str, chunk: &Chunk) -> DisassembledChunk<'a> {
let mut disassembled = String::new();
let mut longest_line = 0;
disassembled.push('\n');
disassembled.push_str(&DisassembledChunk::instructions_header());
disassembled.push('\n');
for (offset, (instruction, position)) in chunk.instructions.iter().enumerate() {
let position = position.to_string();
let operation = instruction.operation.to_string();
let info_option = instruction.disassembly_info(Some(chunk));
let instruction_display = if let Some(info) = info_option {
format!("{offset:<6} {operation:16} {info:17} {position:8}\n")
} else {
format!("{offset:<6} {operation:16} {:17} {position:8}\n", " ")
};
disassembled.push_str(&instruction_display);
let line_length = instruction_display.len();
if line_length > longest_line {
longest_line = line_length;
}
}
let mut push_centered = |section: &str| {
let mut centered = String::new();
for line in section.lines() {
centered.push_str(&format!("{line:^longest_line$}\n"));
}
disassembled.push_str(&centered);
};
push_centered("\n");
push_centered(&mut DisassembledChunk::constant_header());
for (index, value_option) in chunk.constants.iter().enumerate() {
let value_kind_display = match value_option {
Some(Value::Raw(_)) => "RAW ",
Some(Value::Reference(_)) => "REF ",
Some(Value::Mutable(_)) => "MUT ",
None => "EMPTY",
};
let value_display = value_option
.as_ref()
.map(|value| value.to_string())
.unwrap_or_else(|| "EMPTY".to_string());
let constant_display = format!("{index:<5} {value_kind_display:<4} {value_display:<5}");
push_centered(&constant_display);
}
push_centered("\n");
push_centered(&mut DisassembledChunk::local_header());
for (
index,
Local {
identifier,
depth,
value,
},
) in chunk.locals.iter().enumerate()
{
let value_kind_display = match value {
Some(Value::Raw(_)) => "RAW ",
Some(Value::Reference(_)) => "REF ",
Some(Value::Mutable(_)) => "MUT ",
None => "EMPTY",
};
let value_display = value
.as_ref()
.map(|value| value.to_string())
.unwrap_or_else(|| "EMPTY".to_string());
let identifier_display = identifier.as_str();
let local_display =
format!("{index:<5} {identifier_display:<10} {depth:<5} {value_kind_display:<4} {value_display:<5}");
push_centered(&local_display);
}
DisassembledChunk {
name,
body: disassembled,
}
}
pub fn to_string_with_width(&self, width: usize) -> String {
let mut display = String::new();
for line in self.to_string().lines() {
display.push_str(&format!("{line:^width$}\n"));
}
display
}
fn name_header(&self) -> String {
format!("{:^50}\n{:^50}", self.name, "==============")
}
fn instructions_header() -> String {
format!(
"{:^50}\n{:^50}\n{:<6} {:<16} {:<17} {}\n{} {} {} {}",
"Instructions",
"------------",
"OFFSET",
"INSTRUCTION",
"INFO",
"POSITION",
"------",
"----------------",
"-----------------",
"--------"
)
}
fn constant_header() -> String {
format!(
"{:^16}\n{:^16}\n{:<5} {:<4} {}\n{} {} {}",
"Constants", "---------", "INDEX", "KIND", "VALUE", "-----", "----", "-----"
)
}
fn local_header() -> String {
format!(
"{:^50}\n{:^50}\n{:<5} {:<10} {:<5} {:<5} {:<5}\n{} {} {} {} {}",
"Locals",
"------",
"INDEX",
"IDENTIFIER",
"DEPTH",
"KIND",
"VALUE",
"-----",
"----------",
"-----",
"-----",
"-----"
)
}
}
impl Display for DisassembledChunk<'_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}\n{}", self.name_header(), self.body)
}
}
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum ChunkError { pub enum ChunkError {
CodeIndexOfBounds { CodeIndexOfBounds {

View File

@ -54,17 +54,17 @@ impl Instruction {
} }
} }
pub fn declare_variable(to_register: u8, variable_index: u16) -> Instruction { pub fn declare_local(to_register: u8, variable_index: u16) -> Instruction {
Instruction { Instruction {
operation: Operation::DeclareVariable, operation: Operation::DeclareLocal,
destination: to_register, destination: to_register,
arguments: variable_index.to_le_bytes(), arguments: variable_index.to_le_bytes(),
} }
} }
pub fn get_variable(to_register: u8, variable_index: u16) -> Instruction { pub fn get_local(to_register: u8, variable_index: u16) -> Instruction {
Instruction { Instruction {
operation: Operation::GetVariable, operation: Operation::GetLocal,
destination: to_register, destination: to_register,
arguments: variable_index.to_le_bytes(), arguments: variable_index.to_le_bytes(),
} }
@ -72,7 +72,7 @@ impl Instruction {
pub fn set_local(from_register: u8, variable_index: u16) -> Instruction { pub fn set_local(from_register: u8, variable_index: u16) -> Instruction {
Instruction { Instruction {
operation: Operation::SetVariable, operation: Operation::SetLocal,
destination: from_register, destination: from_register,
arguments: variable_index.to_le_bytes(), arguments: variable_index.to_le_bytes(),
} }
@ -127,197 +127,93 @@ impl Instruction {
} }
pub fn disassemble(&self, chunk: &Chunk) -> String { pub fn disassemble(&self, chunk: &Chunk) -> String {
match self.operation { let mut disassembled = format!("{:16} ", self.operation.to_string());
if let Some(info) = self.disassembly_info(Some(chunk)) {
disassembled.push_str(&info);
}
disassembled
}
pub fn disassembly_info(&self, chunk: Option<&Chunk>) -> Option<String> {
let info = match self.operation {
Operation::Move => { Operation::Move => {
format!( format!("R({}) R({})", self.destination, self.arguments[0])
"{:16} R({}) R({})",
self.operation.to_string(),
self.destination,
self.arguments[0]
)
} }
Operation::Close => format!("{:16} R({})", self.operation, self.destination), Operation::Close => format!("R({})", self.destination),
Operation::LoadConstant => { Operation::LoadConstant => {
let constant_index = u16::from_le_bytes(self.arguments) as usize; let constant_index = u16::from_le_bytes(self.arguments) as usize;
let constant_display = match chunk.get_constant(constant_index, Span(0, 0)) {
Ok(value) => value.to_string(),
Err(error) => format!("{:?}", error),
};
format!( if let Some(chunk) = chunk {
"{:16} R({}) = C({}) {}", match chunk.get_constant(constant_index, Span(0, 0)) {
self.operation.to_string(), Ok(value) => {
self.destination, format!("R({}) = C({}) {}", self.destination, constant_index, value)
constant_index, }
constant_display Err(error) => format!(
) "R({}) = C({}) {:?}",
self.destination, constant_index, error
),
}
} else {
format!("R({}) = C({})", self.destination, constant_index)
}
} }
Operation::DeclareVariable => { Operation::DeclareLocal => {
let local_index = u16::from_le_bytes([self.arguments[0], self.arguments[1]]); let local_index = u16::from_le_bytes([self.arguments[0], self.arguments[1]]);
format!( format!("L({}) = R({})", local_index, self.destination)
"{:16} L({}) = R({})",
self.operation.to_string(),
local_index,
self.destination
)
} }
Operation::GetVariable => { Operation::GetLocal => {
let local_index = u16::from_le_bytes([self.arguments[0], self.arguments[1]]);
format!("R({}) = L({})", self.destination, local_index)
}
Operation::SetLocal => {
let identifier_index = u16::from_le_bytes([self.arguments[0], self.arguments[1]]); let identifier_index = u16::from_le_bytes([self.arguments[0], self.arguments[1]]);
format!( format!("L({}) = R({})", identifier_index, self.destination)
"{:16} R{} = R[I({})]",
self.operation.to_string(),
self.destination,
identifier_index
)
}
Operation::SetVariable => {
let identifier_index = u16::from_le_bytes([self.arguments[0], self.arguments[1]]);
format!(
"{:16} R[C({})] = R({})",
self.operation.to_string(),
identifier_index,
self.destination
)
} }
Operation::Add => { Operation::Add => {
format!( format!(
"{:16} R({}) = RC({}) + RC({})", "R({}) = RC({}) + RC({})",
self.operation.to_string(), self.destination, self.arguments[0], self.arguments[1]
self.destination,
self.arguments[0],
self.arguments[1]
) )
} }
Operation::Subtract => { Operation::Subtract => {
format!( format!(
"{:16} R({}) = RC({}) - RC({})", "R({}) = RC({}) - RC({})",
self.operation.to_string(), self.destination, self.arguments[0], self.arguments[1]
self.destination,
self.arguments[0],
self.arguments[1]
) )
} }
Operation::Multiply => { Operation::Multiply => {
format!( format!(
"{:16} R({}) = RC({}) * RC({})", "R({}) = RC({}) * RC({})",
self.operation.to_string(), self.destination, self.arguments[0], self.arguments[1]
self.destination,
self.arguments[0],
self.arguments[1]
) )
} }
Operation::Divide => { Operation::Divide => {
format!( format!(
"{:16} R({}) = RC({}) / RC({})", "R({}) = RC({}) / RC({})",
self.operation.to_string(), self.destination, self.arguments[0], self.arguments[1]
self.destination,
self.arguments[0],
self.arguments[1]
) )
} }
Operation::Negate => { Operation::Negate => {
format!( format!("R({}) = -RC({})", self.destination, self.arguments[0])
"{:16} R({}) = -RC({})",
self.operation.to_string(),
self.destination,
self.arguments[0]
)
} }
Operation::Return => { Operation::Return => return None,
format!("{:16}", self.operation.to_string()) };
}
} Some(info)
} }
} }
impl Display for Instruction { impl Display for Instruction {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self.operation { if let Some(info) = self.disassembly_info(None) {
Operation::Move => { write!(f, "{} {}", self.operation, info)
write!( } else {
f, write!(f, "{}", self.operation)
"{} R({}) R({})",
self.operation, self.destination, self.arguments[0]
)
}
Operation::Close => write!(f, "{} R({})", self.operation, self.destination),
Operation::LoadConstant => {
let constant_index = u16::from_le_bytes(self.arguments);
write!(
f,
"{} R({}) = C({})",
self.operation, self.destination, constant_index
)
}
Operation::DeclareVariable => {
let identifier_index = u16::from_le_bytes([self.arguments[0], self.arguments[1]]);
write!(
f,
"{} L({}) = R({})",
self.operation, identifier_index, self.destination
)
}
Operation::GetVariable => {
let identifier_index = u16::from_le_bytes([self.arguments[0], self.arguments[1]]);
write!(
f,
"{} R{} = R[I({})]",
self.operation, self.destination, identifier_index
)
}
Operation::SetVariable => {
let identifier_index = u16::from_le_bytes([self.arguments[0], self.arguments[1]]);
write!(
f,
"{} R[C({})] = R({})",
self.operation, identifier_index, self.destination
)
}
Operation::Add => {
write!(
f,
"{} R({}) = RC({}) + RC({})",
self.operation, self.destination, self.arguments[0], self.arguments[1]
)
}
Operation::Subtract => {
write!(
f,
"{} R({}) = RC({}) - RC({})",
self.operation, self.destination, self.arguments[0], self.arguments[1]
)
}
Operation::Multiply => {
write!(
f,
"{} R({}) = RC({}) * RC({})",
self.operation, self.destination, self.arguments[0], self.arguments[1]
)
}
Operation::Divide => {
write!(
f,
"{} R({}) = RC({}) / RC({})",
self.operation, self.destination, self.arguments[0], self.arguments[1]
)
}
Operation::Negate => {
write!(
f,
"{} R({}) = -RC({})",
self.operation, self.destination, self.arguments[0]
)
}
Operation::Return => {
write!(f, "{}", self.operation)
}
} }
} }
} }
@ -332,9 +228,9 @@ pub enum Operation {
LoadConstant = 2, LoadConstant = 2,
// Variables // Variables
DeclareVariable = 3, DeclareLocal = 3,
GetVariable = 4, GetLocal = 4,
SetVariable = 5, SetLocal = 5,
// Binary operations // Binary operations
Add = 6, Add = 6,
@ -355,9 +251,9 @@ impl From<u8> for Operation {
0 => Operation::Move, 0 => Operation::Move,
1 => Operation::Close, 1 => Operation::Close,
2 => Operation::LoadConstant, 2 => Operation::LoadConstant,
3 => Operation::DeclareVariable, 3 => Operation::DeclareLocal,
4 => Operation::GetVariable, 4 => Operation::GetLocal,
5 => Operation::SetVariable, 5 => Operation::SetLocal,
6 => Operation::Add, 6 => Operation::Add,
7 => Operation::Subtract, 7 => Operation::Subtract,
8 => Operation::Multiply, 8 => Operation::Multiply,
@ -374,9 +270,9 @@ impl Display for Operation {
Operation::Move => write!(f, "MOVE"), Operation::Move => write!(f, "MOVE"),
Operation::Close => write!(f, "CLOSE"), Operation::Close => write!(f, "CLOSE"),
Operation::LoadConstant => write!(f, "LOAD_CONSTANT"), Operation::LoadConstant => write!(f, "LOAD_CONSTANT"),
Operation::DeclareVariable => write!(f, "DECLARE_VARIABLE"), Operation::DeclareLocal => write!(f, "DECLARE_LOCAL"),
Operation::GetVariable => write!(f, "GET_VARIABLE"), Operation::GetLocal => write!(f, "GET_LOCAL"),
Operation::SetVariable => write!(f, "SET_VARIABLE"), Operation::SetLocal => write!(f, "SET_LOCAL"),
Operation::Add => write!(f, "ADD"), Operation::Add => write!(f, "ADD"),
Operation::Subtract => write!(f, "SUBTRACT"), Operation::Subtract => write!(f, "SUBTRACT"),
Operation::Multiply => write!(f, "MULTIPLY"), Operation::Multiply => write!(f, "MULTIPLY"),

View File

@ -138,7 +138,6 @@ impl<'src> Parser<'src> {
Instruction::load_constant(self.current_register, constant_index), Instruction::load_constant(self.current_register, constant_index),
position, position,
); );
self.increment_register()?;
Ok(()) Ok(())
} }
@ -276,8 +275,8 @@ impl<'src> Parser<'src> {
} }
_ => self.current_register - 1, _ => self.current_register - 1,
}; };
let last_instruction = self.chunk.pop_instruction(); let previous_instruction = self.chunk.pop_instruction();
let left_register = match last_instruction { let left_register = match previous_instruction {
Some(( Some((
Instruction { Instruction {
operation: Operation::LoadConstant, operation: Operation::LoadConstant,
@ -340,12 +339,15 @@ impl<'src> Parser<'src> {
if allow_assignment && self.allow(TokenKind::Equal)? { if allow_assignment && self.allow(TokenKind::Equal)? {
self.parse_expression()?; self.parse_expression()?;
self.emit_instruction( self.emit_instruction(
Instruction::set_local(self.current_register, local_index), Instruction::set_local(self.current_register, local_index),
self.previous_position, self.previous_position,
); );
self.increment_register()?; } else {
self.emit_instruction(
Instruction::get_local(self.current_register, local_index),
self.previous_position,
);
} }
Ok(()) Ok(())
@ -447,7 +449,7 @@ impl<'src> Parser<'src> {
let local_index = self.chunk.declare_local(identifier, position)?; let local_index = self.chunk.declare_local(identifier, position)?;
self.emit_instruction( self.emit_instruction(
Instruction::declare_variable(self.current_register - 1, local_index), Instruction::declare_local(self.current_register, local_index),
position, position,
); );

View File

@ -9,7 +9,7 @@ fn let_statement() {
Ok(Chunk::with_data( Ok(Chunk::with_data(
vec![ vec![
(Instruction::load_constant(0, 0), Span(8, 10)), (Instruction::load_constant(0, 0), Span(8, 10)),
(Instruction::declare_variable(1, 0), Span(4, 5)), (Instruction::declare_local(1, 0), Span(4, 5)),
], ],
vec![Value::integer(42),], vec![Value::integer(42),],
vec![Local::new(Identifier::new("x"), 0, None)] vec![Local::new(Identifier::new("x"), 0, None)]

View File

@ -43,20 +43,20 @@ impl Vm {
self.insert(value, instruction.destination as usize, position)?; self.insert(value, instruction.destination as usize, position)?;
} }
Operation::DeclareVariable => { Operation::DeclareLocal => {
let register_index = instruction.destination as usize; let register_index = instruction.destination as usize;
let local_index = u16::from_le_bytes(instruction.arguments) as usize; let local_index = u16::from_le_bytes(instruction.arguments) as usize;
let value = self.clone(register_index, position)?; let value = self.clone(register_index, position)?;
self.chunk.define_local(local_index, value, position)?; self.chunk.define_local(local_index, value, position)?;
} }
Operation::GetVariable => { Operation::GetLocal => {
let identifier_index = u16::from_le_bytes(instruction.arguments) as usize; let identifier_index = u16::from_le_bytes(instruction.arguments) as usize;
let value = self.clone(identifier_index, position)?; let value = self.clone(identifier_index, position)?;
self.insert(value, identifier_index, position)?; self.insert(value, identifier_index, position)?;
} }
Operation::SetVariable => todo!(), Operation::SetLocal => todo!(),
Operation::Add => { Operation::Add => {
let left = let left =
self.take_or_use_constant(instruction.arguments[0] as usize, position)?; self.take_or_use_constant(instruction.arguments[0] as usize, position)?;