From 8b33df3d4a1ddb2177832c5b5dc293c65d0f81ca Mon Sep 17 00:00:00 2001 From: Jeff Date: Thu, 12 Sep 2024 13:03:24 -0400 Subject: [PATCH] Prettify the hell out of chunk disassembly --- dust-lang/src/chunk.rs | 235 ++++++++++++++++++++++----------- dust-lang/src/instruction.rs | 236 ++++++++++------------------------ dust-lang/src/parser/mod.rs | 14 +- dust-lang/src/parser/tests.rs | 2 +- dust-lang/src/vm.rs | 6 +- 5 files changed, 240 insertions(+), 253 deletions(-) diff --git a/dust-lang/src/chunk.rs b/dust-lang/src/chunk.rs index 209d2b4..36876ac 100644 --- a/dust-lang/src/chunk.rs +++ b/dust-lang/src/chunk.rs @@ -104,7 +104,7 @@ impl Chunk { pub fn get_local(&self, index: usize, position: Span) -> Result<&Local, ChunkError> { self.locals - .get(index as usize) + .get(index) .ok_or(ChunkError::LocalIndexOutOfBounds { index, position }) } @@ -193,78 +193,8 @@ impl Chunk { self.locals.pop() } - pub fn disassemble(&self, name: &str) -> String { - let mut output = String::new(); - - 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 + pub fn disassemble<'a>(&self, name: &'a str) -> DisassembledChunk<'a> { + DisassembledChunk::new(name, self) } } @@ -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(¢ered); + }; + + 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)] pub enum ChunkError { CodeIndexOfBounds { diff --git a/dust-lang/src/instruction.rs b/dust-lang/src/instruction.rs index 71071cc..f4c9eed 100644 --- a/dust-lang/src/instruction.rs +++ b/dust-lang/src/instruction.rs @@ -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 { - operation: Operation::DeclareVariable, + operation: Operation::DeclareLocal, destination: to_register, 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 { - operation: Operation::GetVariable, + operation: Operation::GetLocal, destination: to_register, arguments: variable_index.to_le_bytes(), } @@ -72,7 +72,7 @@ impl Instruction { pub fn set_local(from_register: u8, variable_index: u16) -> Instruction { Instruction { - operation: Operation::SetVariable, + operation: Operation::SetLocal, destination: from_register, arguments: variable_index.to_le_bytes(), } @@ -127,197 +127,93 @@ impl Instruction { } 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 { + let info = match self.operation { Operation::Move => { - format!( - "{:16} R({}) R({})", - self.operation.to_string(), - self.destination, - self.arguments[0] - ) + format!("R({}) R({})", self.destination, self.arguments[0]) } - Operation::Close => format!("{:16} R({})", self.operation, self.destination), + Operation::Close => format!("R({})", self.destination), Operation::LoadConstant => { 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!( - "{:16} R({}) = C({}) {}", - self.operation.to_string(), - self.destination, - constant_index, - constant_display - ) + if let Some(chunk) = chunk { + match chunk.get_constant(constant_index, Span(0, 0)) { + Ok(value) => { + format!("R({}) = C({}) {}", self.destination, constant_index, value) + } + 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]]); - format!( - "{:16} L({}) = R({})", - self.operation.to_string(), - local_index, - self.destination - ) + format!("L({}) = R({})", 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]]); - format!( - "{: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 - ) + format!("L({}) = R({})", identifier_index, self.destination) } Operation::Add => { format!( - "{:16} R({}) = RC({}) + RC({})", - self.operation.to_string(), - self.destination, - self.arguments[0], - self.arguments[1] + "R({}) = RC({}) + RC({})", + self.destination, self.arguments[0], self.arguments[1] ) } Operation::Subtract => { format!( - "{:16} R({}) = RC({}) - RC({})", - self.operation.to_string(), - self.destination, - self.arguments[0], - self.arguments[1] + "R({}) = RC({}) - RC({})", + self.destination, self.arguments[0], self.arguments[1] ) } Operation::Multiply => { format!( - "{:16} R({}) = RC({}) * RC({})", - self.operation.to_string(), - self.destination, - self.arguments[0], - self.arguments[1] + "R({}) = RC({}) * RC({})", + self.destination, self.arguments[0], self.arguments[1] ) } Operation::Divide => { format!( - "{:16} R({}) = RC({}) / RC({})", - self.operation.to_string(), - self.destination, - self.arguments[0], - self.arguments[1] + "R({}) = RC({}) / RC({})", + self.destination, self.arguments[0], self.arguments[1] ) } Operation::Negate => { - format!( - "{:16} R({}) = -RC({})", - self.operation.to_string(), - self.destination, - self.arguments[0] - ) + format!("R({}) = -RC({})", self.destination, self.arguments[0]) } - Operation::Return => { - format!("{:16}", self.operation.to_string()) - } - } + Operation::Return => return None, + }; + + Some(info) } } impl Display for Instruction { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self.operation { - Operation::Move => { - write!( - f, - "{} 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) - } + if let Some(info) = self.disassembly_info(None) { + write!(f, "{} {}", self.operation, info) + } else { + write!(f, "{}", self.operation) } } } @@ -332,9 +228,9 @@ pub enum Operation { LoadConstant = 2, // Variables - DeclareVariable = 3, - GetVariable = 4, - SetVariable = 5, + DeclareLocal = 3, + GetLocal = 4, + SetLocal = 5, // Binary operations Add = 6, @@ -355,9 +251,9 @@ impl From for Operation { 0 => Operation::Move, 1 => Operation::Close, 2 => Operation::LoadConstant, - 3 => Operation::DeclareVariable, - 4 => Operation::GetVariable, - 5 => Operation::SetVariable, + 3 => Operation::DeclareLocal, + 4 => Operation::GetLocal, + 5 => Operation::SetLocal, 6 => Operation::Add, 7 => Operation::Subtract, 8 => Operation::Multiply, @@ -374,9 +270,9 @@ impl Display for Operation { Operation::Move => write!(f, "MOVE"), Operation::Close => write!(f, "CLOSE"), Operation::LoadConstant => write!(f, "LOAD_CONSTANT"), - Operation::DeclareVariable => write!(f, "DECLARE_VARIABLE"), - Operation::GetVariable => write!(f, "GET_VARIABLE"), - Operation::SetVariable => write!(f, "SET_VARIABLE"), + Operation::DeclareLocal => write!(f, "DECLARE_LOCAL"), + Operation::GetLocal => write!(f, "GET_LOCAL"), + Operation::SetLocal => write!(f, "SET_LOCAL"), Operation::Add => write!(f, "ADD"), Operation::Subtract => write!(f, "SUBTRACT"), Operation::Multiply => write!(f, "MULTIPLY"), diff --git a/dust-lang/src/parser/mod.rs b/dust-lang/src/parser/mod.rs index 7356b51..d974b87 100644 --- a/dust-lang/src/parser/mod.rs +++ b/dust-lang/src/parser/mod.rs @@ -138,7 +138,6 @@ impl<'src> Parser<'src> { Instruction::load_constant(self.current_register, constant_index), position, ); - self.increment_register()?; Ok(()) } @@ -276,8 +275,8 @@ impl<'src> Parser<'src> { } _ => self.current_register - 1, }; - let last_instruction = self.chunk.pop_instruction(); - let left_register = match last_instruction { + let previous_instruction = self.chunk.pop_instruction(); + let left_register = match previous_instruction { Some(( Instruction { operation: Operation::LoadConstant, @@ -340,12 +339,15 @@ impl<'src> Parser<'src> { if allow_assignment && self.allow(TokenKind::Equal)? { self.parse_expression()?; - self.emit_instruction( Instruction::set_local(self.current_register, local_index), self.previous_position, ); - self.increment_register()?; + } else { + self.emit_instruction( + Instruction::get_local(self.current_register, local_index), + self.previous_position, + ); } Ok(()) @@ -447,7 +449,7 @@ impl<'src> Parser<'src> { let local_index = self.chunk.declare_local(identifier, position)?; self.emit_instruction( - Instruction::declare_variable(self.current_register - 1, local_index), + Instruction::declare_local(self.current_register, local_index), position, ); diff --git a/dust-lang/src/parser/tests.rs b/dust-lang/src/parser/tests.rs index bcbfa44..b3861c8 100644 --- a/dust-lang/src/parser/tests.rs +++ b/dust-lang/src/parser/tests.rs @@ -9,7 +9,7 @@ fn let_statement() { Ok(Chunk::with_data( vec![ (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![Local::new(Identifier::new("x"), 0, None)] diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index 64c4692..51373ff 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -43,20 +43,20 @@ impl Vm { self.insert(value, instruction.destination as usize, position)?; } - Operation::DeclareVariable => { + Operation::DeclareLocal => { let register_index = instruction.destination as usize; let local_index = u16::from_le_bytes(instruction.arguments) as usize; let value = self.clone(register_index, 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 value = self.clone(identifier_index, position)?; self.insert(value, identifier_index, position)?; } - Operation::SetVariable => todo!(), + Operation::SetLocal => todo!(), Operation::Add => { let left = self.take_or_use_constant(instruction.arguments[0] as usize, position)?;